Compare commits

...

22 Commits

Author SHA1 Message Date
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
30 changed files with 681 additions and 255 deletions

View File

@ -1,22 +1,28 @@
name: build-nmage name: build-nmage
on: on:
create: create:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
build-nmage-macos: build-nmage-macos:
runs-on: macos-10.15 runs-on: macos-12
steps: steps:
- name: Install golang 1.17
uses: actions/setup-go@v2 - name: Install golang 1.18
uses: actions/setup-go@v3
with: with:
go-version: '^1.17' go-version: '^1.18'
- name: Install assimp-go dylib - 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 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 - name: Install SDL2
run: brew install sdl2{,_image,_mixer,_ttf,_gfx} pkg-config run: brew install sdl2{,_image,_mixer,_ttf,_gfx} pkg-config
- name: Clone nmage - name: Clone nmage
run: git clone https://github.com/bloeys/nmage run: git clone https://github.com/bloeys/nmage
- name: build nmage - name: build nmage
working-directory: nmage working-directory: nmage
run: go build . run: go build .

View File

@ -1,6 +1,6 @@
# nMage # 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). 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" "github.com/bloeys/nmage/logging"
) )
func T(check bool, msg string) { func T(check bool, msg string, args ...any) {
if consts.Debug && !check { 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 package assets
var ( var (
Textures map[uint32]Texture = make(map[uint32]Texture) Textures = make(map[uint32]Texture)
TexturePaths map[string]uint32 = make(map[string]uint32) TexturePaths = make(map[string]uint32)
) )
func AddTextureToCache(t Texture) { func AddTextureToCache(t Texture) {

View File

@ -2,10 +2,15 @@ package assets
import ( import (
"bytes" "bytes"
"fmt"
"image" "image"
"image/color" "image/color"
"image/jpeg"
"image/png" "image/png"
"io"
"os" "os"
"path"
"strings"
"unsafe" "unsafe"
"github.com/go-gl/gl/v4.1-core/gl" "github.com/go-gl/gl/v4.1-core/gl"
@ -30,6 +35,18 @@ type TextureLoadOptions struct {
TryLoadFromCache bool TryLoadFromCache bool
WriteToCache bool WriteToCache bool
GenMipMaps 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) { func LoadTexturePNG(file string, loadOptions *TextureLoadOptions) (Texture, error) {
@ -56,28 +73,10 @@ func LoadTexturePNG(file string, loadOptions *TextureLoadOptions) (Texture, erro
} }
tex := Texture{ tex := Texture{
Path: file, Path: file,
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 tex.Pixels, tex.Width, tex.Height = pixelsFromNrgbaPng(img)
//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
}
}
//Prepare opengl stuff //Prepare opengl stuff
gl.GenTextures(1, &tex.TexID) gl.GenTextures(1, &tex.TexID)
@ -100,37 +99,21 @@ func LoadTexturePNG(file string, loadOptions *TextureLoadOptions) (Texture, erro
AddTextureToCache(tex) AddTextureToCache(tex)
} }
if !loadOptions.KeepPixelsInMem {
tex.Pixels = nil
}
return tex, 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 { if loadOptions == nil {
loadOptions = &TextureLoadOptions{} loadOptions = &TextureLoadOptions{}
} }
tex := Texture{ tex := Texture{}
Width: int32(img.Bounds().Dx()), tex.Pixels, tex.Width, tex.Height = pixelsFromNrgbaPng(img)
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
}
}
//Prepare opengl stuff //Prepare opengl stuff
gl.GenTextures(1, &tex.TexID) gl.GenTextures(1, &tex.TexID)
@ -153,5 +136,173 @@ func LoadTextureInMemImg(img image.Image, loadOptions *TextureLoadOptions) (Text
AddTextureToCache(tex) AddTextureToCache(tex)
} }
if !loadOptions.KeepPixelsInMem {
tex.Pixels = nil
}
return tex, 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 { type Buffer struct {
VAOID uint32 VAOID uint32
//BufID is the ID of the VBO // BufID is the ID of the VBO
BufID uint32 BufID uint32
//IndexBufID is the ID of the index/element buffer // IndexBufID is the ID of the index/element buffer
IndexBufID uint32 IndexBufID uint32
// IndexBufCount is the number of elements in the index buffer
// Updated on SetIndexBufData
IndexBufCount int32 IndexBufCount int32
Stride int32 // IndexBufCount int32
Stride int32
layout []Element layout []Element
} }
@ -80,8 +83,8 @@ func (b *Buffer) GetLayout() []Element {
return e return e
} }
//SetLayout updates the layout object and the corresponding vertex attributes. // SetLayout updates the layout object and the corresponding vertex attributes.
//Vertex attributes are also enabled. // Vertex attributes are also enabled.
func (b *Buffer) SetLayout(layout ...Element) { func (b *Buffer) SetLayout(layout ...Element) {
b.layout = layout b.layout = layout
@ -101,7 +104,7 @@ func (b *Buffer) SetLayout(layout ...Element) {
for i := 0; i < len(layout); i++ { for i := 0; i < len(layout); i++ {
gl.EnableVertexAttribArray(uint32(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() b.UnBind()

View File

@ -112,7 +112,7 @@ func Init() error {
func initSDL() error { func initSDL() error {
err := sdl.Init(sdl.INIT_EVERYTHING) err := sdl.Init(sdl.INIT_TIMER | sdl.INIT_VIDEO)
if err != nil { if err != nil {
return err return err
} }

View File

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

27
entity/base_comp.go Executable file
View File

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

62
entity/comp.go Executable file
View File

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

@ -3,9 +3,8 @@ package entity
type EntityFlag byte type EntityFlag byte
const ( const (
EntityFlag_Unknown EntityFlag = 0 EntityFlag_None EntityFlag = 0
EntityFlag_Dead EntityFlag = 1 << (iota - 1) EntityFlag_Alive EntityFlag = 1 << (iota - 1)
EntityFlag_Alive
) )
const ( const (
@ -14,64 +13,37 @@ const (
IndexBitMask = 0x00_00_FFFF_FFFF_FFFF IndexBitMask = 0x00_00_FFFF_FFFF_FFFF
) )
type EntityHandle uint64
type Entity struct { type Entity struct {
// Byte 1: Generation; Byte 2: Flags; Bytes 3-8: Index // Byte 1: Generation; Byte 2: Flags; Bytes 3-8: Index
ID uint64 ID EntityHandle
Comps []Comp Comps []Comp
} }
func GetGeneration(id uint64) byte { 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) return byte(id >> GenerationShiftBits)
} }
func GetFlags(id uint64) byte { func GetFlags(id EntityHandle) EntityFlag {
return byte(id >> FlagsShiftBits) return EntityFlag(id >> FlagsShiftBits)
} }
func GetIndex(id uint64) uint64 { func GetIndex(id EntityHandle) uint64 {
return id & IndexBitMask return uint64(id & IndexBitMask)
} }
func (e *Entity) HasFlag(ef EntityFlag) bool { func NewEntityId(generation byte, flags EntityFlag, index uint64) EntityHandle {
return GetFlags(e.ID)&byte(ef) > 0 return EntityHandle(index | (uint64(generation) << GenerationShiftBits) | (uint64(flags) << FlagsShiftBits))
}
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
} }

View File

@ -4,6 +4,12 @@ import (
"github.com/bloeys/nmage/assert" "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 { type freeListitem struct {
EntityIndex uint64 EntityIndex uint64
nextFree *freeListitem nextFree *freeListitem
@ -12,7 +18,9 @@ type freeListitem struct {
type Registry struct { type Registry struct {
EntityCount uint64 EntityCount uint64
Entities []Entity Entities []Entity
FreeList *freeListitem
FreeList *freeListitem
FreeListSize uint32
} }
func (r *Registry) NewEntity() *Entity { func (r *Registry) NewEntity() *Entity {
@ -22,17 +30,18 @@ func (r *Registry) NewEntity() *Entity {
entityToUseIndex := uint64(0) entityToUseIndex := uint64(0)
var entityToUse *Entity = nil var entityToUse *Entity = nil
if r.FreeList != nil { if r.FreeList != nil && r.FreeListSize > FreeListUsageThreshold {
entityToUseIndex = r.FreeList.EntityIndex entityToUseIndex = r.FreeList.EntityIndex
entityToUse = &r.Entities[entityToUseIndex] entityToUse = &r.Entities[entityToUseIndex]
r.FreeList = r.FreeList.nextFree r.FreeList = r.FreeList.nextFree
r.FreeListSize--
} else { } else {
for i := 0; i < len(r.Entities); i++ { for i := 0; i < len(r.Entities); i++ {
e := &r.Entities[i] e := &r.Entities[i]
if GetFlags(e.ID) != byte(EntityFlag_Unknown) && !e.HasFlag(EntityFlag_Dead) { if e.HasFlag(EntityFlag_Alive) {
continue continue
} }
@ -47,12 +56,12 @@ func (r *Registry) NewEntity() *Entity {
} }
r.EntityCount++ r.EntityCount++
entityToUse.ID = NewEntityId(GetGeneration(entityToUse.ID)+1, byte(EntityFlag_Alive), entityToUseIndex) entityToUse.ID = NewEntityId(GetGeneration(entityToUse.ID)+1, EntityFlag_Alive, entityToUseIndex)
assert.T(entityToUse.ID != 0, "Entity ID must not be zero") assert.T(entityToUse.ID != 0, "Entity ID must not be zero")
return entityToUse return entityToUse
} }
func (r *Registry) GetEntity(id uint64) *Entity { func (r *Registry) GetEntity(id EntityHandle) *Entity {
index := GetIndex(id) index := GetIndex(id)
gen := GetGeneration(id) gen := GetGeneration(id)
@ -67,23 +76,29 @@ func (r *Registry) GetEntity(id uint64) *Entity {
return e return e
} }
func (r *Registry) FreeEntity(id uint64) { // 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) e := r.GetEntity(id)
if e == nil { if e == nil {
return return
} }
for i := 0; i < len(e.Comps); i++ {
e.Comps[i].Destroy()
}
r.EntityCount-- r.EntityCount--
eIndex := GetIndex(e.ID) eIndex := GetIndex(e.ID)
e.Comps = []Comp{} e.Comps = []Comp{}
e.ID = NewEntityId(GetGeneration(e.ID), byte(EntityFlag_Dead), eIndex) e.ID = NewEntityId(GetGeneration(e.ID), EntityFlag_None, eIndex)
r.FreeList = &freeListitem{ r.FreeList = &freeListitem{
EntityIndex: eIndex, EntityIndex: eIndex,
nextFree: r.FreeList, nextFree: r.FreeList,
} }
r.FreeListSize++
} }
func NewRegistry(size uint32) *Registry { func NewRegistry(size uint32) *Registry {

2
go.mod
View File

@ -8,6 +8,6 @@ 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.42.0 github.com/bloeys/gglm v0.43.0
github.com/inkyblackness/imgui-go/v4 v4.6.0 github.com/inkyblackness/imgui-go/v4 v4.6.0
) )

4
go.sum
View File

@ -1,7 +1,7 @@
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.42.0 h1:UAUFGTaZv3dpZ0YSIQVum3bdeCZgNmx965VLnD2v11k= github.com/bloeys/gglm v0.43.0 h1:ZpOghR3PHfpkigTDh+FqxLsF0gN8CD6s/bWoei6LyxI=
github.com/bloeys/gglm v0.42.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 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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=

175
main.go
View File

@ -3,7 +3,6 @@ package main
import ( import (
"fmt" "fmt"
"github.com/bloeys/assimp-go/asig"
"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"
@ -17,14 +16,12 @@ import (
"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/inkyblackness/imgui-go/v4" "github.com/inkyblackness/imgui-go/v4"
"github.com/veandco/go-sdl2/sdl" "github.com/veandco/go-sdl2/sdl"
) )
// @Todo: // @Todo:
// Complete entity registry (e.g. HasEntity, GetEntity, Generational Indices etc...)
// Helper functions to update active entities
// Integrate physx // Integrate physx
// Create VAO struct independent from VBO to support multi-VBO use cases (e.g. instancing) // Create VAO struct independent from VBO to support multi-VBO use cases (e.g. instancing)
// Renderer batching // Renderer batching
@ -48,12 +45,21 @@ var (
cam *camera.Camera cam *camera.Camera
simpleMat *materials.Material simpleMat *materials.Material
cubeMesh *meshes.Mesh skyboxMat *materials.Material
chairMesh *meshes.Mesh
cubeMesh *meshes.Mesh
skyboxMesh *meshes.Mesh
cubeModelMat = gglm.NewTrMatId() cubeModelMat = gglm.NewTrMatId()
lightPos1 = gglm.NewVec3(-2, 0, 2) lightPos1 = gglm.NewVec3(-2, 0, 2)
lightColor1 = gglm.NewVec3(1, 1, 1) lightColor1 = gglm.NewVec3(1, 1, 1)
debugDepthMat *materials.Material
debugDrawDepthBuffer bool
skyboxCmap assets.Cubemap
) )
type OurGame struct { type OurGame struct {
@ -62,12 +68,14 @@ type OurGame struct {
} }
type TransformComp struct { type TransformComp struct {
entity.BaseComp
Pos *gglm.Vec3 Pos *gglm.Vec3
Rot *gglm.Quat Rot *gglm.Quat
Scale *gglm.Vec3 Scale *gglm.Vec3
} }
func (t TransformComp) Name() string { func (t *TransformComp) Name() string {
return "Transform Component" return "Transform Component"
} }
@ -77,23 +85,15 @@ func Test() {
e1 := lvl.Registry.NewEntity() e1 := lvl.Registry.NewEntity()
trComp := entity.GetComp[*TransformComp](e1) trComp := entity.GetComp[*TransformComp](e1)
fmt.Println("Got comp 1:", trComp) fmt.Println("Get comp before adding any:", trComp)
e1.Comps = append(e1.Comps, &TransformComp{ entity.AddComp(e1, &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),
}, &TransformComp{
Pos: gglm.NewVec3(0, 0, 0),
Rot: gglm.NewQuatEulerXYZ(0, 0, 0),
Scale: gglm.NewVec3(1, 1, 1),
}) })
trComp = entity.GetComp[*TransformComp](e1) trComp = entity.GetComp[*TransformComp](e1)
fmt.Println("Got comp 2:", trComp) fmt.Println("Get transform comp:", trComp)
trComps := entity.GetAllCompOfType[*TransformComp](e1)
fmt.Printf("Got comp 3: %+v, %+v\n", trComps[0], trComps[1])
fmt.Printf("Entity: %+v\n", e1) fmt.Printf("Entity: %+v\n", e1)
fmt.Printf("Entity: %+v\n", lvl.Registry.NewEntity()) fmt.Printf("Entity: %+v\n", lvl.Registry.NewEntity())
@ -142,38 +142,60 @@ func (g *OurGame) handleWindowEvents(e sdl.Event) {
cam.Update() cam.Update()
simpleMat.SetUnifMat4("projMat", &cam.ProjMat) simpleMat.SetUnifMat4("projMat", &cam.ProjMat)
debugDepthMat.SetUnifMat4("projMat", &cam.ProjMat)
} }
} }
} }
func (g *OurGame) Init() { func (g *OurGame) Init() {
var err error
//Create materials //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 //Load meshes
var err error cubeMesh, err = meshes.NewMesh("Cube", "./res/models/tex-cube.fbx", 0)
cubeMesh, err = meshes.NewMesh("Cube", "./res/models/tex-cube.fbx", asig.PostProcess(0))
if err != nil { 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 //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 { if err != nil {
logging.ErrLog.Fatalln("Failed to load texture. Err: ", err) 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 simpleMat.DiffuseTex = tex.TexID
//Movement, scale and rotation //Movement, scale and rotation
translationMat := gglm.NewTranslationMat(gglm.NewVec3(0, 0, 0)) translationMat := gglm.NewTranslationMat(gglm.NewVec3(0, 0, 0))
scaleMat := gglm.NewScaleMat(gglm.NewVec3(0.25, 0.25, 0.25)) scaleMat := gglm.NewScaleMat(gglm.NewVec3(1, 1, 1))
rotMat := gglm.NewRotMat(gglm.NewQuatEuler(gglm.NewVec3(0, 0, 0).AsRad())) rotMat := gglm.NewRotMat(gglm.NewQuatEuler(gglm.NewVec3(-90, -90, 0).AsRad()))
cubeModelMat.Mul(translationMat.Mul(rotMat.Mul(scaleMat))) cubeModelMat.Mul(translationMat.Mul(rotMat.Mul(scaleMat)))
simpleMat.SetUnifMat4("modelMat", &cubeModelMat.Mat4)
// Camera // Camera
winWidth, winHeight := g.Win.SDLWin.GetSize() winWidth, winHeight := g.Win.SDLWin.GetSize()
@ -181,11 +203,12 @@ func (g *OurGame) Init() {
gglm.NewVec3(0, 0, 10), gglm.NewVec3(0, 0, 10),
gglm.NewVec3(0, 0, -1), gglm.NewVec3(0, 0, -1),
gglm.NewVec3(0, 1, 0), gglm.NewVec3(0, 1, 0),
0.1, 20, 0.1, 200,
45*gglm.Deg2Rad, 45*gglm.Deg2Rad,
float32(winWidth)/float32(winHeight), float32(winWidth)/float32(winHeight),
) )
simpleMat.SetUnifMat4("projMat", &cam.ProjMat) simpleMat.SetUnifMat4("projMat", &cam.ProjMat)
debugDepthMat.SetUnifMat4("projMat", &cam.ProjMat)
updateViewMat() updateViewMat()
@ -194,21 +217,6 @@ func (g *OurGame) Init() {
simpleMat.SetUnifVec3("lightColor1", lightColor1) 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() { func (g *OurGame) Update() {
if input.IsQuitClicked() || input.KeyClicked(sdl.K_ESCAPE) { if input.IsQuitClicked() || input.KeyClicked(sdl.K_ESCAPE) {
@ -221,7 +229,6 @@ func (g *OurGame) Update() {
//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())
simpleMat.SetUnifMat4("modelMat", &cubeModelMat.Mat4)
} }
if imgui.DragFloat3("Cam Pos", &cam.Pos.Data) { if imgui.DragFloat3("Cam Pos", &cam.Pos.Data) {
@ -231,15 +238,25 @@ func (g *OurGame) Update() {
updateViewMat() 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)
}
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())
} }
g.Win.SDLWin.SetTitle(fmt.Sprint("nMage (", timing.GetAvgFPS(), " fps)"))
} }
func (g *OurGame) updateCameraLookAround() { func (g *OurGame) updateCameraLookAround() {
mouseX, mouseY := input.GetMouseMotion() mouseX, mouseY := input.GetMouseMotion()
if mouseX == 0 && mouseY == 0 { if (mouseX == 0 && mouseY == 0) || !input.MouseDown(sdl.BUTTON_RIGHT) {
return return
} }
@ -266,21 +283,26 @@ func (g *OurGame) updateCameraPos() {
update := false update := false
var camSpeedScale float32 = 1.0
if input.KeyDown(sdl.K_LSHIFT) {
camSpeedScale = 2
}
// Forward and backward // Forward and backward
if input.KeyDown(sdl.K_w) { 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 update = true
} else if input.KeyDown(sdl.K_s) { } 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 update = true
} }
// Left and right // Left and right
if input.KeyDown(sdl.K_d) { 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 update = true
} else if input.KeyDown(sdl.K_a) { } 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 update = true
} }
@ -291,18 +313,54 @@ func (g *OurGame) updateCameraPos() {
func (g *OurGame) Render() { func (g *OurGame) Render() {
tempModelMat := cubeModelMat.Clone() matToUse := simpleMat
imgui.Checkbox("Debug depth buffer", &debugDrawDepthBuffer)
rowSize := 100 if debugDrawDepthBuffer {
for y := 0; y < rowSize; y++ { matToUse = debugDepthMat
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))
} }
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() { func (g *OurGame) FrameEnd() {
@ -315,4 +373,5 @@ func (g *OurGame) DeInit() {
func updateViewMat() { func updateViewMat() {
cam.Update() cam.Update()
simpleMat.SetUnifMat4("viewMat", &cam.ViewMat) simpleMat.SetUnifMat4("viewMat", &cam.ViewMat)
debugDepthMat.SetUnifMat4("viewMat", &cam.ViewMat)
} }

View File

@ -10,9 +10,16 @@ import (
"github.com/bloeys/nmage/buffers" "github.com/bloeys/nmage/buffers"
) )
type SubMesh struct {
BaseVertex int32
BaseIndex uint32
IndexCount int32
}
type Mesh struct { type Mesh struct {
Name string Name string
Buf buffers.Buffer Buf buffers.Buffer
SubMeshes []SubMesh
} }
func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh, error) { 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) return nil, errors.New("No meshes found in file: " + modelPath)
} }
mesh := &Mesh{Name: name} mesh := &Mesh{
sceneMesh := scene.Meshes[0] Name: name,
mesh.Buf = buffers.NewBuffer() Buf: buffers.NewBuffer(),
SubMeshes: make([]SubMesh, 0, 1),
if len(sceneMesh.TexCoords[0]) == 0 {
sceneMesh.TexCoords[0] = make([]gglm.Vec3, len(sceneMesh.Vertices))
} }
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 { for i := 0; i < len(scene.Meshes); i++ {
layoutToUse = append(layoutToUse, buffers.Element{ElementType: buffers.DataTypeVec4})
}
mesh.Buf.SetLayout(layoutToUse...)
var values []float32 sceneMesh := scene.Meshes[i]
arrs := []arrToInterleave{{V3s: sceneMesh.Vertices}, {V3s: sceneMesh.Normals}, {V2s: v3sToV2s(sceneMesh.TexCoords[0])}}
if len(sceneMesh.ColorSets) > 0 && len(sceneMesh.ColorSets[0]) > 0 { if len(sceneMesh.TexCoords[0]) == 0 {
arrs = append(arrs, arrToInterleave{V4s: sceneMesh.ColorSets[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...) // fmt.Printf("!!! Vertex count: %d; Submeshes: %+v\n", len(vertexBufData)*4/int(mesh.Buf.Stride), mesh.SubMeshes)
mesh.Buf.SetData(vertexBufData)
mesh.Buf.SetData(values) mesh.Buf.SetIndexBufData(indexBufData)
mesh.Buf.SetIndexBufData(flattenFaces(sceneMesh.Faces))
return mesh, nil return mesh, nil
} }

View File

@ -28,7 +28,10 @@ func (r3d *Rend3DGL) Draw(mesh *meshes.Mesh, trMat *gglm.TrMat, mat *materials.M
} }
mat.SetUnifMat4("modelMat", &trMat.Mat4) 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() { 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 //shader:fragment
#version 410 #version 410
uniform float ambientStrength = 0.1; uniform float ambientStrength = 0;
uniform vec3 ambientLightColor = vec3(1, 1, 1); uniform vec3 ambientLightColor = vec3(1, 1, 1);
uniform vec3 lightPos1; 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