Compare commits

...

35 Commits

Author SHA1 Message Date
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
b44b00d7e2 Re-add IndexBufCount 2023-02-04 02:00:13 +04:00
70dccd757e Run init within an imgui frame 2023-02-03 02:20:50 +04:00
d7cd5bfc8d Run one imgui frame before init and another after init 2023-02-03 01:37:01 +04:00
3b8e5c06de Only initialize video and timer subsystems of sdl 2023-02-03 01:14:57 +04:00
8e9dbee002 Fix build tag 2022-12-06 21:10:06 +04:00
7b2fb19618 Update workflow dependencies 2022-12-06 06:34:06 +04:00
9651f77348 Update github workflow to use go 1.18 2022-12-06 06:23:02 +04:00
a16654107b Implement UpdateAllComps 2022-12-06 06:19:38 +04:00
855cbfaba3 Improve assert+imporve error messages when adding comps 2022-12-06 06:17:25 +04:00
b025afe1b4 Add Init,Update,Destroy to Comp+HasComp,DestroyComp funcs 2022-12-06 06:07:49 +04:00
84cd8c28c8 Add BaseComp 2022-12-06 05:20:20 +04:00
7d5e3e2d82 Move comp into own file 2022-12-06 04:56:17 +04:00
23a6689346 Improve entity flags+add freeListSize to registry 2022-12-06 04:51:03 +04:00
36488ead04 Add skybox textures 2022-12-06 04:29:57 +04:00
de77d5464e Remove todo + upgrade gglm 2022-12-06 04:28:34 +04:00
305982deca Light pos and color controls 2022-12-06 03:48:07 +04:00
653a315631 Remove some old code 2022-10-20 01:28:06 +04:00
c971324b5a Move camera when right mouse button is clicked 2022-10-14 07:59:46 +04:00
b5a2479c16 Skybox demo 2022-10-14 07:55:48 +04:00
6f54aecb5f Loading cubemap textures+don't store texture by default 2022-10-14 05:52:28 +04:00
7a25aea6ba Clear stencil buffer every frame+Depth buffer viz 2022-10-14 04:34:40 +04:00
3071b52c85 Basic submeshe support 2022-10-07 05:50:48 +04:00
37 changed files with 1242 additions and 466 deletions

View File

@ -1,22 +1,28 @@
name: build-nmage
on:
create:
workflow_dispatch:
jobs:
build-nmage-macos:
runs-on: macos-10.15
runs-on: macos-12
steps:
- name: Install golang 1.17
uses: actions/setup-go@v2
- name: Install golang 1.18
uses: actions/setup-go@v3
with:
go-version: '^1.17'
go-version: '^1.18'
- name: Install assimp-go dylib
run: sudo mkdir -p /usr/local/lib && sudo wget https://github.com/bloeys/assimp-go/releases/download/v0.4.2/libassimp_darwin_amd64.dylib -O /usr/local/lib/libassimp.5.dylib
- name: Install SDL2
run: brew install sdl2{,_image,_mixer,_ttf,_gfx} pkg-config
- name: Clone nmage
run: git clone https://github.com/bloeys/nmage
- name: build nmage
working-directory: nmage
run: go build .

View File

@ -1,6 +1,6 @@
# nMage
[![build](https://github.com/bloeys/nmage/actions/workflows/run-nmage.yml/badge.svg)](https://github.com/bloeys/nmage/actions/workflows/run-nmage.yml)
[![build](https://github.com/bloeys/nmage/actions/workflows/build-nmage.yml/badge.svg)](https://github.com/bloeys/nmage/actions/workflows/build-nmage.yml)
nMage is a (hopefully!) high performance 3D Game Engine written in Go being developed [live](https://twitch.tv/bloeys), with recordings posted on [YouTube](https://www.youtube.com/channel/UCCf4qyNGPVwpj1HYFGahs_A).

View File

@ -5,8 +5,9 @@ import (
"github.com/bloeys/nmage/logging"
)
func T(check bool, msg string) {
func T(check bool, msg string, args ...any) {
if consts.Debug && !check {
logging.ErrLog.Panicln("Assert failed:", msg)
logging.ErrLog.Panicf("Assert failed: "+msg, args...)
}
}

View File

@ -1,8 +1,8 @@
package assets
var (
Textures map[uint32]Texture = make(map[uint32]Texture)
TexturePaths map[string]uint32 = make(map[string]uint32)
Textures = make(map[uint32]Texture)
TexturePaths = make(map[string]uint32)
)
func AddTextureToCache(t Texture) {

View File

@ -2,10 +2,15 @@ package assets
import (
"bytes"
"fmt"
"image"
"image/color"
"image/jpeg"
"image/png"
"io"
"os"
"path"
"strings"
"unsafe"
"github.com/go-gl/gl/v4.1-core/gl"
@ -30,6 +35,18 @@ type TextureLoadOptions struct {
TryLoadFromCache bool
WriteToCache bool
GenMipMaps bool
KeepPixelsInMem bool
}
type Cubemap struct {
// These only exists for textures loaded from disk
RightPath string
LeftPath string
TopPath string
BotPath string
FrontPath string
BackPath string
TexID uint32
}
func LoadTexturePNG(file string, loadOptions *TextureLoadOptions) (Texture, error) {
@ -56,28 +73,10 @@ func LoadTexturePNG(file string, loadOptions *TextureLoadOptions) (Texture, erro
}
tex := Texture{
Path: file,
Width: int32(img.Bounds().Dx()),
Height: int32(img.Bounds().Dy()),
Pixels: make([]byte, img.Bounds().Dx()*img.Bounds().Dy()*4),
Path: file,
}
//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
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)
tex.Pixels[i] = c.R
tex.Pixels[i+1] = c.G
tex.Pixels[i+2] = c.B
tex.Pixels[i+3] = c.A
i += 4
}
}
tex.Pixels, tex.Width, tex.Height = pixelsFromNrgbaPng(img)
//Prepare opengl stuff
gl.GenTextures(1, &tex.TexID)
@ -100,37 +99,21 @@ func LoadTexturePNG(file string, loadOptions *TextureLoadOptions) (Texture, erro
AddTextureToCache(tex)
}
if !loadOptions.KeepPixelsInMem {
tex.Pixels = nil
}
return tex, nil
}
func LoadTextureInMemImg(img image.Image, loadOptions *TextureLoadOptions) (Texture, error) {
func LoadTextureInMemPngImg(img image.Image, loadOptions *TextureLoadOptions) (Texture, error) {
if loadOptions == nil {
loadOptions = &TextureLoadOptions{}
}
tex := Texture{
Width: int32(img.Bounds().Dx()),
Height: int32(img.Bounds().Dy()),
Pixels: make([]byte, img.Bounds().Dx()*img.Bounds().Dy()*4),
}
//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
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)
tex.Pixels[i] = c.R
tex.Pixels[i+1] = c.G
tex.Pixels[i+2] = c.B
tex.Pixels[i+3] = c.A
i += 4
}
}
tex := Texture{}
tex.Pixels, tex.Width, tex.Height = pixelsFromNrgbaPng(img)
//Prepare opengl stuff
gl.GenTextures(1, &tex.TexID)
@ -153,5 +136,173 @@ func LoadTextureInMemImg(img image.Image, loadOptions *TextureLoadOptions) (Text
AddTextureToCache(tex)
}
if !loadOptions.KeepPixelsInMem {
tex.Pixels = nil
}
return tex, nil
}
func LoadTextureJpeg(file string, loadOptions *TextureLoadOptions) (Texture, error) {
if loadOptions == nil {
loadOptions = &TextureLoadOptions{}
}
if loadOptions.TryLoadFromCache {
if tex, ok := GetTextureFromCachePath(file); ok {
return tex, nil
}
}
//Load from disk
fileBytes, err := os.ReadFile(file)
if err != nil {
return Texture{}, err
}
img, err := jpeg.Decode(bytes.NewReader(fileBytes))
if err != nil {
return Texture{}, err
}
tex := Texture{
Path: file,
}
tex.Pixels, tex.Width, tex.Height = pixelsFromNrgbaPng(img)
//Prepare opengl stuff
gl.GenTextures(1, &tex.TexID)
gl.BindTexture(gl.TEXTURE_2D, tex.TexID)
// set the texture wrapping/filtering options (on the currently bound texture object)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
// 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]))
if loadOptions.GenMipMaps {
gl.GenerateMipmap(tex.TexID)
}
if loadOptions.WriteToCache {
AddTextureToCache(tex)
}
if !loadOptions.KeepPixelsInMem {
tex.Pixels = 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) {
var imgDecoder func(r io.Reader) (image.Image, error)
var pixelDecoder func(image.Image) ([]byte, int32, int32)
ext := strings.ToLower(path.Ext(rightTex))
if ext == ".jpg" || ext == ".jpeg" {
imgDecoder = jpeg.Decode
pixelDecoder = pixelsFromNrgbaJpg
} else if ext == ".png" {
imgDecoder = png.Decode
pixelDecoder = pixelsFromNrgbaPng
} else {
return Cubemap{}, fmt.Errorf("unknown image extension: %s. Expected one of: .jpg, .jpeg, .png", ext)
}
cmap := Cubemap{
RightPath: rightTex,
LeftPath: leftTex,
TopPath: topTex,
BotPath: botTex,
FrontPath: frontTex,
BackPath: backTex,
}
gl.GenTextures(1, &cmap.TexID)
gl.BindTexture(gl.TEXTURE_CUBE_MAP, cmap.TexID)
// The order here matters
texturePaths := []string{rightTex, leftTex, topTex, botTex, frontTex, backTex}
for i := uint32(0); i < uint32(len(texturePaths)); i++ {
fPath := texturePaths[i]
//Load from disk
fileBytes, err := os.ReadFile(fPath)
if err != nil {
return Cubemap{}, err
}
img, err := imgDecoder(bytes.NewReader(fileBytes))
if err != nil {
return Cubemap{}, err
}
pixels, width, height := pixelDecoder(img)
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]))
}
// set the texture wrapping/filtering options (on the currently bound texture object)
gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
gl.TexParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE)
return cmap, nil
}

