Compare commits

...

17 Commits

Author SHA1 Message Date
9f9744a142 Fixed now? 2023-10-07 09:55:57 +04:00
b101d54049 Flip textures before uploading to gpu 2023-10-07 09:22:32 +04:00
41b5aea185 Use sRGBA on GPU as PNG/JPEG generally uses that nowadays 2023-10-07 08:46:33 +04:00
3574318552 Fix iterator bug in Nex() 2023-10-07 01:16:10 +04:00
05ccf3e158 Handle one more iterator case 2023-10-06 08:52:27 +04:00
4f5fd50660 Fix iterator bug 2023-10-06 08:42:02 +04:00
aaea27b543 Add an iterator to the registry 2023-10-06 08:10:11 +04:00
039d09f888 Redo and simplify registry and move to own package 2023-10-06 07:28:16 +04:00
1b83d7f9a7 Change Entity->BaseEntity + Add Entity interface 2023-10-06 04:23:42 +04:00
201d9546b2 Make basecomp not use pointer receiver 2023-10-06 04:09:57 +04:00
c1d5033eb0 Separate components from entity 2023-10-06 03:52:43 +04:00
6f646540f9 Enable imgui docking + minor changes 2023-07-24 23:40:20 +04:00
a99dd304ed Complete basic imgui integration 2023-07-24 21:14:09 +04:00
4e45995ed0 Imgui key mapping 2023-07-24 20:39:45 +04:00
a735e01a77 Start transition to github.com/AllenDang/cimgui-go for imgui because the old wrapper is now depcreated. This is auto generated so has much better chance of being supported, and we get latest imgui always (including docking!) 2023-07-24 01:05:42 +04:00
abb45e4c4a Update SDL dep 2023-07-23 23:54:47 +04:00
78ea3ae747 Better DPI handling on windows (crispy text!) 2023-02-04 05:21:48 +04:00
22 changed files with 727 additions and 350 deletions

View File

@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"fmt" "fmt"
"image" "image"
"image/color"
"image/jpeg" "image/jpeg"
"image/png" "image/png"
"io" "io"
@ -14,6 +13,7 @@ import (
"unsafe" "unsafe"
"github.com/go-gl/gl/v4.1-core/gl" "github.com/go-gl/gl/v4.1-core/gl"
"github.com/mandykoh/prism"
) )
type ColorFormat int type ColorFormat int
@ -23,11 +23,20 @@ const (
) )
type Texture struct { type Texture struct {
//Path only exists for textures loaded from disk // Path only exists for textures loaded from disk
Path string Path string
TexID uint32
Width int32 TexID uint32
// Width is the width of the texture in pixels (pixels per row).
// Note that the number of bytes constituting a row is MORE than this (e.g. for RGBA8, bytesPerRow=width*4, since we have 4 bytes per pixel)
Width int32
// Height is the height of the texture in pixels (pixels per column).
// Note that the number of bytes constituting a column is MORE than this (e.g. for RGBA8, bytesPerColumn=height*4, since we have 4 bytes per pixel)
Height int32 Height int32
// Pixels usually stored in RGBA format
Pixels []byte Pixels []byte
} }
@ -67,16 +76,20 @@ func LoadTexturePNG(file string, loadOptions *TextureLoadOptions) (Texture, erro
return Texture{}, err return Texture{}, err
} }
img, err := png.Decode(bytes.NewReader(fileBytes)) bytesReader := bytes.NewReader(fileBytes)
img, err := png.Decode(bytesReader)
if err != nil { if err != nil {
return Texture{}, err return Texture{}, err
} }
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
tex := Texture{ tex := Texture{
Path: file, Path: file,
Pixels: nrgbaImg.Pix,
Width: int32(nrgbaImg.Bounds().Dx()),
Height: int32(nrgbaImg.Bounds().Dy()),
} }
flipImgPixelsVertically(tex.Pixels, int(tex.Width), int(tex.Height), 4)
tex.Pixels, tex.Width, tex.Height = pixelsFromNrgbaPng(img)
//Prepare opengl stuff //Prepare opengl stuff
gl.GenTextures(1, &tex.TexID) gl.GenTextures(1, &tex.TexID)
@ -89,7 +102,7 @@ func LoadTexturePNG(file string, loadOptions *TextureLoadOptions) (Texture, erro
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
// load and generate the texture // load and generate the texture
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0])) gl.TexImage2D(gl.TEXTURE_2D, 0, gl.SRGB_ALPHA, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
if loadOptions.GenMipMaps { if loadOptions.GenMipMaps {
gl.GenerateMipmap(tex.TexID) gl.GenerateMipmap(tex.TexID)
@ -112,8 +125,14 @@ func LoadTextureInMemPngImg(img image.Image, loadOptions *TextureLoadOptions) (T
loadOptions = &TextureLoadOptions{} loadOptions = &TextureLoadOptions{}
} }
tex := Texture{} nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
tex.Pixels, tex.Width, tex.Height = pixelsFromNrgbaPng(img) tex := Texture{
Path: "",
Pixels: nrgbaImg.Pix,
Height: int32(nrgbaImg.Bounds().Dy()),
Width: int32(nrgbaImg.Bounds().Dx()),
}
flipImgPixelsVertically(tex.Pixels, int(tex.Width), int(tex.Height), 4)
//Prepare opengl stuff //Prepare opengl stuff
gl.GenTextures(1, &tex.TexID) gl.GenTextures(1, &tex.TexID)
@ -126,7 +145,7 @@ func LoadTextureInMemPngImg(img image.Image, loadOptions *TextureLoadOptions) (T
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
// load and generate the texture // load and generate the texture
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0])) gl.TexImage2D(gl.TEXTURE_2D, 0, gl.SRGB8_ALPHA8, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
if loadOptions.GenMipMaps { if loadOptions.GenMipMaps {
gl.GenerateMipmap(tex.TexID) gl.GenerateMipmap(tex.TexID)
@ -166,11 +185,14 @@ func LoadTextureJpeg(file string, loadOptions *TextureLoadOptions) (Texture, err
return Texture{}, err return Texture{}, err
} }
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
tex := Texture{ tex := Texture{
Path: file, Path: file,
Pixels: nrgbaImg.Pix,
Height: int32(nrgbaImg.Bounds().Dy()),
Width: int32(nrgbaImg.Bounds().Dx()),
} }
flipImgPixelsVertically(tex.Pixels, int(tex.Width), int(tex.Height), 4)
tex.Pixels, tex.Width, tex.Height = pixelsFromNrgbaPng(img)
//Prepare opengl stuff //Prepare opengl stuff
gl.GenTextures(1, &tex.TexID) gl.GenTextures(1, &tex.TexID)
@ -183,7 +205,7 @@ func LoadTextureJpeg(file string, loadOptions *TextureLoadOptions) (Texture, err
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
// load and generate the texture // load and generate the texture
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0])) gl.TexImage2D(gl.TEXTURE_2D, 0, gl.SRGB8_ALPHA8, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
if loadOptions.GenMipMaps { if loadOptions.GenMipMaps {
gl.GenerateMipmap(tex.TexID) gl.GenerateMipmap(tex.TexID)
@ -200,65 +222,14 @@ func LoadTextureJpeg(file string, loadOptions *TextureLoadOptions) (Texture, err
return tex, nil return tex, nil
} }
func pixelsFromNrgbaPng(img image.Image) (pixels []byte, width, height int32) {
//NOTE: Load bottom left to top right because this is the texture coordinate system used by OpenGL
//NOTE: We only support 8-bit channels (32-bit colors) for now
i := 0
width, height = int32(img.Bounds().Dx()), int32(img.Bounds().Dy())
pixels = make([]byte, img.Bounds().Dx()*img.Bounds().Dy()*4)
for y := img.Bounds().Dy() - 1; y >= 0; y-- {
for x := 0; x < img.Bounds().Dx(); x++ {
c := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA)
pixels[i] = c.R
pixels[i+1] = c.G
pixels[i+2] = c.B
pixels[i+3] = c.A
i += 4
}
}
return pixels, width, height
}
func pixelsFromNrgbaJpg(img image.Image) (pixels []byte, width, height int32) {
//NOTE: Load bottom left to top right because this is the texture coordinate system used by OpenGL
//NOTE: We only support 8-bit channels (32-bit colors) for now
i := 0
width, height = int32(img.Bounds().Dx()), int32(img.Bounds().Dy())
pixels = make([]byte, img.Bounds().Dx()*img.Bounds().Dy()*4)
for y := img.Bounds().Dy() - 1; y >= 0; y-- {
for x := 0; x < img.Bounds().Dx(); x++ {
c := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA)
pixels[i] = c.R
pixels[i+1] = c.G
pixels[i+2] = c.B
pixels[i+3] = c.A
i += 4
}
}
return pixels, width, height
}
func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex string) (Cubemap, error) { func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex string) (Cubemap, error) {
var imgDecoder func(r io.Reader) (image.Image, error) var imgDecoder func(r io.Reader) (image.Image, error)
var pixelDecoder func(image.Image) ([]byte, int32, int32)
ext := strings.ToLower(path.Ext(rightTex)) ext := strings.ToLower(path.Ext(rightTex))
if ext == ".jpg" || ext == ".jpeg" { if ext == ".jpg" || ext == ".jpeg" {
imgDecoder = jpeg.Decode imgDecoder = jpeg.Decode
pixelDecoder = pixelsFromNrgbaJpg
} else if ext == ".png" { } else if ext == ".png" {
imgDecoder = png.Decode imgDecoder = png.Decode
pixelDecoder = pixelsFromNrgbaPng
} else { } else {
return Cubemap{}, fmt.Errorf("unknown image extension: %s. Expected one of: .jpg, .jpeg, .png", ext) return Cubemap{}, fmt.Errorf("unknown image extension: %s. Expected one of: .jpg, .jpeg, .png", ext)
} }
@ -292,9 +263,11 @@ func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex st
return Cubemap{}, err return Cubemap{}, err
} }
pixels, width, height := pixelDecoder(img) nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
height := int32(nrgbaImg.Bounds().Dy())
width := int32(nrgbaImg.Bounds().Dx())
gl.TexImage2D(uint32(gl.TEXTURE_CUBE_MAP_POSITIVE_X)+i, 0, gl.RGBA8, int32(width), int32(height), 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&pixels[0])) gl.TexImage2D(uint32(gl.TEXTURE_CUBE_MAP_POSITIVE_X)+i, 0, gl.RGBA8, int32(width), int32(height), 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&nrgbaImg.Pix[0]))
} }
// set the texture wrapping/filtering options (on the currently bound texture object) // set the texture wrapping/filtering options (on the currently bound texture object)
@ -306,3 +279,21 @@ func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex st
return cmap, nil return cmap, nil
} }
func flipImgPixelsVertically(bytes []byte, width, height, bytesPerPixel int) {
// Flip the image vertically such that (e.g. in an image of 10 rows) rows 0<->9, 1<->8, 2<->7 etc are swapped.
// We do this because images are usually stored top-left to bottom-right, while opengl stores textures bottom-left to top-right, so if we don't swap
// rows textures will appear inverted
widthInBytes := width * bytesPerPixel
rowData := make([]byte, width*bytesPerPixel)
for rowNum := 0; rowNum < height/2; rowNum++ {
upperRowStartIndex := rowNum * widthInBytes
lowerRowStartIndex := (height - rowNum - 1) * widthInBytes
copy(rowData, bytes[upperRowStartIndex:upperRowStartIndex+widthInBytes])
copy(bytes[upperRowStartIndex:upperRowStartIndex+widthInBytes], bytes[lowerRowStartIndex:lowerRowStartIndex+widthInBytes])
copy(bytes[lowerRowStartIndex:lowerRowStartIndex+widthInBytes], rowData)
}
}

View File

@ -3,12 +3,13 @@ package engine
import ( import (
"runtime" "runtime"
imgui "github.com/AllenDang/cimgui-go"
"github.com/bloeys/nmage/assert" "github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/input" "github.com/bloeys/nmage/input"
"github.com/bloeys/nmage/renderer" "github.com/bloeys/nmage/renderer"
"github.com/bloeys/nmage/timing" "github.com/bloeys/nmage/timing"
nmageimgui "github.com/bloeys/nmage/ui/imgui"
"github.com/go-gl/gl/v4.1-core/gl" "github.com/go-gl/gl/v4.1-core/gl"
"github.com/inkyblackness/imgui-go/v4"
"github.com/veandco/go-sdl2/sdl" "github.com/veandco/go-sdl2/sdl"
) )
@ -26,8 +27,9 @@ type Window struct {
func (w *Window) handleInputs() { func (w *Window) handleInputs() {
input.EventLoopStart() input.EventLoopStart()
imIO := imgui.CurrentIO() imIo := imgui.CurrentIO()
// @TODO: Would be nice to have imgui package process its own events via a callback instead of it being part of engine code
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
//Fire callbacks //Fire callbacks
@ -43,19 +45,32 @@ func (w *Window) handleInputs() {
input.HandleMouseWheelEvent(e) input.HandleMouseWheelEvent(e)
xDelta, yDelta := input.GetMouseWheelMotion() xDelta, yDelta := input.GetMouseWheelMotion()
imIO.AddMouseWheelDelta(float32(xDelta), float32(yDelta)) imIo.AddMouseWheelDelta(float32(xDelta), float32(yDelta))
case *sdl.KeyboardEvent: case *sdl.KeyboardEvent:
input.HandleKeyboardEvent(e)
if e.Type == sdl.KEYDOWN { input.HandleKeyboardEvent(e)
imIO.KeyPress(int(e.Keysym.Scancode)) imIo.AddKeyEvent(nmageimgui.SdlScancodeToImGuiKey(e.Keysym.Scancode), e.Type == sdl.KEYDOWN)
} else if e.Type == sdl.KEYUP {
imIO.KeyRelease(int(e.Keysym.Scancode)) // Send modifier key updates to imgui
if e.Keysym.Sym == sdl.K_LCTRL || e.Keysym.Sym == sdl.K_RCTRL {
imIo.SetKeyCtrl(e.Type == sdl.KEYDOWN)
}
if e.Keysym.Sym == sdl.K_LSHIFT || e.Keysym.Sym == sdl.K_RSHIFT {
imIo.SetKeyShift(e.Type == sdl.KEYDOWN)
}
if e.Keysym.Sym == sdl.K_LALT || e.Keysym.Sym == sdl.K_RALT {
imIo.SetKeyAlt(e.Type == sdl.KEYDOWN)
}
if e.Keysym.Sym == sdl.K_LGUI || e.Keysym.Sym == sdl.K_RGUI {
imIo.SetKeySuper(e.Type == sdl.KEYDOWN)
} }
case *sdl.TextInputEvent: case *sdl.TextInputEvent:
imIO.AddInputCharacters(string(e.Text[:])) imIo.AddInputCharactersUTF8(e.GetText())
case *sdl.MouseButtonEvent: case *sdl.MouseButtonEvent:
input.HandleMouseBtnEvent(e) input.HandleMouseBtnEvent(e)
@ -75,15 +90,11 @@ func (w *Window) handleInputs() {
// If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame.
x, y, _ := sdl.GetMouseState() x, y, _ := sdl.GetMouseState()
imIO.SetMousePosition(imgui.Vec2{X: float32(x), Y: float32(y)}) imIo.SetMousePos(imgui.Vec2{X: float32(x), Y: float32(y)})
imIO.SetMouseButtonDown(0, input.MouseDown(sdl.BUTTON_LEFT)) imIo.SetMouseButtonDown(0, input.MouseDown(sdl.BUTTON_LEFT))
imIO.SetMouseButtonDown(1, input.MouseDown(sdl.BUTTON_RIGHT)) imIo.SetMouseButtonDown(1, input.MouseDown(sdl.BUTTON_RIGHT))
imIO.SetMouseButtonDown(2, input.MouseDown(sdl.BUTTON_MIDDLE)) imIo.SetMouseButtonDown(2, input.MouseDown(sdl.BUTTON_MIDDLE))
imIO.KeyShift(sdl.SCANCODE_LSHIFT, sdl.SCANCODE_RSHIFT)
imIO.KeyCtrl(sdl.SCANCODE_LCTRL, sdl.SCANCODE_RCTRL)
imIO.KeyAlt(sdl.SCANCODE_LALT, sdl.SCANCODE_RALT)
} }
func (w *Window) handleWindowResize() { func (w *Window) handleWindowResize() {

View File

@ -1,27 +1,26 @@
package entity package entity
import "github.com/bloeys/nmage/assert" import "github.com/bloeys/nmage/registry"
var _ Comp = &BaseComp{} var _ Comp = &BaseComp{}
type BaseComp struct { type BaseComp struct {
Entity *Entity Handle registry.Handle
} }
func (b *BaseComp) base() { func (b BaseComp) baseComp() {
} }
func (b *BaseComp) Init(parent *Entity) { func (b *BaseComp) Init(parentHandle registry.Handle) {
assert.T(parent != nil, "Component was initialized with a nil parent. That is not allowed.") b.Handle = parentHandle
b.Entity = parent
} }
func (b *BaseComp) Name() string { func (b BaseComp) Name() string {
return "Base Component" return "Base Component"
} }
func (b *BaseComp) Update() { func (b BaseComp) Update() {
} }
func (b *BaseComp) Destroy() { func (b BaseComp) Destroy() {
} }

View File

@ -1,27 +1,38 @@
package entity package entity
import "github.com/bloeys/nmage/assert" import (
"github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/registry"
)
type Comp interface { type Comp interface {
// This ensures that implementors of the Comp interface // This ensures that implementors of the Comp interface
// always embed BaseComp // always embed BaseComp
base() baseComp()
Name() string Name() string
Init(parent *Entity) Init(parentHandle registry.Handle)
Update() Update()
Destroy() Destroy()
} }
func AddComp[T Comp](e *Entity, c T) { func NewCompContainer() CompContainer {
return CompContainer{Comps: []Comp{}}
assert.T(!HasComp[T](e), "Entity with id '%v' already has component of type '%T'", e.ID, c)
e.Comps = append(e.Comps, c)
c.Init(e)
} }
func HasComp[T Comp](e *Entity) bool { type CompContainer struct {
Comps []Comp
}
func AddComp[T Comp](entityHandle registry.Handle, cc *CompContainer, c T) {
assert.T(!HasComp[T](cc), "Entity with id '%v' already has component of type '%T'", entityHandle, c)
cc.Comps = append(cc.Comps, c)
c.Init(entityHandle)
}
func HasComp[T Comp](e *CompContainer) bool {
for i := 0; i < len(e.Comps); i++ { for i := 0; i < len(e.Comps); i++ {
@ -34,7 +45,7 @@ func HasComp[T Comp](e *Entity) bool {
return false return false
} }
func GetComp[T Comp](e *Entity) (out T) { func GetComp[T Comp](e *CompContainer) (out T) {
for i := 0; i < len(e.Comps); i++ { for i := 0; i < len(e.Comps); i++ {
@ -48,7 +59,7 @@ func GetComp[T Comp](e *Entity) (out T) {
} }
// DestroyComp calls Destroy on the component and then removes it from the entities component list // DestroyComp calls Destroy on the component and then removes it from the entities component list
func DestroyComp[T Comp](e *Entity) { func DestroyComp[T Comp](e *CompContainer) {
for i := 0; i < len(e.Comps); i++ { for i := 0; i < len(e.Comps); i++ {

View File

@ -1,49 +1,7 @@
package entity package entity
type EntityFlag byte import "github.com/bloeys/nmage/registry"
const ( type Entity interface {
EntityFlag_None EntityFlag = 0 GetHandle() registry.Handle
EntityFlag_Alive EntityFlag = 1 << (iota - 1)
)
const (
GenerationShiftBits = 64 - 8
FlagsShiftBits = 64 - 16
IndexBitMask = 0x00_00_FFFF_FFFF_FFFF
)
type EntityHandle uint64
type Entity struct {
// Byte 1: Generation; Byte 2: Flags; Bytes 3-8: Index
ID EntityHandle
Comps []Comp
}
func (e *Entity) HasFlag(ef EntityFlag) bool {
return GetFlags(e.ID)&ef > 0
}
func (e *Entity) UpdateAllComps() {
for i := 0; i < len(e.Comps); i++ {
e.Comps[i].Update()
}
}
func GetGeneration(id EntityHandle) byte {
return byte(id >> GenerationShiftBits)
}
func GetFlags(id EntityHandle) EntityFlag {
return EntityFlag(id >> FlagsShiftBits)
}
func GetIndex(id EntityHandle) uint64 {
return uint64(id & IndexBitMask)
}
func NewEntityId(generation byte, flags EntityFlag, index uint64) EntityHandle {
return EntityHandle(index | (uint64(generation) << GenerationShiftBits) | (uint64(flags) << FlagsShiftBits))
} }

View File

@ -1,109 +0,0 @@
package entity
import (
"github.com/bloeys/nmage/assert"
)
var (
// The number of slots required to be in the free list before the free list
// is used for creating new entries
FreeListUsageThreshold uint32 = 20
)
type freeListitem struct {
EntityIndex uint64
nextFree *freeListitem
}
type Registry struct {
EntityCount uint64
Entities []Entity
FreeList *freeListitem
FreeListSize uint32
}
func (r *Registry) NewEntity() *Entity {
assert.T(r.EntityCount < uint64(len(r.Entities)), "Can not add more entities to registry because it is full")
entityToUseIndex := uint64(0)
var entityToUse *Entity = nil
if r.FreeList != nil && r.FreeListSize > FreeListUsageThreshold {
entityToUseIndex = r.FreeList.EntityIndex
entityToUse = &r.Entities[entityToUseIndex]
r.FreeList = r.FreeList.nextFree
r.FreeListSize--
} else {
for i := 0; i < len(r.Entities); i++ {
e := &r.Entities[i]
if e.HasFlag(EntityFlag_Alive) {
continue
}
entityToUse = e
entityToUseIndex = uint64(i)
break
}
}
if entityToUse == nil {
panic("failed to create new entity because we did not find a free spot in the registry. Why did the assert not go off?")
}
r.EntityCount++
entityToUse.ID = NewEntityId(GetGeneration(entityToUse.ID)+1, EntityFlag_Alive, entityToUseIndex)
assert.T(entityToUse.ID != 0, "Entity ID must not be zero")
return entityToUse
}
func (r *Registry) GetEntity(id EntityHandle) *Entity {
index := GetIndex(id)
gen := GetGeneration(id)
e := &r.Entities[index]
eGen := GetGeneration(e.ID)
if gen != eGen {
return nil
}
return e
}
// FreeEntity calls Destroy on all the entities components, resets the component list, resets the entity flags, then ads this entity to the free list
func (r *Registry) FreeEntity(id EntityHandle) {
e := r.GetEntity(id)
if e == nil {
return
}
for i := 0; i < len(e.Comps); i++ {
e.Comps[i].Destroy()
}
r.EntityCount--
eIndex := GetIndex(e.ID)
e.Comps = []Comp{}
e.ID = NewEntityId(GetGeneration(e.ID), EntityFlag_None, eIndex)
r.FreeList = &freeListitem{
EntityIndex: eIndex,
nextFree: r.FreeList,
}
r.FreeListSize++
}
func NewRegistry(size uint32) *Registry {
assert.T(size > 0, "Registry size must be more than zero")
return &Registry{
Entities: make([]Entity, size),
}
}

10
go.mod
View File

@ -2,12 +2,18 @@ module github.com/bloeys/nmage
go 1.18 go 1.18
require github.com/veandco/go-sdl2 v0.4.25 require github.com/veandco/go-sdl2 v0.4.35
require github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 require github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6
require ( require (
github.com/bloeys/assimp-go v0.4.4 github.com/bloeys/assimp-go v0.4.4
github.com/bloeys/gglm v0.43.0 github.com/bloeys/gglm v0.43.0
github.com/inkyblackness/imgui-go/v4 v4.6.0
) )
require (
github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2
github.com/mandykoh/prism v0.35.1
)
require github.com/mandykoh/go-parallel v0.1.0 // indirect

45
go.sum
View File

@ -1,17 +1,40 @@
github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2 h1:3HA/5qD8Rimxz/y1sLyVaM7ws1dzjXzMt4hOBiwHggo=
github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2/go.mod h1:iNfbIyOBN8k3XScMxULbrwYbPsXEAUD0Jb6UwrspQb8=
github.com/bloeys/assimp-go v0.4.4 h1:Yn5e/RpE0Oes0YMBy8O7KkwAO4R/RpgrZPJCt08dVIU= github.com/bloeys/assimp-go v0.4.4 h1:Yn5e/RpE0Oes0YMBy8O7KkwAO4R/RpgrZPJCt08dVIU=
github.com/bloeys/assimp-go v0.4.4/go.mod h1:my3yRxT7CfOztmvi+0svmwbaqw0KFrxaHxncoyaEIP0= github.com/bloeys/assimp-go v0.4.4/go.mod h1:my3yRxT7CfOztmvi+0svmwbaqw0KFrxaHxncoyaEIP0=
github.com/bloeys/gglm v0.43.0 h1:ZpOghR3PHfpkigTDh+FqxLsF0gN8CD6s/bWoei6LyxI= github.com/bloeys/gglm v0.43.0 h1:ZpOghR3PHfpkigTDh+FqxLsF0gN8CD6s/bWoei6LyxI=
github.com/bloeys/gglm v0.43.0/go.mod h1:qwJQ0WzV191wAMwlGicbfbChbKoSedMk7gFFX6GnyOk= github.com/bloeys/gglm v0.43.0/go.mod h1:qwJQ0WzV191wAMwlGicbfbChbKoSedMk7gFFX6GnyOk=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk= github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
github.com/inkyblackness/imgui-go/v4 v4.6.0 h1:ShcnXEYl80+xREGBY9OpGWePA6FfJChY9Varsm+3jjE= github.com/mandykoh/go-parallel v0.1.0 h1:7vJMNMC4dsbgZdkAb2A8tV5ENY1v7VxIO1wzQWZoT8k=
github.com/inkyblackness/imgui-go/v4 v4.6.0/go.mod h1:g8SAGtOYUP7rYaOB2AsVKCEHmPMDmJKgt4z6d+flhb0= github.com/mandykoh/go-parallel v0.1.0/go.mod h1:lkYHqG1JNTaSS6lG+PgFCnyMd2VDy8pH9jN9pY899ig=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/mandykoh/prism v0.35.1 h1:JbQfQarANxSWlgJEpjv+E7DvtrqBaVP1YgJfZPvo6ME=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/mandykoh/prism v0.35.1/go.mod h1:3miB3EAJ0IggYl/4eBB5MmawRbyJI1gKDtbrVvk8Q9I=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/veandco/go-sdl2 v0.4.35 h1:NohzsfageDWGtCd9nf7Pc3sokMK/MOK+UA2QMJARWzQ=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/veandco/go-sdl2 v0.4.35/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/veandco/go-sdl2 v0.4.25 h1:J5ac3KKOccp/0xGJA1PaNYKPUcZm19IxhDGs8lJofPI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
github.com/veandco/go-sdl2 v0.4.25/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -2,19 +2,16 @@ package level
import ( import (
"github.com/bloeys/nmage/assert" "github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/entity"
) )
type Level struct { type Level struct {
*entity.Registry
Name string Name string
} }
func NewLevel(name string, maxEntities uint32) *Level { func NewLevel(name string) *Level {
assert.T(name != "", "Level name can not be empty") assert.T(name != "", "Level name can not be empty")
return &Level{ return &Level{
Name: name, Name: name,
Registry: entity.NewRegistry(maxEntities),
} }
} }

89
main.go
View File

@ -2,22 +2,23 @@ package main
import ( import (
"fmt" "fmt"
"runtime"
imgui "github.com/AllenDang/cimgui-go"
"github.com/bloeys/gglm/gglm" "github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/assets" "github.com/bloeys/nmage/assets"
"github.com/bloeys/nmage/camera" "github.com/bloeys/nmage/camera"
"github.com/bloeys/nmage/engine" "github.com/bloeys/nmage/engine"
"github.com/bloeys/nmage/entity" "github.com/bloeys/nmage/entity"
"github.com/bloeys/nmage/input" "github.com/bloeys/nmage/input"
"github.com/bloeys/nmage/level"
"github.com/bloeys/nmage/logging" "github.com/bloeys/nmage/logging"
"github.com/bloeys/nmage/materials" "github.com/bloeys/nmage/materials"
"github.com/bloeys/nmage/meshes" "github.com/bloeys/nmage/meshes"
"github.com/bloeys/nmage/registry"
"github.com/bloeys/nmage/renderer/rend3dgl" "github.com/bloeys/nmage/renderer/rend3dgl"
"github.com/bloeys/nmage/timing" "github.com/bloeys/nmage/timing"
nmageimgui "github.com/bloeys/nmage/ui/imgui" nmageimgui "github.com/bloeys/nmage/ui/imgui"
"github.com/go-gl/gl/v4.1-core/gl" "github.com/go-gl/gl/v4.1-core/gl"
"github.com/inkyblackness/imgui-go/v4"
"github.com/veandco/go-sdl2/sdl" "github.com/veandco/go-sdl2/sdl"
) )
@ -35,6 +36,9 @@ import (
const ( const (
camSpeed = 15 camSpeed = 15
mouseSensitivity = 0.5 mouseSensitivity = 0.5
unscaledWindowWidth = 1280
unscaledWindowHeight = 720
) )
var ( var (
@ -60,6 +64,8 @@ var (
debugDrawDepthBuffer bool debugDrawDepthBuffer bool
skyboxCmap assets.Cubemap skyboxCmap assets.Cubemap
dpiScaling float32
) )
type OurGame struct { type OurGame struct {
@ -81,24 +87,39 @@ func (t *TransformComp) Name() string {
func Test() { func Test() {
lvl := level.NewLevel("test level", 1000) // lvl := level.NewLevel("test level")
e1 := lvl.Registry.NewEntity() testRegistry := registry.NewRegistry[int](100)
trComp := entity.GetComp[*TransformComp](e1) e1, e1Handle := testRegistry.New()
e1CompContainer := entity.NewCompContainer()
fmt.Printf("Entity 1: %+v; Handle: %+v; Index: %+v; Gen: %+v; Flags: %+v\n", e1, e1Handle, e1Handle.Index(), e1Handle.Generation(), e1Handle.Flags())
trComp := entity.GetComp[*TransformComp](&e1CompContainer)
fmt.Println("Get comp before adding any:", trComp) fmt.Println("Get comp before adding any:", trComp)
entity.AddComp(e1, &TransformComp{ entity.AddComp(e1Handle, &e1CompContainer, &TransformComp{
Pos: gglm.NewVec3(0, 0, 0), Pos: gglm.NewVec3(0, 0, 0),
Rot: gglm.NewQuatEulerXYZ(0, 0, 0), Rot: gglm.NewQuatEulerXYZ(0, 0, 0),
Scale: gglm.NewVec3(0, 0, 0), Scale: gglm.NewVec3(0, 0, 0),
}) })
trComp = entity.GetComp[*TransformComp](e1) trComp = entity.GetComp[*TransformComp](&e1CompContainer)
fmt.Println("Get transform comp:", trComp) fmt.Println("Get transform comp:", trComp)
fmt.Printf("Entity: %+v\n", e1) e2, e2Handle := testRegistry.New()
fmt.Printf("Entity: %+v\n", lvl.Registry.NewEntity()) e3, e3Handle := testRegistry.New()
fmt.Printf("Entity: %+v\n", lvl.Registry.NewEntity()) e4, e4Handle := testRegistry.New()
fmt.Printf("Entity: %+v\n", lvl.Registry.NewEntity()) fmt.Printf("Entity 2: %+v; Handle: %+v; Index: %+v; Gen: %+v; Flags: %+v\n", e2, e2Handle, e2Handle.Index(), e2Handle.Generation(), e2Handle.Flags())
fmt.Printf("Entity 3: %+v; Handle: %+v; Index: %+v; Gen: %+v; Flags: %+v\n", e3, e3Handle, e3Handle.Index(), e3Handle.Generation(), e3Handle.Flags())
fmt.Printf("Entity 4: %+v; Handle: %+v; Index: %+v; Gen: %+v; Flags: %+v\n", e4, e4Handle, e4Handle.Index(), e4Handle.Generation(), e4Handle.Flags())
*e2 = 1000
fmt.Printf("Entity 2 value after registry get: %+v\n", *testRegistry.Get(e2Handle))
testRegistry.Free(e2Handle)
fmt.Printf("Entity 2 value after free: %+v\n", testRegistry.Get(e2Handle))
e5, e5Handle := testRegistry.New()
fmt.Printf("Entity 5: %+v; Handle: %+v; Index: %+v; Gen: %+v; Flags: %+v\n", e5, e5Handle, e5Handle.Index(), e5Handle.Generation(), e5Handle.Flags())
} }
func main() { func main() {
@ -113,7 +134,8 @@ func main() {
} }
//Create window //Create window
window, err = engine.CreateOpenGLWindowCentered("nMage", 1280, 720, engine.WindowFlags_RESIZABLE, rend3dgl.NewRend3DGL()) dpiScaling = getDpiScaling(unscaledWindowWidth, unscaledWindowHeight)
window, err = engine.CreateOpenGLWindowCentered("nMage", int32(unscaledWindowWidth*dpiScaling), int32(unscaledWindowHeight*dpiScaling), engine.WindowFlags_RESIZABLE, rend3dgl.NewRend3DGL())
if err != nil { if err != nil {
logging.ErrLog.Fatalln("Failed to create window. Err: ", err) logging.ErrLog.Fatalln("Failed to create window. Err: ", err)
} }
@ -123,7 +145,7 @@ func main() {
game := &OurGame{ game := &OurGame{
Win: window, Win: window,
ImGUIInfo: nmageimgui.NewImGUI(), ImGUIInfo: nmageimgui.NewImGui(),
} }
window.EventCallbacks = append(window.EventCallbacks, game.handleWindowEvents) window.EventCallbacks = append(window.EventCallbacks, game.handleWindowEvents)
@ -147,6 +169,40 @@ func (g *OurGame) handleWindowEvents(e sdl.Event) {
} }
} }
func getDpiScaling(unscaledWindowWidth, unscaledWindowHeight int32) float32 {
// Great read on DPI here: https://nlguillemot.wordpress.com/2016/12/11/high-dpi-rendering/
// The no-scaling DPI on different platforms (e.g. when scale=100% on windows)
var defaultDpi float32 = 96
if runtime.GOOS == "windows" {
defaultDpi = 96
} else if runtime.GOOS == "darwin" {
defaultDpi = 72
}
// Current DPI of the monitor
_, dpiHorizontal, _, err := sdl.GetDisplayDPI(0)
if err != nil {
dpiHorizontal = defaultDpi
fmt.Printf("Failed to get DPI with error '%s'. Using default DPI of '%f'\n", err.Error(), defaultDpi)
}
// Scaling factor (e.g. will be 1.25 for 125% scaling on windows)
dpiScaling := dpiHorizontal / defaultDpi
fmt.Printf(
"Default DPI=%f\nHorizontal DPI=%f\nDPI scaling=%f\nUnscaled window size (width, height)=(%d, %d)\nScaled window size (width, height)=(%d, %d)\n\n",
defaultDpi,
dpiHorizontal,
dpiScaling,
unscaledWindowWidth, unscaledWindowHeight,
int32(float32(unscaledWindowWidth)*dpiScaling), int32(float32(unscaledWindowHeight)*dpiScaling),
)
return dpiScaling
}
func (g *OurGame) Init() { func (g *OurGame) Init() {
var err error var err error
@ -226,11 +282,14 @@ func (g *OurGame) Update() {
g.updateCameraLookAround() g.updateCameraLookAround()
g.updateCameraPos() g.updateCameraPos()
imgui.ShowDemoWindow()
//Rotating cubes //Rotating cubes
if input.KeyDown(sdl.K_SPACE) { if input.KeyDown(sdl.K_SPACE) {
cubeModelMat.Rotate(10*timing.DT()*gglm.Deg2Rad, gglm.NewVec3(1, 1, 1).Normalize()) cubeModelMat.Rotate(10*timing.DT()*gglm.Deg2Rad, gglm.NewVec3(1, 1, 1).Normalize())
} }
imgui.Begin("Debug controls")
if imgui.DragFloat3("Cam Pos", &cam.Pos.Data) { if imgui.DragFloat3("Cam Pos", &cam.Pos.Data) {
updateViewMat() updateViewMat()
} }
@ -246,6 +305,9 @@ func (g *OurGame) Update() {
simpleMat.SetUnifVec3("lightColor1", lightColor1) simpleMat.SetUnifVec3("lightColor1", lightColor1)
} }
imgui.Checkbox("Debug depth buffer", &debugDrawDepthBuffer)
imgui.End()
if input.KeyClicked(sdl.K_F4) { if input.KeyClicked(sdl.K_F4) {
fmt.Printf("Pos: %s; Forward: %s; |Forward|: %f\n", cam.Pos.String(), cam.Forward.String(), cam.Forward.Mag()) fmt.Printf("Pos: %s; Forward: %s; |Forward|: %f\n", cam.Pos.String(), cam.Forward.String(), cam.Forward.Mag())
} }
@ -314,7 +376,6 @@ func (g *OurGame) updateCameraPos() {
func (g *OurGame) Render() { func (g *OurGame) Render() {
matToUse := simpleMat matToUse := simpleMat
imgui.Checkbox("Debug depth buffer", &debugDrawDepthBuffer)
if debugDrawDepthBuffer { if debugDrawDepthBuffer {
matToUse = debugDepthMat matToUse = debugDepthMat
} }

73
registry/iterator.go Executable file
View File

@ -0,0 +1,73 @@
package registry
// Iterator goes through the entire registry it was created from and
// returns all alive items, and nil after its done.
//
// The iterator will still work if items are added/removed to the registry
// after it was created, but the following conditions apply:
// - If items are removed, iterator will not show the removed items (assuming it didn't return them before their removal)
// - If items are added, the iterator will either only return older items (i.e. is not affected), or only return newer items (i.e. items that were going to be returned before will now not get returned in favor of newly inserted items), or a mix of old and new items.
// However, in all cases the iterator will *never* returns more items than were alive at the time of the iterator's creation.
// - If items were both added and removed, the iterator might follow either of the previous 2 cases or a combination of them
//
// To summarize: The iterator will *never* return more items than were alive at the time of its creation, and will *never* return freed items
//
// Example usage:
//
// for item, handle := it.Next(); !it.IsDone(); item, handle = it.Next() {
// // Do stuff
// }
type Iterator[T any] struct {
registry *Registry[T]
remainingItems uint
currIndex int
}
func (it *Iterator[T]) Next() (*T, Handle) {
if it.IsDone() {
return nil, 0
}
// If IsDone() only checked 'remainingItems', then when Next() returns the last item IsDone() will immediately be true which will cause loops to exit before processing the last item!
// However, with this check IsDone will remain false until Next() is called at least one more time after returning the last item which ensures the last item is processed in the loop.
//
// In cases where iterator is created on an empty registry, IsDone() will report true and the above check will return early
if it.remainingItems == 0 {
it.currIndex = -1
return nil, 0
}
for ; it.currIndex < len(it.registry.Handles); it.currIndex++ {
handle := it.registry.Handles[it.currIndex]
if !handle.HasFlag(HandleFlag_Alive) {
continue
}
item := &it.registry.Items[it.currIndex]
it.currIndex++
it.remainingItems--
return item, handle
}
// If we reached here means we iterated to the end and didn't find anything, which probably
// means that the registry changed since we were created, and that remainingItems is not accurate.
//
// As such, we zero remaining items so that this iterator is considered done
it.currIndex = -1
it.remainingItems = 0
return nil, 0
}
func (it *Iterator[T]) IsDone() bool {
if it.remainingItems != 0 {
return false
}
// We have two cases here:
// 1. Index of zero means Next() never returned an item. Remaining items of zero without returning anything means we have an empty registry and so its safe to report done
// 2. Negative index means Next() has detected we reached the end and that its safe to report being done
return it.currIndex <= 0
}

131
registry/registry.go Executable file
View File

@ -0,0 +1,131 @@
package registry
import (
"math"
"github.com/bloeys/nmage/assert"
)
type freeListitem struct {
ItemIndex uint64
nextFree *freeListitem
}
// Registry is a storage data structure that can efficiently create/get/free items using generational indices.
// Each item stored in the registry is associated with a 'handle' object that is used to get and free objects
//
// The registry 'owns' all items it stores and returns pointers to items in its array. All items are allocated upfront.
//
// It is NOT safe to concurrently create or free items. However, it is SAFE to concurrently get items
type Registry[T any] struct {
ItemCount uint
Handles []Handle
Items []T
FreeList *freeListitem
FreeListSize uint32
// The number of slots required to be in the free list before the free list
// is used for creating new entries
FreeListUsageThreshold uint32
}
func (r *Registry[T]) New() (*T, Handle) {
assert.T(r.ItemCount < uint(len(r.Handles)), "Can not add more entities to registry because it is full")
var index uint64 = math.MaxUint64
// Find index to use for the new item
if r.FreeList != nil && r.FreeListSize > r.FreeListUsageThreshold {
index = r.FreeList.ItemIndex
r.FreeList = r.FreeList.nextFree
r.FreeListSize--
} else {
for i := 0; i < len(r.Handles); i++ {
handle := r.Handles[i]
if handle.HasFlag(HandleFlag_Alive) {
continue
}
index = uint64(i)
break
}
}
if index == math.MaxUint64 {
panic("failed to create new entity because we did not find a free spot in the registry. Why did the item count assert not go off?")
}
var newItem T
newHandle := NewHandle(r.Handles[index].Generation()+1, HandleFlag_Alive, index)
assert.T(newHandle != 0, "Entity handle must not be zero")
r.ItemCount++
r.Handles[index] = newHandle
r.Items[index] = newItem
// It is very important we return directly from the items array, because if we return
// a pointer to newItem, and T is a value not a pointer, then newItem and what's stored in items will be different
return &r.Items[index], newHandle
}
func (r *Registry[T]) Get(id Handle) *T {
index := id.Index()
assert.T(index < uint64(len(r.Handles)), "Failed to get entity because of invalid entity handle. Handle index is %d while registry only has %d slots. Handle: %+v", index, r.ItemCount, id)
handle := r.Handles[index]
if handle.Generation() != id.Generation() || !handle.HasFlag(HandleFlag_Alive) {
return nil
}
item := &r.Items[index]
return item
}
// Free resets the entity flags then adds this entity to the free list
func (r *Registry[T]) Free(id Handle) {
index := id.Index()
assert.T(index < uint64(len(r.Handles)), "Failed to free entity because of invalid entity handle. Handle index is %d while registry only has %d slots. Handle: %+v", index, r.ItemCount, id)
// Nothing to do if already free
handle := r.Handles[index]
if handle.Generation() != id.Generation() || !handle.HasFlag(HandleFlag_Alive) {
return
}
// Generation is incremented on aquire, so here we just reset flags
r.ItemCount--
r.Handles[index] = NewHandle(id.Generation(), HandleFlag_None, index)
// Add to free list
r.FreeList = &freeListitem{
ItemIndex: index,
nextFree: r.FreeList,
}
r.FreeListSize++
}
func (r *Registry[T]) NewIterator() Iterator[T] {
return Iterator[T]{
registry: r,
remainingItems: r.ItemCount,
currIndex: 0,
}
}
func NewRegistry[T any](size uint32) *Registry[T] {
assert.T(size > 0, "Registry size must be more than zero")
return &Registry[T]{
Handles: make([]Handle, size),
Items: make([]T, size),
FreeListUsageThreshold: 30,
}
}

37
registry/registry_handle.go Executable file
View File

@ -0,0 +1,37 @@
package registry
type HandleFlag byte
const (
HandleFlag_None HandleFlag = 0
HandleFlag_Alive HandleFlag = 1 << (iota - 1)
)
const (
GenerationShiftBits = 64 - 8
FlagsShiftBits = 64 - 16
IndexBitMask = 0x00_00_FFFF_FFFF_FFFF
)
// Byte 1: Generation; Byte 2: Flags; Bytes 3-8: Index
type Handle uint64
func (h Handle) HasFlag(ef HandleFlag) bool {
return h.Flags()&ef > 0
}
func (h Handle) Generation() byte {
return byte(h >> GenerationShiftBits)
}
func (h Handle) Flags() HandleFlag {
return HandleFlag(h >> FlagsShiftBits)
}
func (h Handle) Index() uint64 {
return uint64(h & IndexBitMask)
}
func NewHandle(generation byte, flags HandleFlag, index uint64) Handle {
return Handle(index | (uint64(generation) << GenerationShiftBits) | (uint64(flags) << FlagsShiftBits))
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1008 KiB

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 617 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 338 KiB

After

Width:  |  Height:  |  Size: 770 KiB

BIN
rsrc_windows_386.syso Executable file

Binary file not shown.

BIN
rsrc_windows_amd64.syso Executable file

Binary file not shown.

View File

@ -1,17 +1,16 @@
package nmageimgui package nmageimgui
import ( import (
imgui "github.com/AllenDang/cimgui-go"
"github.com/bloeys/gglm/gglm" "github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/materials" "github.com/bloeys/nmage/materials"
"github.com/bloeys/nmage/timing" "github.com/bloeys/nmage/timing"
"github.com/go-gl/gl/v4.1-core/gl" "github.com/go-gl/gl/v4.1-core/gl"
"github.com/inkyblackness/imgui-go/v4"
"github.com/veandco/go-sdl2/sdl" "github.com/veandco/go-sdl2/sdl"
) )
type ImguiInfo struct { type ImguiInfo struct {
ImCtx *imgui.Context ImCtx imgui.Context
Mat *materials.Material Mat *materials.Material
VaoID uint32 VaoID uint32
@ -22,9 +21,9 @@ type ImguiInfo struct {
func (i *ImguiInfo) FrameStart(winWidth, winHeight float32) { func (i *ImguiInfo) FrameStart(winWidth, winHeight float32) {
if err := i.ImCtx.SetCurrent(); err != nil { // if err := i.ImCtx.SetCurrent(); err != nil {
assert.T(false, "Setting imgui ctx as current failed. Err: "+err.Error()) // assert.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
} // }
imIO := imgui.CurrentIO() imIO := imgui.CurrentIO()
imIO.SetDisplaySize(imgui.Vec2{X: float32(winWidth), Y: float32(winHeight)}) imIO.SetDisplaySize(imgui.Vec2{X: float32(winWidth), Y: float32(winHeight)})
@ -35,9 +34,9 @@ func (i *ImguiInfo) FrameStart(winWidth, winHeight float32) {
func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32) { func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32) {
if err := i.ImCtx.SetCurrent(); err != nil { // if err := i.ImCtx.SetCurrent(); err != nil {
assert.T(false, "Setting imgui ctx as current failed. Err: "+err.Error()) // assert.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
} // }
imgui.Render() imgui.Render()
@ -46,7 +45,7 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
return return
} }
drawData := imgui.RenderedDrawData() drawData := imgui.CurrentDrawData()
drawData.ScaleClipRects(imgui.Vec2{ drawData.ScaleClipRects(imgui.Vec2{
X: float32(fbWidth) / float32(winWidth), X: float32(fbWidth) / float32(winWidth),
Y: float32(fbHeight) / float32(winHeight), Y: float32(fbHeight) / float32(winHeight),
@ -96,11 +95,11 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
// Draw // Draw
for _, list := range drawData.CommandLists() { for _, list := range drawData.CommandLists() {
vertexBuffer, vertexBufferSize := list.VertexBuffer() vertexBuffer, vertexBufferSize := list.GetVertexBuffer()
gl.BindBuffer(gl.ARRAY_BUFFER, i.VboID) gl.BindBuffer(gl.ARRAY_BUFFER, i.VboID)
gl.BufferData(gl.ARRAY_BUFFER, vertexBufferSize, vertexBuffer, gl.STREAM_DRAW) gl.BufferData(gl.ARRAY_BUFFER, vertexBufferSize, vertexBuffer, gl.STREAM_DRAW)
indexBuffer, indexBufferSize := list.IndexBuffer() indexBuffer, indexBufferSize := list.GetIndexBuffer()
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, i.IndexBufID) gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, i.IndexBufID)
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, indexBufferSize, indexBuffer, gl.STREAM_DRAW) gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, indexBufferSize, indexBuffer, gl.STREAM_DRAW)
@ -113,7 +112,7 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
clipRect := cmd.ClipRect() clipRect := cmd.ClipRect()
gl.Scissor(int32(clipRect.X), int32(fbHeight)-int32(clipRect.W), int32(clipRect.Z-clipRect.X), int32(clipRect.W-clipRect.Y)) gl.Scissor(int32(clipRect.X), int32(fbHeight)-int32(clipRect.W), int32(clipRect.Z-clipRect.X), int32(clipRect.W-clipRect.Y))
gl.DrawElementsBaseVertex(gl.TRIANGLES, int32(cmd.ElementCount()), uint32(drawType), gl.PtrOffset(cmd.IndexOffset()*indexSize), int32(cmd.VertexOffset())) gl.DrawElementsBaseVertex(gl.TRIANGLES, int32(cmd.ElemCount()), uint32(drawType), gl.PtrOffset(int(cmd.IdxOffset())*indexSize), int32(cmd.VtxOffset()))
} }
} }
} }
@ -124,14 +123,14 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
gl.Enable(gl.DEPTH_TEST) gl.Enable(gl.DEPTH_TEST)
} }
func (i *ImguiInfo) AddFontTTF(fontPath string, fontSize float32, fontConfig *imgui.FontConfig, glyphRanges *imgui.GlyphRanges) imgui.Font { func (i *ImguiInfo) AddFontTTF(fontPath string, fontSize float32, fontConfig *imgui.FontConfig, glyphRanges *imgui.GlyphRange) imgui.Font {
fontConfigToUse := imgui.DefaultFontConfig fontConfigToUse := imgui.NewFontConfig()
if fontConfig != nil { if fontConfig != nil {
fontConfigToUse = *fontConfig fontConfigToUse = *fontConfig
} }
glyphRangesToUse := imgui.EmptyGlyphRanges glyphRangesToUse := imgui.NewGlyphRange()
if glyphRanges != nil { if glyphRanges != nil {
glyphRangesToUse = *glyphRanges glyphRangesToUse = *glyphRanges
} }
@ -139,11 +138,11 @@ func (i *ImguiInfo) AddFontTTF(fontPath string, fontSize float32, fontConfig *im
imIO := imgui.CurrentIO() imIO := imgui.CurrentIO()
a := imIO.Fonts() a := imIO.Fonts()
f := a.AddFontFromFileTTFV(fontPath, fontSize, fontConfigToUse, glyphRangesToUse) f := a.AddFontFromFileTTFV(fontPath, fontSize, fontConfigToUse, glyphRangesToUse.Data())
image := a.TextureDataAlpha8() pixels, width, height, _ := a.GetTextureDataAsAlpha8()
gl.BindTexture(gl.TEXTURE_2D, i.TexID) gl.BindTexture(gl.TEXTURE_2D, i.TexID)
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(image.Width), int32(image.Height), 0, gl.RED, gl.UNSIGNED_BYTE, image.Pixels) gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(width), int32(height), 0, gl.RED, gl.UNSIGNED_BYTE, pixels)
return f return f
} }
@ -184,15 +183,16 @@ void main()
} }
` `
func NewImGUI() ImguiInfo { func NewImGui() ImguiInfo {
imguiInfo := ImguiInfo{ imguiInfo := ImguiInfo{
ImCtx: imgui.CreateContext(nil), ImCtx: imgui.CreateContext(),
Mat: materials.NewMaterialSrc("ImGUI Mat", []byte(imguiShdrSrc)), Mat: materials.NewMaterialSrc("ImGUI Mat", []byte(imguiShdrSrc)),
} }
imIO := imgui.CurrentIO() io := imgui.CurrentIO()
imIO.SetBackendFlags(imIO.GetBackendFlags() | imgui.BackendFlagsRendererHasVtxOffset) io.SetConfigFlags(io.ConfigFlags() | imgui.ConfigFlagsDockingEnable)
io.SetBackendFlags(io.BackendFlags() | imgui.BackendFlagsRendererHasVtxOffset)
gl.GenVertexArrays(1, &imguiInfo.VaoID) gl.GenVertexArrays(1, &imguiInfo.VaoID)
gl.GenBuffers(1, &imguiInfo.VboID) gl.GenBuffers(1, &imguiInfo.VboID)
@ -205,11 +205,11 @@ func NewImGUI() ImguiInfo {
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0) gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
image := imIO.Fonts().TextureDataAlpha8() pixels, width, height, _ := io.Fonts().GetTextureDataAsAlpha8()
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(image.Width), int32(image.Height), 0, gl.RED, gl.UNSIGNED_BYTE, image.Pixels) gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(width), int32(height), 0, gl.RED, gl.UNSIGNED_BYTE, pixels)
// Store our identifier // Store our identifier
imIO.Fonts().SetTextureID(imgui.TextureID(imguiInfo.TexID)) io.Fonts().SetTexID(imgui.TextureID(uintptr(imguiInfo.TexID)))
//Shader attributes //Shader attributes
imguiInfo.Mat.Bind() imguiInfo.Mat.Bind()
@ -218,35 +218,223 @@ func NewImGUI() ImguiInfo {
imguiInfo.Mat.EnableAttribute("Color") imguiInfo.Mat.EnableAttribute("Color")
imguiInfo.Mat.UnBind() imguiInfo.Mat.UnBind()
//Init imgui input mapping
keys := map[int]int{
imgui.KeyTab: sdl.SCANCODE_TAB,
imgui.KeyLeftArrow: sdl.SCANCODE_LEFT,
imgui.KeyRightArrow: sdl.SCANCODE_RIGHT,
imgui.KeyUpArrow: sdl.SCANCODE_UP,
imgui.KeyDownArrow: sdl.SCANCODE_DOWN,
imgui.KeyPageUp: sdl.SCANCODE_PAGEUP,
imgui.KeyPageDown: sdl.SCANCODE_PAGEDOWN,
imgui.KeyHome: sdl.SCANCODE_HOME,
imgui.KeyEnd: sdl.SCANCODE_END,
imgui.KeyInsert: sdl.SCANCODE_INSERT,
imgui.KeyDelete: sdl.SCANCODE_DELETE,
imgui.KeyBackspace: sdl.SCANCODE_BACKSPACE,
imgui.KeySpace: sdl.SCANCODE_BACKSPACE,
imgui.KeyEnter: sdl.SCANCODE_RETURN,
imgui.KeyEscape: sdl.SCANCODE_ESCAPE,
imgui.KeyA: sdl.SCANCODE_A,
imgui.KeyC: sdl.SCANCODE_C,
imgui.KeyV: sdl.SCANCODE_V,
imgui.KeyX: sdl.SCANCODE_X,
imgui.KeyY: sdl.SCANCODE_Y,
imgui.KeyZ: sdl.SCANCODE_Z,
}
// Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array.
for imguiKey, nativeKey := range keys {
imIO.KeyMap(imguiKey, nativeKey)
}
return imguiInfo return imguiInfo
} }
func SdlScancodeToImGuiKey(scancode sdl.Scancode) imgui.Key {
switch scancode {
case sdl.SCANCODE_TAB:
return imgui.KeyTab
case sdl.SCANCODE_LEFT:
return imgui.KeyLeftArrow
case sdl.SCANCODE_RIGHT:
return imgui.KeyRightArrow
case sdl.SCANCODE_UP:
return imgui.KeyUpArrow
case sdl.SCANCODE_DOWN:
return imgui.KeyDownArrow
case sdl.SCANCODE_PAGEUP:
return imgui.KeyPageUp
case sdl.SCANCODE_PAGEDOWN:
return imgui.KeyPageDown
case sdl.SCANCODE_HOME:
return imgui.KeyHome
case sdl.SCANCODE_END:
return imgui.KeyEnd
case sdl.SCANCODE_INSERT:
return imgui.KeyInsert
case sdl.SCANCODE_DELETE:
return imgui.KeyDelete
case sdl.SCANCODE_BACKSPACE:
return imgui.KeyBackspace
case sdl.SCANCODE_SPACE:
return imgui.KeySpace
case sdl.SCANCODE_RETURN:
return imgui.KeyEnter
case sdl.SCANCODE_ESCAPE:
return imgui.KeyEscape
case sdl.SCANCODE_APOSTROPHE:
return imgui.KeyApostrophe
case sdl.SCANCODE_COMMA:
return imgui.KeyComma
case sdl.SCANCODE_MINUS:
return imgui.KeyMinus
case sdl.SCANCODE_PERIOD:
return imgui.KeyPeriod
case sdl.SCANCODE_SLASH:
return imgui.KeySlash
case sdl.SCANCODE_SEMICOLON:
return imgui.KeySemicolon
case sdl.SCANCODE_EQUALS:
return imgui.KeyEqual
case sdl.SCANCODE_LEFTBRACKET:
return imgui.KeyLeftBracket
case sdl.SCANCODE_BACKSLASH:
return imgui.KeyBackslash
case sdl.SCANCODE_RIGHTBRACKET:
return imgui.KeyRightBracket
case sdl.SCANCODE_GRAVE:
return imgui.KeyGraveAccent
case sdl.SCANCODE_CAPSLOCK:
return imgui.KeyCapsLock
case sdl.SCANCODE_SCROLLLOCK:
return imgui.KeyScrollLock
case sdl.SCANCODE_NUMLOCKCLEAR:
return imgui.KeyNumLock
case sdl.SCANCODE_PRINTSCREEN:
return imgui.KeyPrintScreen
case sdl.SCANCODE_PAUSE:
return imgui.KeyPause
case sdl.SCANCODE_KP_0:
return imgui.KeyKeypad0
case sdl.SCANCODE_KP_1:
return imgui.KeyKeypad1
case sdl.SCANCODE_KP_2:
return imgui.KeyKeypad2
case sdl.SCANCODE_KP_3:
return imgui.KeyKeypad3
case sdl.SCANCODE_KP_4:
return imgui.KeyKeypad4
case sdl.SCANCODE_KP_5:
return imgui.KeyKeypad5
case sdl.SCANCODE_KP_6:
return imgui.KeyKeypad6
case sdl.SCANCODE_KP_7:
return imgui.KeyKeypad7
case sdl.SCANCODE_KP_8:
return imgui.KeyKeypad8
case sdl.SCANCODE_KP_9:
return imgui.KeyKeypad9
case sdl.SCANCODE_KP_PERIOD:
return imgui.KeyKeypadDecimal
case sdl.SCANCODE_KP_DIVIDE:
return imgui.KeyKeypadDivide
case sdl.SCANCODE_KP_MULTIPLY:
return imgui.KeyKeypadMultiply
case sdl.SCANCODE_KP_MINUS:
return imgui.KeyKeypadSubtract
case sdl.SCANCODE_KP_PLUS:
return imgui.KeyKeypadAdd
case sdl.SCANCODE_KP_ENTER:
return imgui.KeyKeypadEnter
case sdl.SCANCODE_KP_EQUALS:
return imgui.KeyKeypadEqual
case sdl.SCANCODE_LSHIFT:
return imgui.KeyLeftShift
case sdl.SCANCODE_LCTRL:
return imgui.KeyLeftCtrl
case sdl.SCANCODE_LALT:
return imgui.KeyLeftAlt
case sdl.SCANCODE_LGUI:
return imgui.KeyLeftSuper
case sdl.SCANCODE_RSHIFT:
return imgui.KeyRightShift
case sdl.SCANCODE_RCTRL:
return imgui.KeyRightCtrl
case sdl.SCANCODE_RALT:
return imgui.KeyRightAlt
case sdl.SCANCODE_RGUI:
return imgui.KeyRightSuper
case sdl.SCANCODE_MENU:
return imgui.KeyMenu
case sdl.SCANCODE_0:
return imgui.Key0
case sdl.SCANCODE_1:
return imgui.Key1
case sdl.SCANCODE_2:
return imgui.Key2
case sdl.SCANCODE_3:
return imgui.Key3
case sdl.SCANCODE_4:
return imgui.Key4
case sdl.SCANCODE_5:
return imgui.Key5
case sdl.SCANCODE_6:
return imgui.Key6
case sdl.SCANCODE_7:
return imgui.Key7
case sdl.SCANCODE_8:
return imgui.Key8
case sdl.SCANCODE_9:
return imgui.Key9
case sdl.SCANCODE_A:
return imgui.KeyA
case sdl.SCANCODE_B:
return imgui.KeyB
case sdl.SCANCODE_C:
return imgui.KeyC
case sdl.SCANCODE_D:
return imgui.KeyD
case sdl.SCANCODE_E:
return imgui.KeyE
case sdl.SCANCODE_F:
return imgui.KeyF
case sdl.SCANCODE_G:
return imgui.KeyG
case sdl.SCANCODE_H:
return imgui.KeyH
case sdl.SCANCODE_I:
return imgui.KeyI
case sdl.SCANCODE_J:
return imgui.KeyJ
case sdl.SCANCODE_K:
return imgui.KeyK
case sdl.SCANCODE_L:
return imgui.KeyL
case sdl.SCANCODE_M:
return imgui.KeyM
case sdl.SCANCODE_N:
return imgui.KeyN
case sdl.SCANCODE_O:
return imgui.KeyO
case sdl.SCANCODE_P:
return imgui.KeyP
case sdl.SCANCODE_Q:
return imgui.KeyQ
case sdl.SCANCODE_R:
return imgui.KeyR
case sdl.SCANCODE_S:
return imgui.KeyS
case sdl.SCANCODE_T:
return imgui.KeyT
case sdl.SCANCODE_U:
return imgui.KeyU
case sdl.SCANCODE_V:
return imgui.KeyV
case sdl.SCANCODE_W:
return imgui.KeyW
case sdl.SCANCODE_X:
return imgui.KeyX
case sdl.SCANCODE_Y:
return imgui.KeyY
case sdl.SCANCODE_Z:
return imgui.KeyZ
case sdl.SCANCODE_F1:
return imgui.KeyF1
case sdl.SCANCODE_F2:
return imgui.KeyF2
case sdl.SCANCODE_F3:
return imgui.KeyF3
case sdl.SCANCODE_F4:
return imgui.KeyF4
case sdl.SCANCODE_F5:
return imgui.KeyF5
case sdl.SCANCODE_F6:
return imgui.KeyF6
case sdl.SCANCODE_F7:
return imgui.KeyF7
case sdl.SCANCODE_F8:
return imgui.KeyF8
case sdl.SCANCODE_F9:
return imgui.KeyF9
case sdl.SCANCODE_F10:
return imgui.KeyF10
case sdl.SCANCODE_F11:
return imgui.KeyF11
case sdl.SCANCODE_F12:
return imgui.KeyF12
default:
return imgui.KeyNone
}
}