View File

@ -7,12 +7,15 @@ import (
type Buffer struct {
VAOID uint32
//BufID is the ID of the VBO
// BufID is the ID of the VBO
BufID uint32
//IndexBufID is the ID of the index/element buffer
IndexBufID uint32
// IndexBufID is the ID of the index/element buffer
IndexBufID uint32
// IndexBufCount is the number of elements in the index buffer
// Updated on SetIndexBufData
IndexBufCount int32
Stride int32
// IndexBufCount int32
Stride int32
layout []Element
}
@ -80,8 +83,8 @@ func (b *Buffer) GetLayout() []Element {
return e
}
//SetLayout updates the layout object and the corresponding vertex attributes.
//Vertex attributes are also enabled.
// SetLayout updates the layout object and the corresponding vertex attributes.
// Vertex attributes are also enabled.
func (b *Buffer) SetLayout(layout ...Element) {
b.layout = layout
@ -101,7 +104,7 @@ func (b *Buffer) SetLayout(layout ...Element) {
for i := 0; i < len(layout); i++ {
gl.EnableVertexAttribArray(uint32(i))
gl.VertexAttribPointer(uint32(i), layout[i].ElementType.CompCount(), layout[i].ElementType.GLType(), false, b.Stride, gl.PtrOffset(layout[i].Offset))
gl.VertexAttribPointerWithOffset(uint32(i), layout[i].ElementType.CompCount(), layout[i].ElementType.GLType(), false, b.Stride, uintptr(layout[i].Offset))
}
b.UnBind()

View File

@ -3,12 +3,13 @@ package engine
import (
"runtime"
imgui "github.com/AllenDang/cimgui-go"
"github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/input"
"github.com/bloeys/nmage/renderer"
"github.com/bloeys/nmage/timing"
nmageimgui "github.com/bloeys/nmage/ui/imgui"
"github.com/go-gl/gl/v4.1-core/gl"
"github.com/inkyblackness/imgui-go/v4"
"github.com/veandco/go-sdl2/sdl"
)
@ -26,8 +27,9 @@ type Window struct {
func (w *Window) handleInputs() {
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() {
//Fire callbacks
@ -43,19 +45,32 @@ func (w *Window) handleInputs() {
input.HandleMouseWheelEvent(e)
xDelta, yDelta := input.GetMouseWheelMotion()
imIO.AddMouseWheelDelta(float32(xDelta), float32(yDelta))
imIo.AddMouseWheelDelta(float32(xDelta), float32(yDelta))
case *sdl.KeyboardEvent:
input.HandleKeyboardEvent(e)
if e.Type == sdl.KEYDOWN {
imIO.KeyPress(int(e.Keysym.Scancode))
} else if e.Type == sdl.KEYUP {
imIO.KeyRelease(int(e.Keysym.Scancode))
input.HandleKeyboardEvent(e)
imIo.AddKeyEvent(nmageimgui.SdlScancodeToImGuiKey(e.Keysym.Scancode), e.Type == sdl.KEYDOWN)
// 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:
imIO.AddInputCharacters(string(e.Text[:]))
imIo.AddInputCharactersUTF8(e.GetText())
case *sdl.MouseButtonEvent:
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.
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(1, input.MouseDown(sdl.BUTTON_RIGHT))
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)
imIo.SetMouseButtonDown(0, input.MouseDown(sdl.BUTTON_LEFT))
imIo.SetMouseButtonDown(1, input.MouseDown(sdl.BUTTON_RIGHT))
imIo.SetMouseButtonDown(2, input.MouseDown(sdl.BUTTON_MIDDLE))
}
func (w *Window) handleWindowResize() {
@ -112,7 +123,7 @@ func Init() error {
func initSDL() error {
err := sdl.Init(sdl.INIT_EVERYTHING)
err := sdl.Init(sdl.INIT_TIMER | sdl.INIT_VIDEO)
if err != nil {
return err
}

View File

@ -23,19 +23,26 @@ type Game interface {
func Run(g Game, w *Window, ui nmageimgui.ImguiInfo) {
isRunning = true
// Run init with an active Imgui frame to allow init full imgui access
timing.FrameStarted()
w.handleInputs()
width, height := w.SDLWin.GetSize()
ui.FrameStart(float32(width), float32(height))
g.Init()
//Simulate an imgui frame during init so any imgui calls are allowed within init
tempWidth, tempHeight := w.SDLWin.GetSize()
tempFBWidth, tempFBHeight := w.SDLWin.GLGetDrawableSize()
ui.FrameStart(float32(tempWidth), float32(tempHeight))
ui.Render(float32(tempWidth), float32(tempHeight), tempFBWidth, tempFBHeight)
fbWidth, fbHeight := w.SDLWin.GLGetDrawableSize()
ui.Render(float32(width), float32(height), fbWidth, fbHeight)
timing.FrameEnded()
for isRunning {
//PERF: Cache these
width, height := w.SDLWin.GetSize()
fbWidth, fbHeight := w.SDLWin.GLGetDrawableSize()
width, height = w.SDLWin.GetSize()
fbWidth, fbHeight = w.SDLWin.GLGetDrawableSize()
timing.FrameStarted()
w.handleInputs()
@ -43,7 +50,7 @@ func Run(g Game, w *Window, ui nmageimgui.ImguiInfo) {
g.Update()
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
g.Render()
ui.Render(float32(width), float32(height), fbWidth, fbHeight)
w.SDLWin.GLSwap()

26
entity/base_comp.go Executable file
View File

@ -0,0 +1,26 @@
package entity
import "github.com/bloeys/nmage/registry"
var _ Comp = &BaseComp{}
type BaseComp struct {
Handle registry.Handle
}
func (b BaseComp) baseComp() {
}
func (b *BaseComp) Init(parentHandle registry.Handle) {
b.Handle = parentHandle
}
func (b BaseComp) Name() string {
return "Base Component"
}
func (b BaseComp) Update() {
}
func (b BaseComp) Destroy() {
}

73
entity/comp.go Executable file
View File

@ -0,0 +1,73 @@
package entity
import (
"github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/registry"
)
type Comp interface {
// This ensures that implementors of the Comp interface
// always embed BaseComp
baseComp()
Name() string
Init(parentHandle registry.Handle)
Update()
Destroy()
}
func NewCompContainer() CompContainer {
return CompContainer{Comps: []Comp{}}
}
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++ {
_, ok := e.Comps[i].(T)
if ok {
return true
}
}
return false
}
func GetComp[T Comp](e *CompContainer) (out T) {
for i := 0; i < len(e.Comps); i++ {
comp, ok := e.Comps[i].(T)
if ok {
return comp
}
}
return out
}
// DestroyComp calls Destroy on the component and then removes it from the entities component list
func DestroyComp[T Comp](e *CompContainer) {
for i := 0; i < len(e.Comps); i++ {
comp, ok := e.Comps[i].(T)
if ok {
comp.Destroy()
e.Comps = append(e.Comps[:i], e.Comps[i+1:]...)
return
}
}
}

View File

@ -1,77 +1,7 @@
package entity
type EntityFlag byte
import "github.com/bloeys/nmage/registry"
const (
EntityFlag_Unknown EntityFlag = 0
EntityFlag_Dead EntityFlag = 1 << (iota - 1)
EntityFlag_Alive
)
const (
GenerationShiftBits = 64 - 8
FlagsShiftBits = 64 - 16
IndexBitMask = 0x00_00_FFFF_FFFF_FFFF
)
type Entity struct {
// Byte 1: Generation; Byte 2: Flags; Bytes 3-8: Index
ID uint64
Comps []Comp
}
func GetGeneration(id uint64) byte {
return byte(id >> GenerationShiftBits)
}
func GetFlags(id uint64) byte {
return byte(id >> FlagsShiftBits)
}
func GetIndex(id uint64) uint64 {
return id & IndexBitMask
}
func (e *Entity) HasFlag(ef EntityFlag) bool {
return GetFlags(e.ID)&byte(ef) > 0
}
func NewEntityId(generation, flags byte, index uint64) uint64 {
return index | (uint64(generation) << GenerationShiftBits) | (uint64(flags) << FlagsShiftBits)
}
type Comp interface {
Name() string
}
func AddComp(e *Entity, c Comp) {
e.Comps = append(e.Comps, c)
}
func GetComp[T Comp](e *Entity) (out T) {
for i := 0; i < len(e.Comps); i++ {
comp, ok := e.Comps[i].(T)
if ok {
return comp
}
}
return out
}
func GetAllCompOfType[T Comp](e *Entity) (out []T) {
out = []T{}
for i := 0; i < len(e.Comps); i++ {
comp, ok := e.Comps[i].(T)
if ok {
out = append(out, comp)
}
}
return out
type Entity interface {
GetHandle() registry.Handle
}

View File

@ -1,94 +0,0 @@
package entity
import (
"github.com/bloeys/nmage/assert"
)
type freeListitem struct {
EntityIndex uint64
nextFree *freeListitem
}
type Registry struct {
EntityCount uint64
Entities []Entity
FreeList *freeListitem
}
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 {
entityToUseIndex = r.FreeList.EntityIndex
entityToUse = &r.Entities[entityToUseIndex]
r.FreeList = r.FreeList.nextFree
} else {
for i := 0; i < len(r.Entities); i++ {
e := &r.Entities[i]
if GetFlags(e.ID) != byte(EntityFlag_Unknown) && !e.HasFlag(EntityFlag_Dead) {
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, byte(EntityFlag_Alive), entityToUseIndex)
assert.T(entityToUse.ID != 0, "Entity ID must not be zero")
return entityToUse
}
func (r *Registry) GetEntity(id uint64) *Entity {
index := GetIndex(id)
gen := GetGeneration(id)
e := &r.Entities[index]
eGen := GetGeneration(e.ID)
if gen != eGen {
return nil
}
return e
}
func (r *Registry) FreeEntity(id uint64) {
e := r.GetEntity(id)
if e == nil {
return
}
r.EntityCount--
eIndex := GetIndex(e.ID)
e.Comps = []Comp{}
e.ID = NewEntityId(GetGeneration(e.ID), byte(EntityFlag_Dead), eIndex)
r.FreeList = &freeListitem{
EntityIndex: eIndex,
nextFree: r.FreeList,
}
}
func NewRegistry(size uint32) *Registry {
assert.T(size > 0, "Registry size must be more than zero")
return &Registry{
Entities: make([]Entity, size),
}
}

7
go.mod
View File

@ -2,12 +2,13 @@ module github.com/bloeys/nmage
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/bloeys/assimp-go v0.4.4
github.com/bloeys/gglm v0.42.0
github.com/inkyblackness/imgui-go/v4 v4.6.0
github.com/bloeys/gglm v0.43.0
)
require github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2

19
go.sum
View File

@ -1,17 +1,10 @@
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/go.mod h1:my3yRxT7CfOztmvi+0svmwbaqw0KFrxaHxncoyaEIP0=
github.com/bloeys/gglm v0.42.0 h1:UAUFGTaZv3dpZ0YSIQVum3bdeCZgNmx965VLnD2v11k=
github.com/bloeys/gglm v0.42.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/bloeys/gglm v0.43.0 h1:ZpOghR3PHfpkigTDh+FqxLsF0gN8CD6s/bWoei6LyxI=
github.com/bloeys/gglm v0.43.0/go.mod h1:qwJQ0WzV191wAMwlGicbfbChbKoSedMk7gFFX6GnyOk=
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/inkyblackness/imgui-go/v4 v4.6.0 h1:ShcnXEYl80+xREGBY9OpGWePA6FfJChY9Varsm+3jjE=
github.com/inkyblackness/imgui-go/v4 v4.6.0/go.mod h1:g8SAGtOYUP7rYaOB2AsVKCEHmPMDmJKgt4z6d+flhb0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/veandco/go-sdl2 v0.4.25 h1:J5ac3KKOccp/0xGJA1PaNYKPUcZm19IxhDGs8lJofPI=
github.com/veandco/go-sdl2 v0.4.25/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
github.com/veandco/go-sdl2 v0.4.35 h1:NohzsfageDWGtCd9nf7Pc3sokMK/MOK+UA2QMJARWzQ=
github.com/veandco/go-sdl2 v0.4.35/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=

View File

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

256
main.go
View File

@ -2,29 +2,27 @@ package main
import (
"fmt"
"runtime"
"github.com/bloeys/assimp-go/asig"
imgui "github.com/AllenDang/cimgui-go"
"github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/assets"
"github.com/bloeys/nmage/camera"
"github.com/bloeys/nmage/engine"
"github.com/bloeys/nmage/entity"
"github.com/bloeys/nmage/input"
"github.com/bloeys/nmage/level"
"github.com/bloeys/nmage/logging"
"github.com/bloeys/nmage/materials"
"github.com/bloeys/nmage/meshes"
"github.com/bloeys/nmage/registry"
"github.com/bloeys/nmage/renderer/rend3dgl"
"github.com/bloeys/nmage/timing"
nmageimgui "github.com/bloeys/nmage/ui/imgui"
"github.com/inkyblackness/imgui-go/v4"
"github.com/go-gl/gl/v4.1-core/gl"
"github.com/veandco/go-sdl2/sdl"
)
// @Todo:
// Complete entity registry (e.g. HasEntity, GetEntity, Generational Indices etc...)
// Helper functions to update active entities
// Integrate physx
// Create VAO struct independent from VBO to support multi-VBO use cases (e.g. instancing)
// Renderer batching
@ -38,6 +36,9 @@ import (
const (
camSpeed = 15
mouseSensitivity = 0.5
unscaledWindowWidth = 1280
unscaledWindowHeight = 720
)
var (
@ -48,12 +49,23 @@ var (
cam *camera.Camera
simpleMat *materials.Material
cubeMesh *meshes.Mesh
skyboxMat *materials.Material
chairMesh *meshes.Mesh
cubeMesh *meshes.Mesh
skyboxMesh *meshes.Mesh
cubeModelMat = gglm.NewTrMatId()
lightPos1 = gglm.NewVec3(-2, 0, 2)
lightColor1 = gglm.NewVec3(1, 1, 1)
debugDepthMat *materials.Material
debugDrawDepthBuffer bool
skyboxCmap assets.Cubemap
dpiScaling float32
)
type OurGame struct {
@ -62,43 +74,52 @@ type OurGame struct {
}
type TransformComp struct {
entity.BaseComp
Pos *gglm.Vec3
Rot *gglm.Quat
Scale *gglm.Vec3
}
func (t TransformComp) Name() string {
func (t *TransformComp) Name() string {
return "Transform Component"
}
func Test() {
lvl := level.NewLevel("test level", 1000)
e1 := lvl.Registry.NewEntity()
// lvl := level.NewLevel("test level")
testRegistry := registry.NewRegistry[int](100)
trComp := entity.GetComp[*TransformComp](e1)
fmt.Println("Got comp 1:", trComp)
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())
e1.Comps = append(e1.Comps, &TransformComp{
trComp := entity.GetComp[*TransformComp](&e1CompContainer)
fmt.Println("Get comp before adding any:", trComp)
entity.AddComp(e1Handle, &e1CompContainer, &TransformComp{
Pos: gglm.NewVec3(0, 0, 0),
Rot: gglm.NewQuatEulerXYZ(0, 0, 0),
Scale: gglm.NewVec3(0, 0, 0),
}, &TransformComp{
Pos: gglm.NewVec3(0, 0, 0),
Rot: gglm.NewQuatEulerXYZ(0, 0, 0),
Scale: gglm.NewVec3(1, 1, 1),
})
trComp = entity.GetComp[*TransformComp](&e1CompContainer)
fmt.Println("Get transform comp:", trComp)
trComp = entity.GetComp[*TransformComp](e1)
fmt.Println("Got comp 2:", trComp)
e2, e2Handle := testRegistry.New()
e3, e3Handle := testRegistry.New()
e4, e4Handle := testRegistry.New()
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())
trComps := entity.GetAllCompOfType[*TransformComp](e1)
fmt.Printf("Got comp 3: %+v, %+v\n", trComps[0], trComps[1])
*e2 = 1000
fmt.Printf("Entity 2 value after registry get: %+v\n", *testRegistry.Get(e2Handle))
fmt.Printf("Entity: %+v\n", e1)
fmt.Printf("Entity: %+v\n", lvl.Registry.NewEntity())
fmt.Printf("Entity: %+v\n", lvl.Registry.NewEntity())
fmt.Printf("Entity: %+v\n", lvl.Registry.NewEntity())
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() {
@ -113,7 +134,8 @@ func main() {
}
//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 {
logging.ErrLog.Fatalln("Failed to create window. Err: ", err)
}
@ -123,7 +145,7 @@ func main() {
game := &OurGame{
Win: window,
ImGUIInfo: nmageimgui.NewImGUI(),
ImGUIInfo: nmageimgui.NewImGui(),
}
window.EventCallbacks = append(window.EventCallbacks, game.handleWindowEvents)
@ -142,38 +164,94 @@ func (g *OurGame) handleWindowEvents(e sdl.Event) {
cam.Update()
simpleMat.SetUnifMat4("projMat", &cam.ProjMat)
debugDepthMat.SetUnifMat4("projMat", &cam.ProjMat)
}
}
}
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() {
var err error
//Create materials
simpleMat = materials.NewMaterial("Simple Mat", "./res/shaders/simple.glsl")
simpleMat = materials.NewMaterial("Simple mat", "./res/shaders/simple.glsl")
debugDepthMat = materials.NewMaterial("Debug depth mat", "./res/shaders/debug-depth.glsl")
skyboxMat = materials.NewMaterial("Skybox mat", "./res/shaders/skybox.glsl")
//Load meshes
var err error
cubeMesh, err = meshes.NewMesh("Cube", "./res/models/tex-cube.fbx", asig.PostProcess(0))
cubeMesh, err = meshes.NewMesh("Cube", "./res/models/tex-cube.fbx", 0)
if err != nil {
logging.ErrLog.Fatalln("Failed to load cube mesh. Err: ", err)
logging.ErrLog.Fatalln("Failed to load mesh. Err: ", err)
}
chairMesh, err = meshes.NewMesh("Chair", "./res/models/chair.fbx", 0)
if err != nil {
logging.ErrLog.Fatalln("Failed to load mesh. Err: ", err)
}
skyboxMesh, err = meshes.NewMesh("Skybox", "./res/models/skybox-cube.obj", 0)
if err != nil {
logging.ErrLog.Fatalln("Failed to load mesh. Err: ", err)
}
//Load textures
tex, err := assets.LoadTexturePNG("./res/textures/Low poly planet.png", nil)
tex, err := assets.LoadTexturePNG("./res/textures/pallete-endesga-64-1x.png", nil)
if err != nil {
logging.ErrLog.Fatalln("Failed to load texture. Err: ", err)
}
//Configure material
skyboxCmap, err = assets.LoadCubemapTextures(
"./res/textures/sb-right.jpg", "./res/textures/sb-left.jpg",
"./res/textures/sb-top.jpg", "./res/textures/sb-bottom.jpg",
"./res/textures/sb-front.jpg", "./res/textures/sb-back.jpg",
)
if err != nil {
logging.ErrLog.Fatalln("Failed to load cubemap. Err: ", err)
}
// Configure materials
simpleMat.DiffuseTex = tex.TexID
//Movement, scale and rotation
translationMat := gglm.NewTranslationMat(gglm.NewVec3(0, 0, 0))
scaleMat := gglm.NewScaleMat(gglm.NewVec3(0.25, 0.25, 0.25))
rotMat := gglm.NewRotMat(gglm.NewQuatEuler(gglm.NewVec3(0, 0, 0).AsRad()))
scaleMat := gglm.NewScaleMat(gglm.NewVec3(1, 1, 1))
rotMat := gglm.NewRotMat(gglm.NewQuatEuler(gglm.NewVec3(-90, -90, 0).AsRad()))
cubeModelMat.Mul(translationMat.Mul(rotMat.Mul(scaleMat)))
simpleMat.SetUnifMat4("modelMat", &cubeModelMat.Mat4)
// Camera
winWidth, winHeight := g.Win.SDLWin.GetSize()
@ -181,11 +259,12 @@ func (g *OurGame) Init() {
gglm.NewVec3(0, 0, 10),
gglm.NewVec3(0, 0, -1),
gglm.NewVec3(0, 1, 0),
0.1, 20,
0.1, 200,
45*gglm.Deg2Rad,
float32(winWidth)/float32(winHeight),
)
simpleMat.SetUnifMat4("projMat", &cam.ProjMat)
debugDepthMat.SetUnifMat4("projMat", &cam.ProjMat)
updateViewMat()
@ -194,21 +273,6 @@ func (g *OurGame) Init() {
simpleMat.SetUnifVec3("lightColor1", lightColor1)
}
// @TODO: Add this to gglm
// func vecRotByQuat(v *gglm.Vec3, q *gglm.Quat) *gglm.Vec3 {
// // Reference: https://gamedev.stackexchange.com/questions/28395/rotating-vector3-by-a-quaternion
// qVec := gglm.NewVec3(q.X(), q.Y(), q.Z())
// rotatedVec := qVec.Clone().Scale(2 * gglm.DotVec3(v, qVec))
// t1 := q.W()*q.W() - gglm.DotVec3(qVec, qVec)
// rotatedVec.Add(v.Clone().Scale(t1))
// rotatedVec.Add(gglm.Cross(qVec, v).Scale(2 * q.W()))
// return rotatedVec
// }
func (g *OurGame) Update() {
if input.IsQuitClicked() || input.KeyClicked(sdl.K_ESCAPE) {
@ -218,12 +282,14 @@ func (g *OurGame) Update() {
g.updateCameraLookAround()
g.updateCameraPos()
imgui.ShowDemoWindow()
//Rotating cubes
if input.KeyDown(sdl.K_SPACE) {
cubeModelMat.Rotate(10*timing.DT()*gglm.Deg2Rad, gglm.NewVec3(1, 1, 1).Normalize())
simpleMat.SetUnifMat4("modelMat", &cubeModelMat.Mat4)
}
imgui.Begin("Debug controls")
if imgui.DragFloat3("Cam Pos", &cam.Pos.Data) {
updateViewMat()
}
@ -231,15 +297,28 @@ func (g *OurGame) Update() {
updateViewMat()
}
if imgui.DragFloat3("Light Pos 1", &lightPos1.Data) {
simpleMat.SetUnifVec3("lightPos1", lightPos1)
}
if imgui.DragFloat3("Light Color 1", &lightColor1.Data) {
simpleMat.SetUnifVec3("lightColor1", lightColor1)
}
imgui.Checkbox("Debug depth buffer", &debugDrawDepthBuffer)
imgui.End()
if input.KeyClicked(sdl.K_F4) {
fmt.Printf("Pos: %s; Forward: %s; |Forward|: %f\n", cam.Pos.String(), cam.Forward.String(), cam.Forward.Mag())
}
g.Win.SDLWin.SetTitle(fmt.Sprint("nMage (", timing.GetAvgFPS(), " fps)"))
}
func (g *OurGame) updateCameraLookAround() {
mouseX, mouseY := input.GetMouseMotion()
if mouseX == 0 && mouseY == 0 {
if (mouseX == 0 && mouseY == 0) || !input.MouseDown(sdl.BUTTON_RIGHT) {
return
}
@ -266,21 +345,26 @@ func (g *OurGame) updateCameraPos() {
update := false
var camSpeedScale float32 = 1.0
if input.KeyDown(sdl.K_LSHIFT) {
camSpeedScale = 2
}
// Forward and backward
if input.KeyDown(sdl.K_w) {
cam.Pos.Add(cam.Forward.Clone().Scale(camSpeed * timing.DT()))
cam.Pos.Add(cam.Forward.Clone().Scale(camSpeed * camSpeedScale * timing.DT()))
update = true
} else if input.KeyDown(sdl.K_s) {
cam.Pos.Add(cam.Forward.Clone().Scale(-camSpeed * timing.DT()))
cam.Pos.Add(cam.Forward.Clone().Scale(-camSpeed * camSpeedScale * timing.DT()))
update = true
}
// Left and right
if input.KeyDown(sdl.K_d) {
cam.Pos.Add(gglm.Cross(&cam.Forward, &cam.WorldUp).Normalize().Scale(camSpeed * timing.DT()))
cam.Pos.Add(gglm.Cross(&cam.Forward, &cam.WorldUp).Normalize().Scale(camSpeed * camSpeedScale * timing.DT()))
update = true
} else if input.KeyDown(sdl.K_a) {
cam.Pos.Add(gglm.Cross(&cam.Forward, &cam.WorldUp).Normalize().Scale(-camSpeed * timing.DT()))
cam.Pos.Add(gglm.Cross(&cam.Forward, &cam.WorldUp).Normalize().Scale(-camSpeed * camSpeedScale * timing.DT()))
update = true
}
@ -291,18 +375,53 @@ func (g *OurGame) updateCameraPos() {
func (g *OurGame) Render() {
tempModelMat := cubeModelMat.Clone()
rowSize := 100
for y := 0; y < rowSize; y++ {
for x := 0; x < rowSize; x++ {
tempModelMat.Translate(gglm.NewVec3(-1, 0, 0))
window.Rend.Draw(cubeMesh, tempModelMat, simpleMat)
}
tempModelMat.Translate(gglm.NewVec3(float32(rowSize), -1, 0))
matToUse := simpleMat
if debugDrawDepthBuffer {
matToUse = debugDepthMat
}
g.Win.SDLWin.SetTitle(fmt.Sprint("nMage (", timing.GetAvgFPS(), " fps)"))
tempModelMatrix := cubeModelMat.Clone()
window.Rend.Draw(chairMesh, tempModelMatrix, matToUse)
rowSize := 1
for y := 0; y < rowSize; y++ {
for x := 0; x < rowSize; x++ {
tempModelMatrix.Translate(gglm.NewVec3(-6, 0, 0))
window.Rend.Draw(cubeMesh, tempModelMatrix, matToUse)
}
tempModelMatrix.Translate(gglm.NewVec3(float32(rowSize), -1, 0))
}
g.DrawSkybox()
}
func (g *OurGame) DrawSkybox() {
gl.Disable(gl.CULL_FACE)
gl.DepthFunc(gl.LEQUAL)
skyboxMesh.Buf.Bind()
skyboxMat.Bind()
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_CUBE_MAP, skyboxCmap.TexID)
viewMat := cam.ViewMat.Clone()
viewMat.Set(0, 3, 0)
viewMat.Set(1, 3, 0)
viewMat.Set(2, 3, 0)
viewMat.Set(3, 0, 0)
viewMat.Set(3, 1, 0)
viewMat.Set(3, 2, 0)
viewMat.Set(3, 3, 0)
skyboxMat.SetUnifMat4("viewMat", viewMat)
skyboxMat.SetUnifMat4("projMat", &cam.ProjMat)
// window.Rend.Draw(cubeMesh, gglm.NewTrMatId(), skyboxMat)
for i := 0; i < len(skyboxMesh.SubMeshes); i++ {
gl.DrawElementsBaseVertexWithOffset(gl.TRIANGLES, skyboxMesh.SubMeshes[i].IndexCount, gl.UNSIGNED_INT, uintptr(skyboxMesh.SubMeshes[i].BaseIndex), skyboxMesh.SubMeshes[i].BaseVertex)
}
gl.DepthFunc(gl.LESS)
gl.Enable(gl.CULL_FACE)
}
func (g *OurGame) FrameEnd() {
@ -315,4 +434,5 @@ func (g *OurGame) DeInit() {
func updateViewMat() {
cam.Update()
simpleMat.SetUnifMat4("viewMat", &cam.ViewMat)
debugDepthMat.SetUnifMat4("viewMat", &cam.ViewMat)
}

View File

@ -10,9 +10,16 @@ import (
"github.com/bloeys/nmage/buffers"
)
type SubMesh struct {
BaseVertex int32
BaseIndex uint32
IndexCount int32
}
type Mesh struct {
Name string
Buf buffers.Buffer
Name string
Buf buffers.Buffer
SubMeshes []SubMesh
}
func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh, error) {
@ -27,32 +34,68 @@ func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh,
return nil, errors.New("No meshes found in file: " + modelPath)
}
mesh := &Mesh{Name: name}
sceneMesh := scene.Meshes[0]
mesh.Buf = buffers.NewBuffer()
if len(sceneMesh.TexCoords[0]) == 0 {
sceneMesh.TexCoords[0] = make([]gglm.Vec3, len(sceneMesh.Vertices))
mesh := &Mesh{
Name: name,
Buf: buffers.NewBuffer(),
SubMeshes: make([]SubMesh, 0, 1),
}
layoutToUse := []buffers.Element{{ElementType: buffers.DataTypeVec3}, {ElementType: buffers.DataTypeVec3}, {ElementType: buffers.DataTypeVec2}}
// Initial sizes assuming one submesh that has vertex pos+normals+texCoords, and 3 indices per face
var vertexBufData []float32 = make([]float32, 0, len(scene.Meshes[0].Vertices)*3*3*2)
var indexBufData []uint32 = make([]uint32, 0, len(scene.Meshes[0].Faces)*3)
if len(sceneMesh.ColorSets) > 0 && len(sceneMesh.ColorSets[0]) > 0 {
layoutToUse = append(layoutToUse, buffers.Element{ElementType: buffers.DataTypeVec4})
}
mesh.Buf.SetLayout(layoutToUse...)
for i := 0; i < len(scene.Meshes); i++ {
var values []float32
arrs := []arrToInterleave{{V3s: sceneMesh.Vertices}, {V3s: sceneMesh.Normals}, {V2s: v3sToV2s(sceneMesh.TexCoords[0])}}
sceneMesh := scene.Meshes[i]
if len(sceneMesh.ColorSets) > 0 && len(sceneMesh.ColorSets[0]) > 0 {
arrs = append(arrs, arrToInterleave{V4s: sceneMesh.ColorSets[0]})
if len(sceneMesh.TexCoords[0]) == 0 {
sceneMesh.TexCoords[0] = make([]gglm.Vec3, len(sceneMesh.Vertices))
println("Zeroing tex coords for submesh", i)
}
layoutToUse := []buffers.Element{{ElementType: buffers.DataTypeVec3}, {ElementType: buffers.DataTypeVec3}, {ElementType: buffers.DataTypeVec2}}
if len(sceneMesh.ColorSets) > 0 && len(sceneMesh.ColorSets[0]) > 0 {
layoutToUse = append(layoutToUse, buffers.Element{ElementType: buffers.DataTypeVec4})
}
if i == 0 {
mesh.Buf.SetLayout(layoutToUse...)
} else {
// @NOTE: Require that all submeshes have the same vertex buffer layout
firstSubmeshLayout := mesh.Buf.GetLayout()
assert.T(len(firstSubmeshLayout) == len(layoutToUse), fmt.Sprintf("Vertex layout of submesh %d does not equal vertex layout of the first submesh. Original layout: %v; This layout: %v", i, firstSubmeshLayout, layoutToUse))
for i := 0; i < len(firstSubmeshLayout); i++ {
if firstSubmeshLayout[i].ElementType != layoutToUse[i].ElementType {
panic(fmt.Sprintf("Vertex layout of submesh %d does not equal vertex layout of the first submesh. Original layout: %v; This layout: %v", i, firstSubmeshLayout, layoutToUse))
}
}
}
arrs := []arrToInterleave{{V3s: sceneMesh.Vertices}, {V3s: sceneMesh.Normals}, {V2s: v3sToV2s(sceneMesh.TexCoords[0])}}
if len(sceneMesh.ColorSets) > 0 && len(sceneMesh.ColorSets[0]) > 0 {
arrs = append(arrs, arrToInterleave{V4s: sceneMesh.ColorSets[0]})
}
indices := flattenFaces(sceneMesh.Faces)
mesh.SubMeshes = append(mesh.SubMeshes, SubMesh{
// Index of the vertex to start from (e.g. if index buffer says use vertex 5, and BaseVertex=3, the vertex used will be vertex 8)
BaseVertex: int32(len(vertexBufData)*4) / mesh.Buf.Stride,
// Which index (in the index buffer) to start from
BaseIndex: uint32(len(indexBufData)),
// How many indices in this submesh
IndexCount: int32(len(indices)),
})
vertexBufData = append(vertexBufData, interleave(arrs...)...)
indexBufData = append(indexBufData, indices...)
}
values = interleave(arrs...)
mesh.Buf.SetData(values)
mesh.Buf.SetIndexBufData(flattenFaces(sceneMesh.Faces))
// fmt.Printf("!!! Vertex count: %d; Submeshes: %+v\n", len(vertexBufData)*4/int(mesh.Buf.Stride), mesh.SubMeshes)
mesh.Buf.SetData(vertexBufData)
mesh.Buf.SetIndexBufData(indexBufData)
return mesh, nil
}

72
registry/iterator.go Executable file
View File

@ -0,0 +1,72 @@
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
}
it.remainingItems--
it.currIndex++
return &it.registry.Items[it.currIndex], 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))
}

View File

@ -28,7 +28,10 @@ func (r3d *Rend3DGL) Draw(mesh *meshes.Mesh, trMat *gglm.TrMat, mat *materials.M
}
mat.SetUnifMat4("modelMat", &trMat.Mat4)
gl.DrawElements(gl.TRIANGLES, mesh.Buf.IndexBufCount, gl.UNSIGNED_INT, gl.PtrOffset(0))
for i := 0; i < len(mesh.SubMeshes); i++ {
gl.DrawElementsBaseVertexWithOffset(gl.TRIANGLES, mesh.SubMeshes[i].IndexCount, gl.UNSIGNED_INT, uintptr(mesh.SubMeshes[i].BaseIndex), mesh.SubMeshes[i].BaseVertex)
}
}
func (r3d *Rend3DGL) FrameEnd() {

BIN
res/models/chair.fbx Executable file

Binary file not shown.

View File

@ -1,46 +0,0 @@
# Blender v2.92.0 OBJ File: ''
# www.blender.org
mtllib obj.mtl
o Cube
v 2.275618 1.000000 0.349413
v 3.520138 -1.000000 0.102233
v 2.275618 1.000000 0.752820
v 3.520138 -1.000000 1.000000
v 0.244520 1.000000 0.349413
v -1.000000 -1.000000 0.102233
v 0.244520 1.000000 0.752820
v -1.000000 -1.000000 1.000000
vt 0.806168 0.568832
vt 0.693832 0.681168
vt 0.693832 0.568832
vt 0.375000 1.000000
vt 0.375000 0.750000
vt 0.375000 0.000000
vt 0.625000 0.250000
vt 0.375000 0.250000
vt 0.375000 0.500000
vt 0.125000 0.750000
vt 0.125000 0.500000
vt 0.806168 0.681168
vt 0.625000 0.931168
vt 0.625000 0.068832
vn 0.0000 1.0000 0.0000
vn 0.0000 0.1227 0.9924
vn -0.8490 0.5283 0.0000
vn 0.0000 -1.0000 0.0000
vn 0.8490 0.5283 0.0000
vn 0.0000 0.1227 -0.9924
usemtl Material
s off
f 5/1/1 3/2/1 1/3/1
f 3/2/2 8/4/2 4/5/2
f 8/6/3 5/7/3 6/8/3
f 2/9/4 8/10/4 6/11/4
f 1/3/5 4/5/5 2/9/5
f 5/7/6 2/9/6 6/8/6
f 5/1/1 7/12/1 3/2/1
f 3/2/2 7/13/2 8/4/2
f 8/6/3 7/14/3 5/7/3
f 2/9/4 4/5/4 8/10/4
f 1/3/5 3/2/5 4/5/5
f 5/7/6 1/3/6 2/9/6

38
res/models/skybox-cube.obj Executable file
View File

@ -0,0 +1,38 @@
# Blender v2.92.0 OBJ File: 'chair.blend'
# www.blender.org
o Cube.002_Cube.005
v -1.000000 -1.000000 1.000000
v -1.000000 1.000000 1.000000
v -1.000000 -1.000000 -1.000000
v -1.000000 1.000000 -1.000000
v 1.000000 -1.000000 1.000000
v 1.000000 1.000000 1.000000
v 1.000000 -1.000000 -1.000000
v 1.000000 1.000000 -1.000000
vt 0.375000 0.000000
vt 0.625000 0.000000
vt 0.625000 0.250000
vt 0.375000 0.250000
vt 0.625000 0.500000
vt 0.375000 0.500000
vt 0.625000 0.750000
vt 0.375000 0.750000
vt 0.625000 1.000000
vt 0.375000 1.000000
vt 0.125000 0.500000
vt 0.125000 0.750000
vt 0.875000 0.500000
vt 0.875000 0.750000
vn -1.0000 0.0000 0.0000
vn 0.0000 0.0000 -1.0000
vn 1.0000 0.0000 0.0000
vn 0.0000 0.0000 1.0000
vn 0.0000 -1.0000 0.0000
vn 0.0000 1.0000 0.0000
s off
f 1/1/1 2/2/1 4/3/1 3/4/1
f 3/4/2 4/3/2 8/5/2 7/6/2
f 7/6/3 8/5/3 6/7/3 5/8/3
f 5/8/4 6/7/4 2/9/4 1/10/4
f 3/11/5 7/6/5 5/8/5 1/12/5
f 8/5/6 4/13/6 2/14/6 6/7/6

52
res/shaders/debug-depth.glsl Executable file
View File

@ -0,0 +1,52 @@
//shader:vertex
#version 410
layout(location=0) in vec3 vertPosIn;
layout(location=1) in vec3 vertNormalIn;
layout(location=2) in vec2 vertUV0In;
layout(location=3) in vec3 vertColorIn;
out vec3 vertNormal;
out vec2 vertUV0;
out vec3 vertColor;
out vec3 fragPos;
//MVP = Model View Projection
uniform mat4 modelMat;
uniform mat4 viewMat;
uniform mat4 projMat;
void main()
{
vertNormal = mat3(transpose(inverse(modelMat))) * vertNormalIn;
vertUV0 = vertUV0In;
vertColor = vertColorIn;
fragPos = vec3(modelMat * vec4(vertPosIn, 1.0));
gl_Position = projMat * viewMat * modelMat * vec4(vertPosIn, 1.0);
}
//shader:fragment
#version 410
in vec3 vertColor;
in vec3 vertNormal;
in vec2 vertUV0;
in vec3 fragPos;
out vec4 fragColor;
uniform float near = 0.1;
uniform float far = 200.0;
float LinearizeDepth(float depth)
{
float z = depth * 2.0 - 1.0; // back to NDC
return (2.0 * near * far) / (far + near - z * (far - near));
}
void main()
{
float depth = LinearizeDepth(gl_FragCoord.z) / far;
fragColor = vec4(vec3(depth), 1.0);
}

View File

@ -29,7 +29,7 @@ void main()
//shader:fragment
#version 410
uniform float ambientStrength = 0.1;
uniform float ambientStrength = 0;
uniform vec3 ambientLightColor = vec3(1, 1, 1);
uniform vec3 lightPos1;

33
res/shaders/skybox.glsl Executable file
View File

@ -0,0 +1,33 @@
//shader:vertex
#version 410
layout(location=0) in vec3 vertPosIn;
layout(location=1) in vec3 vertNormalIn;
layout(location=2) in vec2 vertUV0In;
layout(location=3) in vec3 vertColorIn;
out vec3 vertUV0;
uniform mat4 viewMat;
uniform mat4 projMat;
void main()
{
vertUV0 = vec3(vertPosIn.x, vertPosIn.y, -vertPosIn.z);
vec4 pos = projMat * viewMat * vec4(vertPosIn, 1.0);
gl_Position = pos.xyww;
}
//shader:fragment
#version 410
in vec3 vertUV0;
out vec4 fragColor;
uniform samplerCube skybox;
void main()
{
fragColor = texture(skybox, vertUV0);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

BIN
res/textures/sb-back.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1008 KiB

BIN
res/textures/sb-bottom.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

BIN
res/textures/sb-front.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
res/textures/sb-left.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
res/textures/sb-right.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
res/textures/sb-top.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 338 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
import (
imgui "github.com/AllenDang/cimgui-go"
"github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/assert"
"github.com/bloeys/nmage/materials"
"github.com/bloeys/nmage/timing"
"github.com/go-gl/gl/v4.1-core/gl"
"github.com/inkyblackness/imgui-go/v4"
"github.com/veandco/go-sdl2/sdl"
)
type ImguiInfo struct {
ImCtx *imgui.Context
ImCtx imgui.Context
Mat *materials.Material
VaoID uint32
@ -22,9 +21,9 @@ type ImguiInfo struct {
func (i *ImguiInfo) FrameStart(winWidth, winHeight float32) {
if err := i.ImCtx.SetCurrent(); err != nil {
assert.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
}
// if err := i.ImCtx.SetCurrent(); err != nil {
// assert.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
// }
imIO := imgui.CurrentIO()
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) {
if err := i.ImCtx.SetCurrent(); err != nil {
assert.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
}
// if err := i.ImCtx.SetCurrent(); err != nil {
// assert.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
// }
imgui.Render()
@ -46,7 +45,7 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
return
}
drawData := imgui.RenderedDrawData()
drawData := imgui.CurrentDrawData()
drawData.ScaleClipRects(imgui.Vec2{
X: float32(fbWidth) / float32(winWidth),
Y: float32(fbHeight) / float32(winHeight),
@ -96,11 +95,11 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
// Draw
for _, list := range drawData.CommandLists() {
vertexBuffer, vertexBufferSize := list.VertexBuffer()
vertexBuffer, vertexBufferSize := list.GetVertexBuffer()
gl.BindBuffer(gl.ARRAY_BUFFER, i.VboID)
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.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()
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)
}
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 {
fontConfigToUse = *fontConfig
}
glyphRangesToUse := imgui.EmptyGlyphRanges
glyphRangesToUse := imgui.NewGlyphRange()
if glyphRanges != nil {
glyphRangesToUse = *glyphRanges
}
@ -139,11 +138,11 @@ func (i *ImguiInfo) AddFontTTF(fontPath string, fontSize float32, fontConfig *im
imIO := imgui.CurrentIO()
a := imIO.Fonts()
f := a.AddFontFromFileTTFV(fontPath, fontSize, fontConfigToUse, glyphRangesToUse)
image := a.TextureDataAlpha8()
f := a.AddFontFromFileTTFV(fontPath, fontSize, fontConfigToUse, glyphRangesToUse.Data())
pixels, width, height, _ := a.GetTextureDataAsAlpha8()
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
}
@ -184,15 +183,16 @@ void main()
}
`
func NewImGUI() ImguiInfo {
func NewImGui() ImguiInfo {
imguiInfo := ImguiInfo{
ImCtx: imgui.CreateContext(nil),
ImCtx: imgui.CreateContext(),
Mat: materials.NewMaterialSrc("ImGUI Mat", []byte(imguiShdrSrc)),
}
imIO := imgui.CurrentIO()
imIO.SetBackendFlags(imIO.GetBackendFlags() | imgui.BackendFlagsRendererHasVtxOffset)
io := imgui.CurrentIO()
io.SetConfigFlags(io.ConfigFlags() | imgui.ConfigFlagsDockingEnable)
io.SetBackendFlags(io.BackendFlags() | imgui.BackendFlagsRendererHasVtxOffset)
gl.GenVertexArrays(1, &imguiInfo.VaoID)
gl.GenBuffers(1, &imguiInfo.VboID)
@ -205,11 +205,11 @@ func NewImGUI() ImguiInfo {
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
image := imIO.Fonts().TextureDataAlpha8()
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(image.Width), int32(image.Height), 0, gl.RED, gl.UNSIGNED_BYTE, image.Pixels)
pixels, width, height, _ := io.Fonts().GetTextureDataAsAlpha8()
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(width), int32(height), 0, gl.RED, gl.UNSIGNED_BYTE, pixels)
// Store our identifier
imIO.Fonts().SetTextureID(imgui.TextureID(imguiInfo.TexID))
io.Fonts().SetTexID(imgui.TextureID(uintptr(imguiInfo.TexID)))
//Shader attributes
imguiInfo.Mat.Bind()
@ -218,35 +218,223 @@ func NewImGUI() ImguiInfo {
imguiInfo.Mat.EnableAttribute("Color")
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
}
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
}
}