Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ddd8db3cb0 | |||
| 692167ada2 | |||
| 524ef068f0 | |||
| b060dcdbe9 | |||
| e22525e2ee | |||
| ee61373069 | |||
| 1f922b6a47 | |||
| 9d7bdc0196 | |||
| 83922f1908 | |||
| c00f6d97dd | |||
| 3c0f82a735 | |||
| c058b82a92 | |||
| 908e5e96aa | |||
| c83e263476 | |||
| 01f06cce1e | |||
| 20ed804d2a | |||
| 80ce6d60d2 | |||
| c998fc26ce | |||
| 81b515197d | |||
| d703a5270c | |||
| caa76c2a5e | |||
| da50d597f9 | |||
| 9f9744a142 | |||
| b101d54049 | |||
| 41b5aea185 | |||
| 3574318552 | |||
| 05ccf3e158 | |||
| 4f5fd50660 | |||
| aaea27b543 | |||
| 039d09f888 | |||
| 1b83d7f9a7 | |||
| 201d9546b2 | |||
| c1d5033eb0 | |||
| 6f646540f9 | |||
| a99dd304ed | |||
| 4e45995ed0 | |||
| a735e01a77 | |||
| abb45e4c4a | |||
| 78ea3ae747 | |||
| b44b00d7e2 | |||
| 70dccd757e | |||
| d7cd5bfc8d | |||
| 3b8e5c06de | |||
| 8e9dbee002 | |||
| 7b2fb19618 | |||
| 9651f77348 | |||
| a16654107b | |||
| 855cbfaba3 | |||
| b025afe1b4 | |||
| 84cd8c28c8 | |||
| 7d5e3e2d82 | |||
| 23a6689346 | |||
| 36488ead04 | |||
| de77d5464e | |||
| 305982deca | |||
| 653a315631 | |||
| c971324b5a | |||
| b5a2479c16 | |||
| 6f54aecb5f | |||
| 7a25aea6ba | |||
| 3071b52c85 | |||
| 1b858bd4ac | |||
| d550767cb6 | |||
| 271b1c0cea | |||
| 0da031aa57 | |||
| 62194c4cad | |||
| bd79f6e274 | |||
| ac0ca8ee39 | |||
| 35ff496a9a | |||
| 52b77e017e | |||
| b85056dd31 | |||
| e5ea6f986f | |||
| c4853792a5 | |||
| 8cf9be2830 | |||
| 71acc2e9ab | |||
| 2690014fc5 | |||
| fe2aef6b6d |
@ -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
|
||||||
|
uses: actions/setup-go@v3
|
||||||
with:
|
with:
|
||||||
go-version: '^1.17'
|
go-version: '>=1.22'
|
||||||
|
|
||||||
- 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 .
|
||||||
@ -1,6 +1,6 @@
|
|||||||
# nMage
|
# nMage
|
||||||
|
|
||||||
[](https://github.com/bloeys/nmage/actions/workflows/run-nmage.yml)
|
[](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).
|
||||||
|
|
||||||
|
|||||||
13
assert/assert.go
Executable file
@ -0,0 +1,13 @@
|
|||||||
|
package assert
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bloeys/nmage/consts"
|
||||||
|
"github.com/bloeys/nmage/logging"
|
||||||
|
)
|
||||||
|
|
||||||
|
func T(check bool, msg string, args ...any) {
|
||||||
|
|
||||||
|
if consts.Debug && !check {
|
||||||
|
logging.ErrLog.Panicf("Assert failed: "+msg, args...)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +0,0 @@
|
|||||||
package asserts
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bloeys/nmage/consts"
|
|
||||||
"github.com/bloeys/nmage/logging"
|
|
||||||
)
|
|
||||||
|
|
||||||
func T(check bool, msg string) {
|
|
||||||
if consts.Debug && !check {
|
|
||||||
logging.ErrLog.Panicln("Assert failed:", msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,18 +1,23 @@
|
|||||||
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) {
|
||||||
|
|
||||||
if _, ok := TexturePaths[t.Path]; ok {
|
if t.Path != "" {
|
||||||
|
if _, ok := TexturePaths[t.Path]; ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
println("Loaded texture from path:", t.Path)
|
||||||
|
Textures[t.TexID] = t
|
||||||
|
TexturePaths[t.Path] = t.TexID
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
println("Loaded texture:", t.Path)
|
println("Loaded in-mem texture with ID:", t.TexID)
|
||||||
Textures[t.TexID] = t
|
|
||||||
TexturePaths[t.Path] = t.TexID
|
TexturePaths[t.Path] = t.TexID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,12 +2,18 @@ package assets
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"image/color"
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"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"
|
||||||
|
"github.com/mandykoh/prism"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ColorFormat int
|
type ColorFormat int
|
||||||
@ -17,10 +23,20 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Texture struct {
|
type Texture struct {
|
||||||
Path string
|
// Path only exists for textures loaded from disk
|
||||||
TexID uint32
|
Path string
|
||||||
Width int32
|
|
||||||
|
TexID uint32
|
||||||
|
|
||||||
|
// Width is the width of the texture in pixels (pixels per row).
|
||||||
|
// Note that the number of bytes constituting a row is MORE than this (e.g. for RGBA8, bytesPerRow=width*4, since we have 4 bytes per pixel)
|
||||||
|
Width int32
|
||||||
|
|
||||||
|
// Height is the height of the texture in pixels (pixels per column).
|
||||||
|
// Note that the number of bytes constituting a column is MORE than this (e.g. for RGBA8, bytesPerColumn=height*4, since we have 4 bytes per pixel)
|
||||||
Height int32
|
Height int32
|
||||||
|
|
||||||
|
// Pixels usually stored in RGBA format
|
||||||
Pixels []byte
|
Pixels []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,9 +44,22 @@ type TextureLoadOptions struct {
|
|||||||
TryLoadFromCache bool
|
TryLoadFromCache bool
|
||||||
WriteToCache bool
|
WriteToCache bool
|
||||||
GenMipMaps bool
|
GenMipMaps bool
|
||||||
|
KeepPixelsInMem bool
|
||||||
|
NoSrgba bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadPNGTexture(file string, loadOptions *TextureLoadOptions) (Texture, error) {
|
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) {
|
||||||
|
|
||||||
if loadOptions == nil {
|
if loadOptions == nil {
|
||||||
loadOptions = &TextureLoadOptions{}
|
loadOptions = &TextureLoadOptions{}
|
||||||
@ -48,34 +77,20 @@ func LoadPNGTexture(file string, loadOptions *TextureLoadOptions) (Texture, erro
|
|||||||
return Texture{}, err
|
return Texture{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
img, err := png.Decode(bytes.NewReader(fileBytes))
|
bytesReader := bytes.NewReader(fileBytes)
|
||||||
|
img, err := png.Decode(bytesReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Texture{}, err
|
return Texture{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
|
||||||
tex := Texture{
|
tex := Texture{
|
||||||
Path: file,
|
Path: file,
|
||||||
Width: int32(img.Bounds().Dx()),
|
Pixels: nrgbaImg.Pix,
|
||||||
Height: int32(img.Bounds().Dy()),
|
Width: int32(nrgbaImg.Bounds().Dx()),
|
||||||
Pixels: make([]byte, img.Bounds().Dx()*img.Bounds().Dy()*4),
|
Height: int32(nrgbaImg.Bounds().Dy()),
|
||||||
}
|
|
||||||
|
|
||||||
//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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
flipImgPixelsVertically(tex.Pixels, int(tex.Width), int(tex.Height), 4)
|
||||||
|
|
||||||
//Prepare opengl stuff
|
//Prepare opengl stuff
|
||||||
gl.GenTextures(1, &tex.TexID)
|
gl.GenTextures(1, &tex.TexID)
|
||||||
@ -88,7 +103,12 @@ func LoadPNGTexture(file string, loadOptions *TextureLoadOptions) (Texture, erro
|
|||||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||||
|
|
||||||
// load and generate the texture
|
// load and generate the texture
|
||||||
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
|
internalFormat := int32(gl.SRGB_ALPHA)
|
||||||
|
if loadOptions.NoSrgba {
|
||||||
|
internalFormat = gl.RGBA8
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.TexImage2D(gl.TEXTURE_2D, 0, internalFormat, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
|
||||||
|
|
||||||
if loadOptions.GenMipMaps {
|
if loadOptions.GenMipMaps {
|
||||||
gl.GenerateMipmap(tex.TexID)
|
gl.GenerateMipmap(tex.TexID)
|
||||||
@ -98,5 +118,208 @@ func LoadPNGTexture(file string, loadOptions *TextureLoadOptions) (Texture, erro
|
|||||||
AddTextureToCache(tex)
|
AddTextureToCache(tex)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !loadOptions.KeepPixelsInMem {
|
||||||
|
tex.Pixels = nil
|
||||||
|
}
|
||||||
|
|
||||||
return tex, nil
|
return tex, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LoadTextureInMemPngImg(img image.Image, loadOptions *TextureLoadOptions) (Texture, error) {
|
||||||
|
|
||||||
|
if loadOptions == nil {
|
||||||
|
loadOptions = &TextureLoadOptions{}
|
||||||
|
}
|
||||||
|
|
||||||
|
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
|
||||||
|
tex := Texture{
|
||||||
|
Path: "",
|
||||||
|
Pixels: nrgbaImg.Pix,
|
||||||
|
Height: int32(nrgbaImg.Bounds().Dy()),
|
||||||
|
Width: int32(nrgbaImg.Bounds().Dx()),
|
||||||
|
}
|
||||||
|
flipImgPixelsVertically(tex.Pixels, int(tex.Width), int(tex.Height), 4)
|
||||||
|
|
||||||
|
//Prepare opengl stuff
|
||||||
|
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
|
||||||
|
internalFormat := int32(gl.SRGB_ALPHA)
|
||||||
|
if loadOptions.NoSrgba {
|
||||||
|
internalFormat = gl.RGBA8
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.TexImage2D(gl.TEXTURE_2D, 0, internalFormat, 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 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
|
||||||
|
}
|
||||||
|
|
||||||
|
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
|
||||||
|
tex := Texture{
|
||||||
|
Path: file,
|
||||||
|
Pixels: nrgbaImg.Pix,
|
||||||
|
Height: int32(nrgbaImg.Bounds().Dy()),
|
||||||
|
Width: int32(nrgbaImg.Bounds().Dx()),
|
||||||
|
}
|
||||||
|
flipImgPixelsVertically(tex.Pixels, int(tex.Width), int(tex.Height), 4)
|
||||||
|
|
||||||
|
//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
|
||||||
|
internalFormat := int32(gl.SRGB_ALPHA)
|
||||||
|
if loadOptions.NoSrgba {
|
||||||
|
internalFormat = gl.RGBA8
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.TexImage2D(gl.TEXTURE_2D, 0, internalFormat, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadCubemapTextures only supports the 'TextureIsSrgba' option
|
||||||
|
func LoadCubemapTextures(rightTex, leftTex, topTex, botTex, frontTex, backTex string, loadOptions *TextureLoadOptions) (Cubemap, error) {
|
||||||
|
|
||||||
|
if loadOptions == nil {
|
||||||
|
loadOptions = &TextureLoadOptions{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var imgDecoder func(r io.Reader) (image.Image, error)
|
||||||
|
ext := strings.ToLower(path.Ext(rightTex))
|
||||||
|
if ext == ".jpg" || ext == ".jpeg" {
|
||||||
|
imgDecoder = jpeg.Decode
|
||||||
|
} else if ext == ".png" {
|
||||||
|
imgDecoder = png.Decode
|
||||||
|
} 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
|
||||||
|
}
|
||||||
|
|
||||||
|
nrgbaImg := prism.ConvertImageToNRGBA(img, 2)
|
||||||
|
height := int32(nrgbaImg.Bounds().Dy())
|
||||||
|
width := int32(nrgbaImg.Bounds().Dx())
|
||||||
|
|
||||||
|
internalFormat := int32(gl.SRGB_ALPHA)
|
||||||
|
if loadOptions.NoSrgba {
|
||||||
|
internalFormat = gl.RGBA8
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.TexImage2D(uint32(gl.TEXTURE_CUBE_MAP_POSITIVE_X)+i, 0, internalFormat, int32(width), int32(height), 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&nrgbaImg.Pix[0]))
|
||||||
|
}
|
||||||
|
|
||||||
|
// set the texture wrapping/filtering options (on the currently bound texture object)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func flipImgPixelsVertically(bytes []byte, width, height, bytesPerPixel int) {
|
||||||
|
|
||||||
|
// Flip the image vertically such that (e.g. in an image of 10 rows) rows 0<->9, 1<->8, 2<->7 etc are swapped.
|
||||||
|
// We do this because images are usually stored top-left to bottom-right, while opengl stores textures bottom-left to top-right, so if we don't swap
|
||||||
|
// rows textures will appear inverted
|
||||||
|
widthInBytes := width * bytesPerPixel
|
||||||
|
rowData := make([]byte, width*bytesPerPixel)
|
||||||
|
for rowNum := 0; rowNum < height/2; rowNum++ {
|
||||||
|
|
||||||
|
upperRowStartIndex := rowNum * widthInBytes
|
||||||
|
lowerRowStartIndex := (height - rowNum - 1) * widthInBytes
|
||||||
|
copy(rowData, bytes[upperRowStartIndex:upperRowStartIndex+widthInBytes])
|
||||||
|
copy(bytes[upperRowStartIndex:upperRowStartIndex+widthInBytes], bytes[lowerRowStartIndex:lowerRowStartIndex+widthInBytes])
|
||||||
|
copy(bytes[lowerRowStartIndex:lowerRowStartIndex+widthInBytes], rowData)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ package buffers
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/bloeys/nmage/asserts"
|
"github.com/bloeys/nmage/assert"
|
||||||
"github.com/go-gl/gl/v4.1-core/gl"
|
"github.com/go-gl/gl/v4.1-core/gl"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,6 +28,6 @@ func (b BufUsage) ToGL() uint32 {
|
|||||||
return gl.STREAM_DRAW
|
return gl.STREAM_DRAW
|
||||||
}
|
}
|
||||||
|
|
||||||
asserts.T(false, fmt.Sprintf("Unexpected BufUsage value '%v'", b))
|
assert.T(false, fmt.Sprintf("Unexpected BufUsage value '%v'", b))
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,105 +0,0 @@
|
|||||||
package buffers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/bloeys/nmage/logging"
|
|
||||||
"github.com/go-gl/gl/v4.1-core/gl"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Buffer struct {
|
|
||||||
VAOID uint32
|
|
||||||
//BufID is the ID of the VBO
|
|
||||||
BufID uint32
|
|
||||||
//IndexBufID is the ID of the index/element buffer
|
|
||||||
IndexBufID uint32
|
|
||||||
IndexBufCount int32
|
|
||||||
Stride int32
|
|
||||||
|
|
||||||
layout []Element
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Buffer) Bind() {
|
|
||||||
gl.BindVertexArray(b.VAOID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Buffer) UnBind() {
|
|
||||||
gl.BindVertexArray(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Buffer) SetData(values []float32) {
|
|
||||||
|
|
||||||
gl.BindVertexArray(b.VAOID)
|
|
||||||
gl.BindBuffer(gl.ARRAY_BUFFER, b.BufID)
|
|
||||||
|
|
||||||
gl.BufferData(gl.ARRAY_BUFFER, len(values)*4, gl.Ptr(values), BufUsage_Static.ToGL())
|
|
||||||
|
|
||||||
gl.BindVertexArray(0)
|
|
||||||
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Buffer) SetIndexBufData(values []uint32) {
|
|
||||||
|
|
||||||
b.IndexBufCount = int32(len(values))
|
|
||||||
gl.BindVertexArray(b.VAOID)
|
|
||||||
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, b.IndexBufID)
|
|
||||||
|
|
||||||
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, len(values)*4, gl.Ptr(values), BufUsage_Static.ToGL())
|
|
||||||
|
|
||||||
gl.BindVertexArray(0)
|
|
||||||
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Buffer) GetLayout() []Element {
|
|
||||||
e := make([]Element, len(b.layout))
|
|
||||||
copy(e, b.layout)
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
//SetLayout updates the layout object and the corresponding vertex attributes.
|
|
||||||
//Vertex attributes are also enabled.
|
|
||||||
func (b *Buffer) SetLayout(layout ...Element) {
|
|
||||||
|
|
||||||
b.layout = layout
|
|
||||||
|
|
||||||
b.Stride = 0
|
|
||||||
for i := 0; i < len(b.layout); i++ {
|
|
||||||
|
|
||||||
b.layout[i].Offset = int(b.Stride)
|
|
||||||
b.Stride += b.layout[i].Size()
|
|
||||||
}
|
|
||||||
|
|
||||||
//Set opengl stuff
|
|
||||||
b.Bind()
|
|
||||||
|
|
||||||
//NOTE: VBOs are only bound at 'VertexAttribPointer', not BindBUffer, so we need to bind the buffer and vao here
|
|
||||||
gl.BindBuffer(gl.ARRAY_BUFFER, b.BufID)
|
|
||||||
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
b.UnBind()
|
|
||||||
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewBuffer(layout ...Element) Buffer {
|
|
||||||
|
|
||||||
b := Buffer{}
|
|
||||||
gl.GenVertexArrays(1, &b.VAOID)
|
|
||||||
if b.VAOID == 0 {
|
|
||||||
logging.ErrLog.Println("Failed to create openGL vertex array object")
|
|
||||||
}
|
|
||||||
|
|
||||||
gl.GenBuffers(1, &b.BufID)
|
|
||||||
if b.BufID == 0 {
|
|
||||||
logging.ErrLog.Println("Failed to create openGL buffer")
|
|
||||||
}
|
|
||||||
|
|
||||||
gl.GenBuffers(1, &b.IndexBufID)
|
|
||||||
if b.IndexBufID == 0 {
|
|
||||||
logging.ErrLog.Println("Failed to create openGL buffer")
|
|
||||||
}
|
|
||||||
|
|
||||||
b.SetLayout(layout...)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
@ -3,7 +3,7 @@ package buffers
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/bloeys/nmage/asserts"
|
"github.com/bloeys/nmage/assert"
|
||||||
"github.com/go-gl/gl/v4.1-core/gl"
|
"github.com/go-gl/gl/v4.1-core/gl"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ func (dt ElementType) GLType() uint32 {
|
|||||||
return gl.FLOAT
|
return gl.FLOAT
|
||||||
|
|
||||||
default:
|
default:
|
||||||
asserts.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
|
assert.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ func (dt ElementType) CompSize() int32 {
|
|||||||
return 4
|
return 4
|
||||||
|
|
||||||
default:
|
default:
|
||||||
asserts.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
|
assert.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,7 +92,7 @@ func (dt ElementType) CompCount() int32 {
|
|||||||
return 4
|
return 4
|
||||||
|
|
||||||
default:
|
default:
|
||||||
asserts.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
|
assert.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,7 +116,7 @@ func (dt ElementType) Size() int32 {
|
|||||||
return 4 * 4
|
return 4 * 4
|
||||||
|
|
||||||
default:
|
default:
|
||||||
asserts.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
|
assert.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
295
buffers/framebuffer.go
Executable file
@ -0,0 +1,295 @@
|
|||||||
|
package buffers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bloeys/nmage/logging"
|
||||||
|
"github.com/go-gl/gl/v4.1-core/gl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FramebufferAttachmentType int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
FramebufferAttachmentType_Unknown FramebufferAttachmentType = iota
|
||||||
|
FramebufferAttachmentType_Texture
|
||||||
|
FramebufferAttachmentType_Renderbuffer
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f FramebufferAttachmentType) IsValid() bool {
|
||||||
|
|
||||||
|
switch f {
|
||||||
|
case FramebufferAttachmentType_Texture:
|
||||||
|
fallthrough
|
||||||
|
case FramebufferAttachmentType_Renderbuffer:
|
||||||
|
return true
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FramebufferAttachmentDataFormat int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
FramebufferAttachmentDataFormat_Unknown FramebufferAttachmentDataFormat = iota
|
||||||
|
FramebufferAttachmentDataFormat_R32Int
|
||||||
|
FramebufferAttachmentDataFormat_RGBA8
|
||||||
|
FramebufferAttachmentDataFormat_SRGBA
|
||||||
|
FramebufferAttachmentDataFormat_Depth24Stencil8
|
||||||
|
)
|
||||||
|
|
||||||
|
func (f FramebufferAttachmentDataFormat) IsColorFormat() bool {
|
||||||
|
return f == FramebufferAttachmentDataFormat_R32Int ||
|
||||||
|
f == FramebufferAttachmentDataFormat_RGBA8 ||
|
||||||
|
f == FramebufferAttachmentDataFormat_SRGBA
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FramebufferAttachmentDataFormat) IsDepthFormat() bool {
|
||||||
|
return f == FramebufferAttachmentDataFormat_Depth24Stencil8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FramebufferAttachmentDataFormat) GlInternalFormat() int32 {
|
||||||
|
|
||||||
|
switch f {
|
||||||
|
case FramebufferAttachmentDataFormat_R32Int:
|
||||||
|
return gl.R32I
|
||||||
|
case FramebufferAttachmentDataFormat_RGBA8:
|
||||||
|
return gl.RGB8
|
||||||
|
case FramebufferAttachmentDataFormat_SRGBA:
|
||||||
|
return gl.SRGB_ALPHA
|
||||||
|
case FramebufferAttachmentDataFormat_Depth24Stencil8:
|
||||||
|
return gl.DEPTH24_STENCIL8
|
||||||
|
default:
|
||||||
|
logging.ErrLog.Fatalf("unknown framebuffer attachment data format. Format=%d\n", f)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f FramebufferAttachmentDataFormat) GlFormat() uint32 {
|
||||||
|
|
||||||
|
switch f {
|
||||||
|
case FramebufferAttachmentDataFormat_R32Int:
|
||||||
|
return gl.RED_INTEGER
|
||||||
|
|
||||||
|
case FramebufferAttachmentDataFormat_RGBA8:
|
||||||
|
fallthrough
|
||||||
|
case FramebufferAttachmentDataFormat_SRGBA:
|
||||||
|
return gl.RGBA
|
||||||
|
|
||||||
|
case FramebufferAttachmentDataFormat_Depth24Stencil8:
|
||||||
|
return gl.DEPTH_STENCIL
|
||||||
|
|
||||||
|
default:
|
||||||
|
logging.ErrLog.Fatalf("unknown framebuffer attachment data format. Format=%d\n", f)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FramebufferAttachment struct {
|
||||||
|
Id uint32
|
||||||
|
Type FramebufferAttachmentType
|
||||||
|
Format FramebufferAttachmentDataFormat
|
||||||
|
}
|
||||||
|
|
||||||
|
type Framebuffer struct {
|
||||||
|
Id uint32
|
||||||
|
Attachments []FramebufferAttachment
|
||||||
|
ColorAttachmentsCount uint32
|
||||||
|
Width uint32
|
||||||
|
Height uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fbo *Framebuffer) Bind() {
|
||||||
|
gl.BindFramebuffer(gl.FRAMEBUFFER, fbo.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fbo *Framebuffer) BindWithViewport() {
|
||||||
|
gl.BindFramebuffer(gl.FRAMEBUFFER, fbo.Id)
|
||||||
|
gl.Viewport(0, 0, int32(fbo.Width), int32(fbo.Height))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fbo *Framebuffer) UnBind() {
|
||||||
|
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fbo *Framebuffer) UnBindWithViewport(width, height uint32) {
|
||||||
|
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
|
||||||
|
gl.Viewport(0, 0, int32(width), int32(height))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsComplete returns true if OpenGL reports that the fbo is complete/usable.
|
||||||
|
// Note that this function binds and then unbinds the fbo
|
||||||
|
func (fbo *Framebuffer) IsComplete() bool {
|
||||||
|
fbo.Bind()
|
||||||
|
isComplete := gl.CheckFramebufferStatus(gl.FRAMEBUFFER) == gl.FRAMEBUFFER_COMPLETE
|
||||||
|
fbo.UnBind()
|
||||||
|
return isComplete
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fbo *Framebuffer) HasColorAttachment() bool {
|
||||||
|
return fbo.ColorAttachmentsCount > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fbo *Framebuffer) HasDepthAttachment() bool {
|
||||||
|
|
||||||
|
for i := 0; i < len(fbo.Attachments); i++ {
|
||||||
|
|
||||||
|
a := &fbo.Attachments[i]
|
||||||
|
if a.Format.IsDepthFormat() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fbo *Framebuffer) NewColorAttachment(
|
||||||
|
attachType FramebufferAttachmentType,
|
||||||
|
attachFormat FramebufferAttachmentDataFormat,
|
||||||
|
) {
|
||||||
|
|
||||||
|
if fbo.ColorAttachmentsCount == 8 {
|
||||||
|
logging.ErrLog.Fatalf("failed creating color attachment for framebuffer due it already having %d attached\n", fbo.ColorAttachmentsCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !attachType.IsValid() {
|
||||||
|
logging.ErrLog.Fatalf("failed creating color attachment for framebuffer due to unknown attachment type. Type=%d\n", attachType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !attachFormat.IsColorFormat() {
|
||||||
|
logging.ErrLog.Fatalf("failed creating color attachment for framebuffer due to attachment data format not being a valid color type. Data format=%d\n", attachFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
a := FramebufferAttachment{
|
||||||
|
Type: attachType,
|
||||||
|
Format: attachFormat,
|
||||||
|
}
|
||||||
|
|
||||||
|
fbo.Bind()
|
||||||
|
|
||||||
|
if attachType == FramebufferAttachmentType_Texture {
|
||||||
|
|
||||||
|
// Create texture
|
||||||
|
gl.GenTextures(1, &a.Id)
|
||||||
|
if a.Id == 0 {
|
||||||
|
logging.ErrLog.Fatalf("failed to generate texture for framebuffer. GlError=%d\n", gl.GetError())
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.BindTexture(gl.TEXTURE_2D, a.Id)
|
||||||
|
gl.TexImage2D(gl.TEXTURE_2D, 0, attachFormat.GlInternalFormat(), int32(fbo.Width), int32(fbo.Height), 0, attachFormat.GlFormat(), gl.UNSIGNED_BYTE, nil)
|
||||||
|
|
||||||
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
|
||||||
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||||
|
gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||||
|
|
||||||
|
// Attach to fbo
|
||||||
|
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0+fbo.ColorAttachmentsCount, gl.TEXTURE_2D, a.Id, 0)
|
||||||
|
|
||||||
|
} else if attachType == FramebufferAttachmentType_Renderbuffer {
|
||||||
|
|
||||||
|
// Create rbo
|
||||||
|
gl.GenRenderbuffers(1, &a.Id)
|
||||||
|
if a.Id == 0 {
|
||||||
|
logging.ErrLog.Fatalf("failed to generate render buffer for framebuffer. GlError=%d\n", gl.GetError())
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.BindRenderbuffer(gl.RENDERBUFFER, a.Id)
|
||||||
|
gl.RenderbufferStorage(gl.RENDERBUFFER, uint32(attachFormat.GlInternalFormat()), int32(fbo.Width), int32(fbo.Height))
|
||||||
|
gl.BindRenderbuffer(gl.RENDERBUFFER, 0)
|
||||||
|
|
||||||
|
// Attach to fbo
|
||||||
|
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0+fbo.ColorAttachmentsCount, gl.RENDERBUFFER, a.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fbo.UnBind()
|
||||||
|
fbo.ColorAttachmentsCount++
|
||||||
|
fbo.Attachments = append(fbo.Attachments, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fbo *Framebuffer) NewDepthStencilAttachment(
|
||||||
|
attachType FramebufferAttachmentType,
|
||||||
|
attachFormat FramebufferAttachmentDataFormat,
|
||||||
|
) {
|
||||||
|
|
||||||
|
if fbo.HasDepthAttachment() {
|
||||||
|
logging.ErrLog.Fatalf("failed creating depth-stencil attachment for framebuffer because a depth-stencil attachment already exists\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !attachType.IsValid() {
|
||||||
|
logging.ErrLog.Fatalf("failed creating depth-stencil attachment for framebuffer due to unknown attachment type. Type=%d\n", attachType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !attachFormat.IsDepthFormat() {
|
||||||
|
logging.ErrLog.Fatalf("failed creating depth-stencil attachment for framebuffer due to attachment data format not being a valid depth-stencil type. Data format=%d\n", attachFormat)
|
||||||
|
}
|
||||||
|
|
||||||
|
a := FramebufferAttachment{
|
||||||
|
Type: attachType,
|
||||||
|
Format: attachFormat,
|
||||||
|
}
|
||||||
|
|
||||||
|
fbo.Bind()
|
||||||
|
|
||||||
|
if attachType == FramebufferAttachmentType_Texture {
|
||||||
|
|
||||||
|
// Create texture
|
||||||
|
gl.GenTextures(1, &a.Id)
|
||||||
|
if a.Id == 0 {
|
||||||
|
logging.ErrLog.Fatalf("failed to generate texture for framebuffer. GlError=%d\n", gl.GetError())
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.BindTexture(gl.TEXTURE_2D, a.Id)
|
||||||
|
gl.TexImage2D(gl.TEXTURE_2D, 0, attachFormat.GlInternalFormat(), int32(fbo.Width), int32(fbo.Height), 0, attachFormat.GlFormat(), gl.UNSIGNED_INT_24_8, nil)
|
||||||
|
|
||||||
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
||||||
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||||
|
gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||||
|
|
||||||
|
// Attach to fbo
|
||||||
|
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.TEXTURE_2D, a.Id, 0)
|
||||||
|
|
||||||
|
} else if attachType == FramebufferAttachmentType_Renderbuffer {
|
||||||
|
|
||||||
|
// Create rbo
|
||||||
|
gl.GenRenderbuffers(1, &a.Id)
|
||||||
|
if a.Id == 0 {
|
||||||
|
logging.ErrLog.Fatalf("failed to generate render buffer for framebuffer. GlError=%d\n", gl.GetError())
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.BindRenderbuffer(gl.RENDERBUFFER, a.Id)
|
||||||
|
gl.RenderbufferStorage(gl.RENDERBUFFER, uint32(attachFormat.GlInternalFormat()), int32(fbo.Width), int32(fbo.Height))
|
||||||
|
gl.BindRenderbuffer(gl.RENDERBUFFER, 0)
|
||||||
|
|
||||||
|
// Attach to fbo
|
||||||
|
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, a.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fbo.UnBind()
|
||||||
|
fbo.Attachments = append(fbo.Attachments, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fbo *Framebuffer) Delete() {
|
||||||
|
|
||||||
|
if fbo.Id == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.DeleteFramebuffers(1, &fbo.Id)
|
||||||
|
fbo.Id = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFramebuffer(width, height uint32) Framebuffer {
|
||||||
|
|
||||||
|
// It is allowed to have attachments of differnt sizes in one FBO,
|
||||||
|
// but that complicates things (e.g. which size to use for gl.viewport) and I don't see much use
|
||||||
|
// for it now, so we will have all attachments share size
|
||||||
|
fbo := Framebuffer{
|
||||||
|
Width: width,
|
||||||
|
Height: height,
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.GenFramebuffers(1, &fbo.Id)
|
||||||
|
if fbo.Id == 0 {
|
||||||
|
logging.ErrLog.Fatalf("failed to generate framebuffer. GlError=%d\n", gl.GetError())
|
||||||
|
}
|
||||||
|
|
||||||
|
return fbo
|
||||||
|
}
|
||||||
46
buffers/index_buffer.go
Executable file
@ -0,0 +1,46 @@
|
|||||||
|
package buffers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bloeys/nmage/logging"
|
||||||
|
"github.com/go-gl/gl/v4.1-core/gl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IndexBuffer struct {
|
||||||
|
Id uint32
|
||||||
|
// IndexBufCount is the number of elements in the index buffer. Updated in IndexBuffer.SetData
|
||||||
|
IndexBufCount int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ib *IndexBuffer) Bind() {
|
||||||
|
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, ib.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ib *IndexBuffer) UnBind() {
|
||||||
|
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ib *IndexBuffer) SetData(values []uint32) {
|
||||||
|
|
||||||
|
ib.Bind()
|
||||||
|
|
||||||
|
sizeInBytes := len(values) * 4
|
||||||
|
ib.IndexBufCount = int32(len(values))
|
||||||
|
|
||||||
|
if sizeInBytes == 0 {
|
||||||
|
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, 0, gl.Ptr(nil), BufUsage_Static.ToGL())
|
||||||
|
} else {
|
||||||
|
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, sizeInBytes, gl.Ptr(&values[0]), BufUsage_Static.ToGL())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIndexBuffer() IndexBuffer {
|
||||||
|
|
||||||
|
ib := IndexBuffer{}
|
||||||
|
|
||||||
|
gl.GenBuffers(1, &ib.Id)
|
||||||
|
if ib.Id == 0 {
|
||||||
|
logging.ErrLog.Println("Failed to create OpenGL buffer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return ib
|
||||||
|
}
|
||||||
54
buffers/vertex_array.go
Executable file
@ -0,0 +1,54 @@
|
|||||||
|
package buffers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bloeys/nmage/logging"
|
||||||
|
"github.com/go-gl/gl/v4.1-core/gl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VertexArray struct {
|
||||||
|
Id uint32
|
||||||
|
Vbos []VertexBuffer
|
||||||
|
IndexBuffer IndexBuffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (va *VertexArray) Bind() {
|
||||||
|
gl.BindVertexArray(va.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (va *VertexArray) UnBind() {
|
||||||
|
gl.BindVertexArray(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (va *VertexArray) AddVertexBuffer(vbo VertexBuffer) {
|
||||||
|
|
||||||
|
// NOTE: VBOs are only bound at 'VertexAttribPointer' (and related) calls
|
||||||
|
|
||||||
|
va.Bind()
|
||||||
|
vbo.Bind()
|
||||||
|
|
||||||
|
for i := 0; i < len(vbo.layout); i++ {
|
||||||
|
|
||||||
|
l := &vbo.layout[i]
|
||||||
|
|
||||||
|
gl.EnableVertexAttribArray(uint32(i))
|
||||||
|
gl.VertexAttribPointerWithOffset(uint32(i), l.ElementType.CompCount(), l.ElementType.GLType(), false, vbo.Stride, uintptr(l.Offset))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (va *VertexArray) SetIndexBuffer(ib IndexBuffer) {
|
||||||
|
va.Bind()
|
||||||
|
ib.Bind()
|
||||||
|
va.IndexBuffer = ib
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVertexArray() VertexArray {
|
||||||
|
|
||||||
|
vao := VertexArray{}
|
||||||
|
|
||||||
|
gl.GenVertexArrays(1, &vao.Id)
|
||||||
|
if vao.Id == 0 {
|
||||||
|
logging.ErrLog.Println("Failed to create OpenGL vertex array object")
|
||||||
|
}
|
||||||
|
|
||||||
|
return vao
|
||||||
|
}
|
||||||
63
buffers/vertex_buffer.go
Executable file
@ -0,0 +1,63 @@
|
|||||||
|
package buffers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bloeys/nmage/logging"
|
||||||
|
"github.com/go-gl/gl/v4.1-core/gl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type VertexBuffer struct {
|
||||||
|
Id uint32
|
||||||
|
Stride int32
|
||||||
|
layout []Element
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vb *VertexBuffer) Bind() {
|
||||||
|
gl.BindBuffer(gl.ARRAY_BUFFER, vb.Id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vb *VertexBuffer) UnBind() {
|
||||||
|
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vb *VertexBuffer) SetData(values []float32, usage BufUsage) {
|
||||||
|
|
||||||
|
vb.Bind()
|
||||||
|
|
||||||
|
sizeInBytes := len(values) * 4
|
||||||
|
if sizeInBytes == 0 {
|
||||||
|
gl.BufferData(gl.ARRAY_BUFFER, 0, gl.Ptr(nil), usage.ToGL())
|
||||||
|
} else {
|
||||||
|
gl.BufferData(gl.ARRAY_BUFFER, sizeInBytes, gl.Ptr(&values[0]), usage.ToGL())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vb *VertexBuffer) GetLayout() []Element {
|
||||||
|
e := make([]Element, len(vb.layout))
|
||||||
|
copy(e, vb.layout)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (vb *VertexBuffer) SetLayout(layout ...Element) {
|
||||||
|
|
||||||
|
vb.Stride = 0
|
||||||
|
vb.layout = layout
|
||||||
|
|
||||||
|
for i := 0; i < len(vb.layout); i++ {
|
||||||
|
|
||||||
|
vb.layout[i].Offset = int(vb.Stride)
|
||||||
|
vb.Stride += vb.layout[i].Size()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVertexBuffer(layout ...Element) VertexBuffer {
|
||||||
|
|
||||||
|
vb := VertexBuffer{}
|
||||||
|
|
||||||
|
gl.GenBuffers(1, &vb.Id)
|
||||||
|
if vb.Id == 0 {
|
||||||
|
logging.ErrLog.Println("Failed to create OpenGL buffer")
|
||||||
|
}
|
||||||
|
|
||||||
|
vb.SetLayout(layout...)
|
||||||
|
return vb
|
||||||
|
}
|
||||||
100
camera/camera.go
Executable file
@ -0,0 +1,100 @@
|
|||||||
|
package camera
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bloeys/gglm/gglm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Type int32
|
||||||
|
|
||||||
|
const (
|
||||||
|
Type_Unknown Type = iota
|
||||||
|
Type_Perspective
|
||||||
|
Type_Orthographic
|
||||||
|
)
|
||||||
|
|
||||||
|
type Camera struct {
|
||||||
|
Type Type
|
||||||
|
|
||||||
|
Pos gglm.Vec3
|
||||||
|
Forward gglm.Vec3
|
||||||
|
WorldUp gglm.Vec3
|
||||||
|
|
||||||
|
NearClip float32
|
||||||
|
FarClip float32
|
||||||
|
|
||||||
|
// Perspective data
|
||||||
|
Fov float32
|
||||||
|
AspectRatio float32
|
||||||
|
|
||||||
|
// Ortho data
|
||||||
|
Left, Right, Top, Bottom float32
|
||||||
|
|
||||||
|
// Matrices
|
||||||
|
ViewMat gglm.Mat4
|
||||||
|
ProjMat gglm.Mat4
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update recalculates view matrix and projection matrix.
|
||||||
|
// Should be called whenever a camera parameter changes
|
||||||
|
func (c *Camera) Update() {
|
||||||
|
|
||||||
|
c.ViewMat = gglm.LookAtRH(&c.Pos, c.Pos.Clone().Add(&c.Forward), &c.WorldUp).Mat4
|
||||||
|
|
||||||
|
if c.Type == Type_Perspective {
|
||||||
|
c.ProjMat = *gglm.Perspective(c.Fov, c.AspectRatio, c.NearClip, c.FarClip)
|
||||||
|
} else {
|
||||||
|
c.ProjMat = gglm.Ortho(c.Left, c.Right, c.Top, c.Bottom, c.NearClip, c.FarClip).Mat4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateRotation calculates a new forward vector and then calls camera.Update()
|
||||||
|
func (c *Camera) UpdateRotation(pitch, yaw float32) {
|
||||||
|
|
||||||
|
dir := gglm.NewVec3(
|
||||||
|
gglm.Cos32(yaw)*gglm.Cos32(pitch),
|
||||||
|
gglm.Sin32(pitch),
|
||||||
|
gglm.Sin32(yaw)*gglm.Cos32(pitch),
|
||||||
|
)
|
||||||
|
c.Forward = *dir.Normalize()
|
||||||
|
c.Update()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPerspective(pos, forward, worldUp *gglm.Vec3, nearClip, farClip, fovRadians, aspectRatio float32) *Camera {
|
||||||
|
|
||||||
|
cam := &Camera{
|
||||||
|
Type: Type_Perspective,
|
||||||
|
Pos: *pos,
|
||||||
|
Forward: *forward,
|
||||||
|
WorldUp: *worldUp,
|
||||||
|
|
||||||
|
NearClip: nearClip,
|
||||||
|
FarClip: farClip,
|
||||||
|
|
||||||
|
Fov: fovRadians,
|
||||||
|
AspectRatio: aspectRatio,
|
||||||
|
}
|
||||||
|
cam.Update()
|
||||||
|
|
||||||
|
return cam
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOrthographic(pos, forward, worldUp *gglm.Vec3, nearClip, farClip, left, right, top, bottom float32) *Camera {
|
||||||
|
|
||||||
|
cam := &Camera{
|
||||||
|
Type: Type_Orthographic,
|
||||||
|
Pos: *pos,
|
||||||
|
Forward: *forward,
|
||||||
|
WorldUp: *worldUp,
|
||||||
|
|
||||||
|
NearClip: nearClip,
|
||||||
|
FarClip: farClip,
|
||||||
|
|
||||||
|
Left: left,
|
||||||
|
Right: right,
|
||||||
|
Top: top,
|
||||||
|
Bottom: bottom,
|
||||||
|
}
|
||||||
|
cam.Update()
|
||||||
|
|
||||||
|
return cam
|
||||||
|
}
|
||||||
147
engine/engine.go
@ -3,17 +3,25 @@ package engine
|
|||||||
import (
|
import (
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/bloeys/nmage/asserts"
|
imgui "github.com/AllenDang/cimgui-go"
|
||||||
|
"github.com/bloeys/nmage/assert"
|
||||||
"github.com/bloeys/nmage/input"
|
"github.com/bloeys/nmage/input"
|
||||||
"github.com/bloeys/nmage/renderer"
|
"github.com/bloeys/nmage/renderer"
|
||||||
"github.com/bloeys/nmage/timing"
|
"github.com/bloeys/nmage/timing"
|
||||||
|
nmageimgui "github.com/bloeys/nmage/ui/imgui"
|
||||||
"github.com/go-gl/gl/v4.1-core/gl"
|
"github.com/go-gl/gl/v4.1-core/gl"
|
||||||
"github.com/inkyblackness/imgui-go/v4"
|
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
isInited = false
|
isInited = false
|
||||||
|
|
||||||
|
isSdlButtonLeftDown = false
|
||||||
|
isSdlButtonMiddleDown = false
|
||||||
|
isSdlButtonRightDown = false
|
||||||
|
|
||||||
|
ImguiRelativeMouseModePosX float32
|
||||||
|
ImguiRelativeMouseModePosY float32
|
||||||
)
|
)
|
||||||
|
|
||||||
type Window struct {
|
type Window struct {
|
||||||
@ -26,7 +34,24 @@ type Window struct {
|
|||||||
func (w *Window) handleInputs() {
|
func (w *Window) handleInputs() {
|
||||||
|
|
||||||
input.EventLoopStart()
|
input.EventLoopStart()
|
||||||
imIO := imgui.CurrentIO()
|
imIo := imgui.CurrentIO()
|
||||||
|
|
||||||
|
imguiCaptureMouse := imIo.WantCaptureMouse()
|
||||||
|
imguiCaptureKeyboard := imIo.WantCaptureKeyboard()
|
||||||
|
|
||||||
|
// These two are to fix a bug where state isn't cleared
|
||||||
|
// even after imgui captures the keyboard/mouse.
|
||||||
|
//
|
||||||
|
// For example, if player is moving due to key held and then imgui captures the keyboard,
|
||||||
|
// the player keeps moving even when the key is no longer pressed because the input system never
|
||||||
|
// receives the key up event.
|
||||||
|
if imguiCaptureMouse {
|
||||||
|
input.ClearMouseState()
|
||||||
|
}
|
||||||
|
|
||||||
|
if imguiCaptureKeyboard {
|
||||||
|
input.ClearKeyboardState()
|
||||||
|
}
|
||||||
|
|
||||||
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
|
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
|
||||||
|
|
||||||
@ -40,30 +65,64 @@ func (w *Window) handleInputs() {
|
|||||||
|
|
||||||
case *sdl.MouseWheelEvent:
|
case *sdl.MouseWheelEvent:
|
||||||
|
|
||||||
input.HandleMouseWheelEvent(e)
|
if !imguiCaptureMouse {
|
||||||
|
input.HandleMouseWheelEvent(e)
|
||||||
|
}
|
||||||
|
|
||||||
xDelta, yDelta := input.GetMouseWheelMotion()
|
imIo.AddMouseWheelDelta(float32(e.X), float32(e.Y))
|
||||||
imIO.AddMouseWheelDelta(float32(xDelta), float32(yDelta))
|
|
||||||
|
|
||||||
case *sdl.KeyboardEvent:
|
case *sdl.KeyboardEvent:
|
||||||
input.HandleKeyboardEvent(e)
|
|
||||||
|
|
||||||
if e.Type == sdl.KEYDOWN {
|
if !imguiCaptureKeyboard {
|
||||||
imIO.KeyPress(int(e.Keysym.Scancode))
|
input.HandleKeyboardEvent(e)
|
||||||
} else if e.Type == sdl.KEYUP {
|
}
|
||||||
imIO.KeyRelease(int(e.Keysym.Scancode))
|
|
||||||
|
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:
|
case *sdl.TextInputEvent:
|
||||||
imIO.AddInputCharacters(string(e.Text[:]))
|
imIo.AddInputCharactersUTF8(e.GetText())
|
||||||
|
|
||||||
case *sdl.MouseButtonEvent:
|
case *sdl.MouseButtonEvent:
|
||||||
input.HandleMouseBtnEvent(e)
|
|
||||||
|
if !imguiCaptureMouse {
|
||||||
|
input.HandleMouseBtnEvent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
isPressed := e.State == sdl.PRESSED
|
||||||
|
|
||||||
|
if e.Button == sdl.BUTTON_LEFT {
|
||||||
|
isSdlButtonLeftDown = isPressed
|
||||||
|
} else if e.Button == sdl.BUTTON_MIDDLE {
|
||||||
|
isSdlButtonMiddleDown = isPressed
|
||||||
|
} else if e.Button == sdl.BUTTON_RIGHT {
|
||||||
|
isSdlButtonRightDown = isPressed
|
||||||
|
}
|
||||||
|
|
||||||
case *sdl.MouseMotionEvent:
|
case *sdl.MouseMotionEvent:
|
||||||
input.HandleMouseMotionEvent(e)
|
|
||||||
|
if !imguiCaptureMouse {
|
||||||
|
input.HandleMouseMotionEvent(e)
|
||||||
|
}
|
||||||
|
|
||||||
case *sdl.WindowEvent:
|
case *sdl.WindowEvent:
|
||||||
|
|
||||||
if e.Event == sdl.WINDOWEVENT_SIZE_CHANGED {
|
if e.Event == sdl.WINDOWEVENT_SIZE_CHANGED {
|
||||||
w.handleWindowResize()
|
w.handleWindowResize()
|
||||||
}
|
}
|
||||||
@ -73,17 +132,17 @@ func (w *Window) handleInputs() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if sdl.GetRelativeMouseMode() {
|
||||||
|
imIo.SetMousePos(imgui.Vec2{X: ImguiRelativeMouseModePosX, Y: ImguiRelativeMouseModePosY})
|
||||||
|
} else {
|
||||||
|
x, y, _ := sdl.GetMouseState()
|
||||||
|
imIo.SetMousePos(imgui.Vec2{X: float32(x), Y: float32(y)})
|
||||||
|
}
|
||||||
|
|
||||||
// If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame.
|
// If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame.
|
||||||
x, y, _ := sdl.GetMouseState()
|
imIo.SetMouseButtonDown(imgui.MouseButtonLeft, isSdlButtonLeftDown)
|
||||||
imIO.SetMousePosition(imgui.Vec2{X: float32(x), Y: float32(y)})
|
imIo.SetMouseButtonDown(imgui.MouseButtonRight, isSdlButtonRightDown)
|
||||||
|
imIo.SetMouseButtonDown(imgui.MouseButtonMiddle, isSdlButtonMiddleDown)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Window) handleWindowResize() {
|
func (w *Window) handleWindowResize() {
|
||||||
@ -112,7 +171,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
|
||||||
}
|
}
|
||||||
@ -122,15 +181,21 @@ func initSDL() error {
|
|||||||
sdl.GLSetAttribute(sdl.MAJOR_VERSION, 4)
|
sdl.GLSetAttribute(sdl.MAJOR_VERSION, 4)
|
||||||
sdl.GLSetAttribute(sdl.MINOR_VERSION, 1)
|
sdl.GLSetAttribute(sdl.MINOR_VERSION, 1)
|
||||||
|
|
||||||
// R(0-255) G(0-255) B(0-255)
|
|
||||||
sdl.GLSetAttribute(sdl.GL_RED_SIZE, 8)
|
sdl.GLSetAttribute(sdl.GL_RED_SIZE, 8)
|
||||||
sdl.GLSetAttribute(sdl.GL_GREEN_SIZE, 8)
|
sdl.GLSetAttribute(sdl.GL_GREEN_SIZE, 8)
|
||||||
sdl.GLSetAttribute(sdl.GL_BLUE_SIZE, 8)
|
sdl.GLSetAttribute(sdl.GL_BLUE_SIZE, 8)
|
||||||
|
sdl.GLSetAttribute(sdl.GL_ALPHA_SIZE, 8)
|
||||||
|
|
||||||
sdl.GLSetAttribute(sdl.GL_DOUBLEBUFFER, 1)
|
sdl.GLSetAttribute(sdl.GL_DOUBLEBUFFER, 1)
|
||||||
sdl.GLSetAttribute(sdl.GL_DEPTH_SIZE, 24)
|
sdl.GLSetAttribute(sdl.GL_DEPTH_SIZE, 24)
|
||||||
sdl.GLSetAttribute(sdl.GL_STENCIL_SIZE, 8)
|
sdl.GLSetAttribute(sdl.GL_STENCIL_SIZE, 8)
|
||||||
|
|
||||||
|
sdl.GLSetAttribute(sdl.GL_FRAMEBUFFER_SRGB_CAPABLE, 1)
|
||||||
|
|
||||||
|
// Allows us to do MSAA
|
||||||
|
sdl.GLSetAttribute(sdl.GL_MULTISAMPLEBUFFERS, 1)
|
||||||
|
sdl.GLSetAttribute(sdl.GL_MULTISAMPLESAMPLES, 4)
|
||||||
|
|
||||||
sdl.GLSetAttribute(sdl.GL_CONTEXT_PROFILE_MASK, sdl.GL_CONTEXT_PROFILE_CORE)
|
sdl.GLSetAttribute(sdl.GL_CONTEXT_PROFILE_MASK, sdl.GL_CONTEXT_PROFILE_CORE)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -141,16 +206,12 @@ func CreateOpenGLWindow(title string, x, y, width, height int32, flags WindowFla
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CreateOpenGLWindowCentered(title string, width, height int32, flags WindowFlags, rend renderer.Render) (*Window, error) {
|
func CreateOpenGLWindowCentered(title string, width, height int32, flags WindowFlags, rend renderer.Render) (*Window, error) {
|
||||||
return createWindow(title, -1, -1, width, height, WindowFlags_OPENGL|flags, rend)
|
return createWindow(title, sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, width, height, WindowFlags_OPENGL|flags, rend)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createWindow(title string, x, y, width, height int32, flags WindowFlags, rend renderer.Render) (*Window, error) {
|
func createWindow(title string, x, y, width, height int32, flags WindowFlags, rend renderer.Render) (*Window, error) {
|
||||||
|
|
||||||
asserts.T(isInited, "engine.Init was not called!")
|
assert.T(isInited, "engine.Init() was not called!")
|
||||||
if x == -1 && y == -1 {
|
|
||||||
x = sdl.WINDOWPOS_CENTERED
|
|
||||||
y = sdl.WINDOWPOS_CENTERED
|
|
||||||
}
|
|
||||||
|
|
||||||
sdlWin, err := sdl.CreateWindow(title, x, y, width, height, uint32(flags))
|
sdlWin, err := sdl.CreateWindow(title, x, y, width, height, uint32(flags))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -182,19 +243,30 @@ func initOpenGL() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
gl.Enable(gl.DEPTH_TEST)
|
gl.Enable(gl.DEPTH_TEST)
|
||||||
|
gl.Enable(gl.STENCIL_TEST)
|
||||||
gl.Enable(gl.CULL_FACE)
|
gl.Enable(gl.CULL_FACE)
|
||||||
gl.CullFace(gl.BACK)
|
gl.CullFace(gl.BACK)
|
||||||
gl.FrontFace(gl.CCW)
|
gl.FrontFace(gl.CCW)
|
||||||
|
|
||||||
gl.Enable(gl.BLEND)
|
gl.Enable(gl.BLEND)
|
||||||
|
gl.Enable(gl.MULTISAMPLE)
|
||||||
|
gl.Enable(gl.FRAMEBUFFER_SRGB)
|
||||||
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
|
||||||
|
|
||||||
gl.ClearColor(0, 0, 0, 1)
|
gl.ClearColor(0, 0, 0, 1)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetSrgbFramebuffer(isEnabled bool) {
|
||||||
|
|
||||||
|
if isEnabled {
|
||||||
|
gl.Enable(gl.FRAMEBUFFER_SRGB)
|
||||||
|
} else {
|
||||||
|
gl.Disable(gl.FRAMEBUFFER_SRGB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func SetVSync(enabled bool) {
|
func SetVSync(enabled bool) {
|
||||||
asserts.T(isInited, "engine.Init was not called!")
|
|
||||||
|
|
||||||
if enabled {
|
if enabled {
|
||||||
sdl.GLSetSwapInterval(1)
|
sdl.GLSetSwapInterval(1)
|
||||||
@ -202,3 +274,12 @@ func SetVSync(enabled bool) {
|
|||||||
sdl.GLSetSwapInterval(0)
|
sdl.GLSetSwapInterval(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetMSAA(isEnabled bool) {
|
||||||
|
|
||||||
|
if isEnabled {
|
||||||
|
gl.Enable(gl.MULTISAMPLE)
|
||||||
|
} else {
|
||||||
|
gl.Disable(gl.MULTISAMPLE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
26
entity/base_comp.go
Executable 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
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
7
entity/entity.go
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
import "github.com/bloeys/nmage/registry"
|
||||||
|
|
||||||
|
type Entity interface {
|
||||||
|
GetHandle() registry.Handle
|
||||||
|
}
|
||||||
18
go.mod
@ -1,13 +1,19 @@
|
|||||||
module github.com/bloeys/nmage
|
module github.com/bloeys/nmage
|
||||||
|
|
||||||
go 1.17
|
go 1.22
|
||||||
|
|
||||||
require github.com/veandco/go-sdl2 v0.4.10
|
require github.com/veandco/go-sdl2 v0.4.35
|
||||||
|
|
||||||
require github.com/go-gl/gl v0.0.0-20211025173605-bda47ffaa784
|
require github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bloeys/assimp-go v0.4.2
|
github.com/bloeys/assimp-go v0.4.4
|
||||||
github.com/bloeys/gglm v0.41.10
|
github.com/bloeys/gglm v0.43.0
|
||||||
github.com/inkyblackness/imgui-go/v4 v4.3.0
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2
|
||||||
|
github.com/mandykoh/prism v0.35.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require github.com/mandykoh/go-parallel v0.1.0 // indirect
|
||||||
|
|||||||
59
go.sum
@ -1,19 +1,40 @@
|
|||||||
github.com/bloeys/assimp-go v0.4.2 h1:ArVK74BCFcTO/rCGj2NgZG9xtbjnJdEn5npIeJx1Z04=
|
github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2 h1:3HA/5qD8Rimxz/y1sLyVaM7ws1dzjXzMt4hOBiwHggo=
|
||||||
github.com/bloeys/assimp-go v0.4.2/go.mod h1:my3yRxT7CfOztmvi+0svmwbaqw0KFrxaHxncoyaEIP0=
|
github.com/AllenDang/cimgui-go v0.0.0-20230720025235-f2ff398a66b2/go.mod h1:iNfbIyOBN8k3XScMxULbrwYbPsXEAUD0Jb6UwrspQb8=
|
||||||
github.com/bloeys/gglm v0.3.1 h1:Sy9upW7SBsBfDXrSmEhid3aQ+7J7itej+upwcxOnPMQ=
|
github.com/bloeys/assimp-go v0.4.4 h1:Yn5e/RpE0Oes0YMBy8O7KkwAO4R/RpgrZPJCt08dVIU=
|
||||||
github.com/bloeys/gglm v0.3.1/go.mod h1:qwJQ0WzV191wAMwlGicbfbChbKoSedMk7gFFX6GnyOk=
|
github.com/bloeys/assimp-go v0.4.4/go.mod h1:my3yRxT7CfOztmvi+0svmwbaqw0KFrxaHxncoyaEIP0=
|
||||||
github.com/bloeys/gglm v0.41.10 h1:R9FMiI+VQVXAI+vDwCB7z9xqzy5VAR1657u8TQTDNKA=
|
github.com/bloeys/gglm v0.43.0 h1:ZpOghR3PHfpkigTDh+FqxLsF0gN8CD6s/bWoei6LyxI=
|
||||||
github.com/bloeys/gglm v0.41.10/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/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk=
|
||||||
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/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
||||||
github.com/go-gl/gl v0.0.0-20211025173605-bda47ffaa784 h1:1Zi56D0LNfvkzM+BdoxKryvUEdyWO7LP8oRT+oSYJW0=
|
github.com/mandykoh/go-parallel v0.1.0 h1:7vJMNMC4dsbgZdkAb2A8tV5ENY1v7VxIO1wzQWZoT8k=
|
||||||
github.com/go-gl/gl v0.0.0-20211025173605-bda47ffaa784/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
github.com/mandykoh/go-parallel v0.1.0/go.mod h1:lkYHqG1JNTaSS6lG+PgFCnyMd2VDy8pH9jN9pY899ig=
|
||||||
github.com/inkyblackness/imgui-go/v4 v4.3.0 h1:iyAzqWXq/dG5+6ckDPhGivtrIo6AywGQMvENKzun04s=
|
github.com/mandykoh/prism v0.35.1 h1:JbQfQarANxSWlgJEpjv+E7DvtrqBaVP1YgJfZPvo6ME=
|
||||||
github.com/inkyblackness/imgui-go/v4 v4.3.0/go.mod h1:g8SAGtOYUP7rYaOB2AsVKCEHmPMDmJKgt4z6d+flhb0=
|
github.com/mandykoh/prism v0.35.1/go.mod h1:3miB3EAJ0IggYl/4eBB5MmawRbyJI1gKDtbrVvk8Q9I=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/veandco/go-sdl2 v0.4.35 h1:NohzsfageDWGtCd9nf7Pc3sokMK/MOK+UA2QMJARWzQ=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/veandco/go-sdl2 v0.4.35/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
github.com/veandco/go-sdl2 v0.4.10 h1:8QoD2bhWl7SbQDflIAUYWfl9Vq+mT8/boJFAUzAScgY=
|
golang.org/x/image v0.5.0 h1:5JMiNunQeQw++mMOz48/ISeNu3Iweh/JaZU8ZLqHRrI=
|
||||||
github.com/veandco/go-sdl2 v0.4.10/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
|
golang.org/x/image v0.5.0/go.mod h1:FVC7BI/5Ym8R25iw5OLsgshdUBbT1h5jZTpA+mvAdZ4=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|||||||
@ -31,8 +31,8 @@ type mouseWheelState struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
keyMap = make(map[sdl.Keycode]*keyState)
|
keyMap = make(map[sdl.Keycode]keyState)
|
||||||
mouseBtnMap = make(map[int]*mouseBtnState)
|
mouseBtnMap = make(map[int]mouseBtnState)
|
||||||
mouseMotion = mouseMotionState{}
|
mouseMotion = mouseMotionState{}
|
||||||
mouseWheel = mouseWheelState{}
|
mouseWheel = mouseWheelState{}
|
||||||
quitRequested bool
|
quitRequested bool
|
||||||
@ -40,15 +40,17 @@ var (
|
|||||||
|
|
||||||
func EventLoopStart() {
|
func EventLoopStart() {
|
||||||
|
|
||||||
for _, v := range keyMap {
|
for k, v := range keyMap {
|
||||||
v.IsPressedThisFrame = false
|
v.IsPressedThisFrame = false
|
||||||
v.IsReleasedThisFrame = false
|
v.IsReleasedThisFrame = false
|
||||||
|
keyMap[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range mouseBtnMap {
|
for k, v := range mouseBtnMap {
|
||||||
v.IsPressedThisFrame = false
|
v.IsPressedThisFrame = false
|
||||||
v.IsReleasedThisFrame = false
|
v.IsReleasedThisFrame = false
|
||||||
v.IsDoubleClicked = false
|
v.IsDoubleClicked = false
|
||||||
|
mouseBtnMap[k] = v
|
||||||
}
|
}
|
||||||
|
|
||||||
mouseMotion.XDelta = 0
|
mouseMotion.XDelta = 0
|
||||||
@ -60,6 +62,16 @@ func EventLoopStart() {
|
|||||||
quitRequested = false
|
quitRequested = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ClearKeyboardState() {
|
||||||
|
clear(keyMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClearMouseState() {
|
||||||
|
clear(mouseBtnMap)
|
||||||
|
mouseMotion = mouseMotionState{}
|
||||||
|
mouseWheel = mouseWheelState{}
|
||||||
|
}
|
||||||
|
|
||||||
func HandleQuitEvent(e *sdl.QuitEvent) {
|
func HandleQuitEvent(e *sdl.QuitEvent) {
|
||||||
quitRequested = true
|
quitRequested = true
|
||||||
}
|
}
|
||||||
@ -70,29 +82,31 @@ func IsQuitClicked() bool {
|
|||||||
|
|
||||||
func HandleKeyboardEvent(e *sdl.KeyboardEvent) {
|
func HandleKeyboardEvent(e *sdl.KeyboardEvent) {
|
||||||
|
|
||||||
ks := keyMap[e.Keysym.Sym]
|
ks, ok := keyMap[e.Keysym.Sym]
|
||||||
if ks == nil {
|
if !ok {
|
||||||
ks = &keyState{Key: e.Keysym.Sym}
|
ks = keyState{Key: e.Keysym.Sym}
|
||||||
keyMap[ks.Key] = ks
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ks.State = int(e.State)
|
ks.State = int(e.State)
|
||||||
ks.IsPressedThisFrame = e.State == sdl.PRESSED && e.Repeat == 0
|
ks.IsPressedThisFrame = e.State == sdl.PRESSED && e.Repeat == 0
|
||||||
ks.IsReleasedThisFrame = e.State == sdl.RELEASED && e.Repeat == 0
|
ks.IsReleasedThisFrame = e.State == sdl.RELEASED && e.Repeat == 0
|
||||||
|
|
||||||
|
keyMap[ks.Key] = ks
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleMouseBtnEvent(e *sdl.MouseButtonEvent) {
|
func HandleMouseBtnEvent(e *sdl.MouseButtonEvent) {
|
||||||
|
|
||||||
mb := mouseBtnMap[int(e.Button)]
|
mb, ok := mouseBtnMap[int(e.Button)]
|
||||||
if mb == nil {
|
if !ok {
|
||||||
mb = &mouseBtnState{Btn: int(e.Button)}
|
mb = mouseBtnState{Btn: int(e.Button)}
|
||||||
mouseBtnMap[int(e.Button)] = mb
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mb.State = int(e.State)
|
mb.State = int(e.State)
|
||||||
mb.IsDoubleClicked = e.Clicks == 2 && e.State == sdl.PRESSED
|
mb.IsDoubleClicked = e.Clicks == 2 && e.State == sdl.PRESSED
|
||||||
mb.IsPressedThisFrame = e.State == sdl.PRESSED
|
mb.IsPressedThisFrame = e.State == sdl.PRESSED
|
||||||
mb.IsReleasedThisFrame = e.State == sdl.RELEASED
|
mb.IsReleasedThisFrame = e.State == sdl.RELEASED
|
||||||
|
|
||||||
|
mouseBtnMap[int(e.Button)] = mb
|
||||||
}
|
}
|
||||||
|
|
||||||
func HandleMouseMotionEvent(e *sdl.MouseMotionEvent) {
|
func HandleMouseMotionEvent(e *sdl.MouseMotionEvent) {
|
||||||
@ -109,12 +123,12 @@ func HandleMouseWheelEvent(e *sdl.MouseWheelEvent) {
|
|||||||
mouseWheel.YDelta = e.Y
|
mouseWheel.YDelta = e.Y
|
||||||
}
|
}
|
||||||
|
|
||||||
//GetMousePos returns the window coordinates of the mouse
|
// GetMousePos returns the window coordinates of the mouse
|
||||||
func GetMousePos() (x, y int32) {
|
func GetMousePos() (x, y int32) {
|
||||||
return mouseMotion.XPos, mouseMotion.YPos
|
return mouseMotion.XPos, mouseMotion.YPos
|
||||||
}
|
}
|
||||||
|
|
||||||
//GetMouseMotion returns how many pixels were moved last frame
|
// GetMouseMotion returns how many pixels were moved last frame
|
||||||
func GetMouseMotion() (xDelta, yDelta int32) {
|
func GetMouseMotion() (xDelta, yDelta int32) {
|
||||||
return mouseMotion.XDelta, mouseMotion.YDelta
|
return mouseMotion.XDelta, mouseMotion.YDelta
|
||||||
}
|
}
|
||||||
@ -141,7 +155,7 @@ func GetMouseWheelMotion() (xDelta, yDelta int32) {
|
|||||||
return mouseWheel.XDelta, mouseWheel.YDelta
|
return mouseWheel.XDelta, mouseWheel.YDelta
|
||||||
}
|
}
|
||||||
|
|
||||||
//GetMouseWheelXNorm returns 1 if mouse wheel xDelta > 0, -1 if xDelta < 0, and 0 otherwise
|
// GetMouseWheelXNorm returns 1 if mouse wheel xDelta > 0, -1 if xDelta < 0, and 0 otherwise
|
||||||
func GetMouseWheelXNorm() int32 {
|
func GetMouseWheelXNorm() int32 {
|
||||||
|
|
||||||
if mouseWheel.XDelta > 0 {
|
if mouseWheel.XDelta > 0 {
|
||||||
@ -153,7 +167,7 @@ func GetMouseWheelXNorm() int32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
//returns 1 if mouse wheel yDelta > 0, -1 if yDelta < 0, and 0 otherwise
|
// returns 1 if mouse wheel yDelta > 0, -1 if yDelta < 0, and 0 otherwise
|
||||||
func GetMouseWheelYNorm() int32 {
|
func GetMouseWheelYNorm() int32 {
|
||||||
|
|
||||||
if mouseWheel.YDelta > 0 {
|
if mouseWheel.YDelta > 0 {
|
||||||
@ -167,8 +181,8 @@ func GetMouseWheelYNorm() int32 {
|
|||||||
|
|
||||||
func KeyClicked(kc sdl.Keycode) bool {
|
func KeyClicked(kc sdl.Keycode) bool {
|
||||||
|
|
||||||
ks := keyMap[kc]
|
ks, ok := keyMap[kc]
|
||||||
if ks == nil {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,8 +191,8 @@ func KeyClicked(kc sdl.Keycode) bool {
|
|||||||
|
|
||||||
func KeyReleased(kc sdl.Keycode) bool {
|
func KeyReleased(kc sdl.Keycode) bool {
|
||||||
|
|
||||||
ks := keyMap[kc]
|
ks, ok := keyMap[kc]
|
||||||
if ks == nil {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,8 +201,8 @@ func KeyReleased(kc sdl.Keycode) bool {
|
|||||||
|
|
||||||
func KeyDown(kc sdl.Keycode) bool {
|
func KeyDown(kc sdl.Keycode) bool {
|
||||||
|
|
||||||
ks := keyMap[kc]
|
ks, ok := keyMap[kc]
|
||||||
if ks == nil {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,8 +211,8 @@ func KeyDown(kc sdl.Keycode) bool {
|
|||||||
|
|
||||||
func KeyUp(kc sdl.Keycode) bool {
|
func KeyUp(kc sdl.Keycode) bool {
|
||||||
|
|
||||||
ks := keyMap[kc]
|
ks, ok := keyMap[kc]
|
||||||
if ks == nil {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,8 +221,8 @@ func KeyUp(kc sdl.Keycode) bool {
|
|||||||
|
|
||||||
func MouseClicked(mb int) bool {
|
func MouseClicked(mb int) bool {
|
||||||
|
|
||||||
btn := mouseBtnMap[mb]
|
btn, ok := mouseBtnMap[mb]
|
||||||
if btn == nil {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,8 +231,8 @@ func MouseClicked(mb int) bool {
|
|||||||
|
|
||||||
func MouseDoubleClicked(mb int) bool {
|
func MouseDoubleClicked(mb int) bool {
|
||||||
|
|
||||||
btn := mouseBtnMap[mb]
|
btn, ok := mouseBtnMap[mb]
|
||||||
if btn == nil {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,8 +240,8 @@ func MouseDoubleClicked(mb int) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func MouseReleased(mb int) bool {
|
func MouseReleased(mb int) bool {
|
||||||
btn := mouseBtnMap[mb]
|
btn, ok := mouseBtnMap[mb]
|
||||||
if btn == nil {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,8 +250,8 @@ func MouseReleased(mb int) bool {
|
|||||||
|
|
||||||
func MouseDown(mb int) bool {
|
func MouseDown(mb int) bool {
|
||||||
|
|
||||||
btn := mouseBtnMap[mb]
|
btn, ok := mouseBtnMap[mb]
|
||||||
if btn == nil {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,8 +260,8 @@ func MouseDown(mb int) bool {
|
|||||||
|
|
||||||
func MouseUp(mb int) bool {
|
func MouseUp(mb int) bool {
|
||||||
|
|
||||||
btn := mouseBtnMap[mb]
|
btn, ok := mouseBtnMap[mb]
|
||||||
if btn == nil {
|
if !ok {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
17
level/level.go
Executable file
@ -0,0 +1,17 @@
|
|||||||
|
package level
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bloeys/nmage/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Level struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLevel(name string) *Level {
|
||||||
|
|
||||||
|
assert.T(name != "", "Level name can not be empty")
|
||||||
|
return &Level{
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@ package materials
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/bloeys/gglm/gglm"
|
"github.com/bloeys/gglm/gglm"
|
||||||
"github.com/bloeys/nmage/asserts"
|
"github.com/bloeys/nmage/assert"
|
||||||
"github.com/bloeys/nmage/logging"
|
"github.com/bloeys/nmage/logging"
|
||||||
"github.com/bloeys/nmage/shaders"
|
"github.com/bloeys/nmage/shaders"
|
||||||
"github.com/go-gl/gl/v4.1-core/gl"
|
"github.com/go-gl/gl/v4.1-core/gl"
|
||||||
@ -12,10 +12,16 @@ type Material struct {
|
|||||||
Name string
|
Name string
|
||||||
ShaderProg shaders.ShaderProgram
|
ShaderProg shaders.ShaderProgram
|
||||||
|
|
||||||
DiffuseTex uint32
|
|
||||||
|
|
||||||
UnifLocs map[string]int32
|
UnifLocs map[string]int32
|
||||||
AttribLocs map[string]int32
|
AttribLocs map[string]int32
|
||||||
|
|
||||||
|
// Phong shading
|
||||||
|
DiffuseTex uint32
|
||||||
|
SpecularTex uint32
|
||||||
|
NormalTex uint32
|
||||||
|
EmissionTex uint32
|
||||||
|
|
||||||
|
Shininess float32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Material) Bind() {
|
func (m *Material) Bind() {
|
||||||
@ -24,6 +30,15 @@ func (m *Material) Bind() {
|
|||||||
|
|
||||||
gl.ActiveTexture(gl.TEXTURE0)
|
gl.ActiveTexture(gl.TEXTURE0)
|
||||||
gl.BindTexture(gl.TEXTURE_2D, m.DiffuseTex)
|
gl.BindTexture(gl.TEXTURE_2D, m.DiffuseTex)
|
||||||
|
|
||||||
|
gl.ActiveTexture(gl.TEXTURE1)
|
||||||
|
gl.BindTexture(gl.TEXTURE_2D, m.SpecularTex)
|
||||||
|
|
||||||
|
gl.ActiveTexture(gl.TEXTURE2)
|
||||||
|
gl.BindTexture(gl.TEXTURE_2D, m.NormalTex)
|
||||||
|
|
||||||
|
gl.ActiveTexture(gl.TEXTURE3)
|
||||||
|
gl.BindTexture(gl.TEXTURE_2D, m.EmissionTex)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Material) UnBind() {
|
func (m *Material) UnBind() {
|
||||||
@ -32,6 +47,15 @@ func (m *Material) UnBind() {
|
|||||||
//TODO: Should we unbind textures here? Are these two lines needed?
|
//TODO: Should we unbind textures here? Are these two lines needed?
|
||||||
// gl.ActiveTexture(gl.TEXTURE0)
|
// gl.ActiveTexture(gl.TEXTURE0)
|
||||||
// gl.BindTexture(gl.TEXTURE_2D, 0)
|
// gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||||
|
|
||||||
|
// gl.ActiveTexture(gl.TEXTURE1)
|
||||||
|
// gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||||
|
|
||||||
|
// gl.ActiveTexture(gl.TEXTURE2)
|
||||||
|
// gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||||
|
|
||||||
|
// gl.ActiveTexture(gl.TEXTURE3)
|
||||||
|
// gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Material) GetAttribLoc(attribName string) int32 {
|
func (m *Material) GetAttribLoc(attribName string) int32 {
|
||||||
@ -42,7 +66,7 @@ func (m *Material) GetAttribLoc(attribName string) int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loc = gl.GetAttribLocation(m.ShaderProg.ID, gl.Str(attribName+"\x00"))
|
loc = gl.GetAttribLocation(m.ShaderProg.ID, gl.Str(attribName+"\x00"))
|
||||||
asserts.T(loc != -1, "Attribute '"+attribName+"' doesn't exist on material "+m.Name)
|
assert.T(loc != -1, "Attribute '"+attribName+"' doesn't exist on material "+m.Name)
|
||||||
m.AttribLocs[attribName] = loc
|
m.AttribLocs[attribName] = loc
|
||||||
return loc
|
return loc
|
||||||
}
|
}
|
||||||
@ -55,7 +79,7 @@ func (m *Material) GetUnifLoc(uniformName string) int32 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loc = gl.GetUniformLocation(m.ShaderProg.ID, gl.Str(uniformName+"\x00"))
|
loc = gl.GetUniformLocation(m.ShaderProg.ID, gl.Str(uniformName+"\x00"))
|
||||||
asserts.T(loc != -1, "Uniform '"+uniformName+"' doesn't exist on material "+m.Name)
|
assert.T(loc != -1, "Uniform '"+uniformName+"' doesn't exist on material "+m.Name)
|
||||||
m.UnifLocs[uniformName] = loc
|
m.UnifLocs[uniformName] = loc
|
||||||
return loc
|
return loc
|
||||||
}
|
}
|
||||||
@ -106,24 +130,20 @@ func (m *Material) Delete() {
|
|||||||
|
|
||||||
func NewMaterial(matName, shaderPath string) *Material {
|
func NewMaterial(matName, shaderPath string) *Material {
|
||||||
|
|
||||||
shdrProg, err := shaders.NewShaderProgram()
|
shdrProg, err := shaders.LoadAndCompileCombinedShader(shaderPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.ErrLog.Fatalln("Failed to create new shader program. Err: ", err)
|
logging.ErrLog.Fatalln("Failed to create new material. Err: ", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Material{Name: matName, ShaderProg: shdrProg, UnifLocs: make(map[string]int32), AttribLocs: make(map[string]int32)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMaterialSrc(matName string, shaderSrc []byte) *Material {
|
||||||
|
|
||||||
|
shdrProg, err := shaders.LoadAndCompileCombinedShaderSrc(shaderSrc)
|
||||||
|
if err != nil {
|
||||||
|
logging.ErrLog.Fatalln("Failed to create new material. Err: ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
vertShader, err := shaders.LoadAndCompilerShader(shaderPath+".vert.glsl", shaders.VertexShaderType)
|
|
||||||
if err != nil {
|
|
||||||
logging.ErrLog.Fatalln("Failed to load and create vertex shader. Err: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fragShader, err := shaders.LoadAndCompilerShader(shaderPath+".frag.glsl", shaders.FragmentShaderType)
|
|
||||||
if err != nil {
|
|
||||||
logging.ErrLog.Fatalln("Failed to load and create fragment shader. Err: ", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
shdrProg.AttachShader(vertShader)
|
|
||||||
shdrProg.AttachShader(fragShader)
|
|
||||||
shdrProg.Link()
|
|
||||||
|
|
||||||
return &Material{Name: matName, ShaderProg: shdrProg, UnifLocs: make(map[string]int32), AttribLocs: make(map[string]int32)}
|
return &Material{Name: matName, ShaderProg: shdrProg, UnifLocs: make(map[string]int32), AttribLocs: make(map[string]int32)}
|
||||||
}
|
}
|
||||||
|
|||||||
146
meshes/mesh.go
@ -2,17 +2,23 @@ package meshes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/bloeys/assimp-go/asig"
|
"github.com/bloeys/assimp-go/asig"
|
||||||
"github.com/bloeys/gglm/gglm"
|
"github.com/bloeys/gglm/gglm"
|
||||||
"github.com/bloeys/nmage/asserts"
|
"github.com/bloeys/nmage/assert"
|
||||||
"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
|
Vao buffers.VertexArray
|
||||||
|
SubMeshes []SubMesh
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh, error) {
|
func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh, error) {
|
||||||
@ -27,29 +33,82 @@ 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()
|
Vao: buffers.NewVertexArray(),
|
||||||
|
SubMeshes: make([]SubMesh, 0, 1),
|
||||||
asserts.T(len(sceneMesh.TexCoords[0]) > 0, "Mesh has no UV0")
|
|
||||||
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})
|
|
||||||
}
|
|
||||||
mesh.Buf.SetLayout(layoutToUse...)
|
|
||||||
|
|
||||||
var values []float32
|
|
||||||
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]})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
values = interleave(arrs...)
|
vbo := buffers.NewVertexBuffer()
|
||||||
|
ibo := buffers.NewIndexBuffer()
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
// fmt.Printf("\nMesh %s has %d meshe(s) with first mesh having %d vertices\n", name, len(scene.Meshes), len(scene.Meshes[0].Vertices))
|
||||||
|
|
||||||
|
for i := 0; i < len(scene.Meshes); i++ {
|
||||||
|
|
||||||
|
sceneMesh := scene.Meshes[i]
|
||||||
|
|
||||||
|
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}}
|
||||||
|
if len(sceneMesh.ColorSets) > 0 && len(sceneMesh.ColorSets[0]) > 0 {
|
||||||
|
layoutToUse = append(layoutToUse, buffers.Element{ElementType: buffers.DataTypeVec4})
|
||||||
|
}
|
||||||
|
|
||||||
|
if i == 0 {
|
||||||
|
vbo.SetLayout(layoutToUse...)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// @TODO @NOTE: This requirement is because we are using one VAO+VBO for all
|
||||||
|
// the meshes and so the buffer must have one format.
|
||||||
|
//
|
||||||
|
// If we want to allow different layouts then we can simply create one vbo per layout and put
|
||||||
|
// meshes of the same layout in the same vbo, and we store the index of the vbo the mesh
|
||||||
|
// uses in the submesh struct.
|
||||||
|
firstSubmeshLayout := vbo.GetLayout()
|
||||||
|
assert.T(len(firstSubmeshLayout) == len(layoutToUse), "Vertex layout of submesh '%d' of mesh '%s' at path '%s' does not equal vertex layout of the first submesh. Original layout: %v; This layout: %v", i, name, modelPath, firstSubmeshLayout, layoutToUse)
|
||||||
|
|
||||||
|
for i := 0; i < len(firstSubmeshLayout); i++ {
|
||||||
|
assert.T(firstSubmeshLayout[i].ElementType == layoutToUse[i].ElementType, "Vertex layout of submesh '%d' of mesh '%s' at path '%s' does not equal vertex layout of the first submesh. Original layout: %v; This layout: %v", i, name, modelPath, 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) / vbo.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...)
|
||||||
|
}
|
||||||
|
|
||||||
|
vbo.SetData(vertexBufData, buffers.BufUsage_Static)
|
||||||
|
ibo.SetData(indexBufData)
|
||||||
|
|
||||||
|
mesh.Vao.AddVertexBuffer(vbo)
|
||||||
|
mesh.Vao.SetIndexBuffer(ibo)
|
||||||
|
|
||||||
|
// This is needed so that if you load meshes one after the other the
|
||||||
|
// following mesh doesn't attach its vbo/ibo to this vao
|
||||||
|
mesh.Vao.UnBind()
|
||||||
|
|
||||||
mesh.Buf.SetData(values)
|
|
||||||
mesh.Buf.SetIndexBufData(flattenFaces(sceneMesh.Faces))
|
|
||||||
return mesh, nil
|
return mesh, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,9 +132,9 @@ type arrToInterleave struct {
|
|||||||
|
|
||||||
func (a *arrToInterleave) get(i int) []float32 {
|
func (a *arrToInterleave) get(i int) []float32 {
|
||||||
|
|
||||||
asserts.T(len(a.V2s) == 0 || len(a.V3s) == 0, "One array should be set in arrToInterleave, but both arrays are set")
|
assert.T(len(a.V2s) == 0 || len(a.V3s) == 0, "One array should be set in arrToInterleave, but multiple arrays are set")
|
||||||
asserts.T(len(a.V2s) == 0 || len(a.V4s) == 0, "One array should be set in arrToInterleave, but both arrays are set")
|
assert.T(len(a.V2s) == 0 || len(a.V4s) == 0, "One array should be set in arrToInterleave, but multiple arrays are set")
|
||||||
asserts.T(len(a.V3s) == 0 || len(a.V4s) == 0, "One array should be set in arrToInterleave, but both arrays are set")
|
assert.T(len(a.V3s) == 0 || len(a.V4s) == 0, "One array should be set in arrToInterleave, but multiple arrays are set")
|
||||||
|
|
||||||
if len(a.V2s) > 0 {
|
if len(a.V2s) > 0 {
|
||||||
return a.V2s[i].Data[:]
|
return a.V2s[i].Data[:]
|
||||||
@ -88,8 +147,8 @@ func (a *arrToInterleave) get(i int) []float32 {
|
|||||||
|
|
||||||
func interleave(arrs ...arrToInterleave) []float32 {
|
func interleave(arrs ...arrToInterleave) []float32 {
|
||||||
|
|
||||||
asserts.T(len(arrs) > 0, "No input sent to interleave")
|
assert.T(len(arrs) > 0, "No input sent to interleave")
|
||||||
asserts.T(len(arrs[0].V2s) > 0 || len(arrs[0].V3s) > 0 || len(arrs[0].V4s) > 0, "Interleave arrays are empty")
|
assert.T(len(arrs[0].V2s) > 0 || len(arrs[0].V3s) > 0 || len(arrs[0].V4s) > 0, "Interleave arrays are empty")
|
||||||
|
|
||||||
elementCount := 0
|
elementCount := 0
|
||||||
if len(arrs[0].V2s) > 0 {
|
if len(arrs[0].V2s) > 0 {
|
||||||
@ -104,7 +163,7 @@ func interleave(arrs ...arrToInterleave) []float32 {
|
|||||||
totalSize := 0
|
totalSize := 0
|
||||||
for i := 0; i < len(arrs); i++ {
|
for i := 0; i < len(arrs); i++ {
|
||||||
|
|
||||||
asserts.T(len(arrs[i].V2s) == elementCount || len(arrs[i].V3s) == elementCount || len(arrs[i].V4s) == elementCount, "Mesh vertex data given to interleave is not the same length")
|
assert.T(len(arrs[i].V2s) == elementCount || len(arrs[i].V3s) == elementCount || len(arrs[i].V4s) == elementCount, "Mesh vertex data given to interleave is not the same length")
|
||||||
|
|
||||||
if len(arrs[i].V2s) > 0 {
|
if len(arrs[i].V2s) > 0 {
|
||||||
totalSize += len(arrs[i].V2s) * 2
|
totalSize += len(arrs[i].V2s) * 2
|
||||||
@ -125,34 +184,9 @@ func interleave(arrs ...arrToInterleave) []float32 {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
func flattenVec3(vec3s []gglm.Vec3) []float32 {
|
|
||||||
|
|
||||||
floats := make([]float32, len(vec3s)*3)
|
|
||||||
for i := 0; i < len(vec3s); i++ {
|
|
||||||
floats[i*3+0] = vec3s[i].X()
|
|
||||||
floats[i*3+1] = vec3s[i].Y()
|
|
||||||
floats[i*3+2] = vec3s[i].Z()
|
|
||||||
}
|
|
||||||
|
|
||||||
return floats
|
|
||||||
}
|
|
||||||
|
|
||||||
func flattenVec4(vec4s []gglm.Vec4) []float32 {
|
|
||||||
|
|
||||||
floats := make([]float32, len(vec4s)*4)
|
|
||||||
for i := 0; i < len(vec4s); i++ {
|
|
||||||
floats[i*4+0] = vec4s[i].X()
|
|
||||||
floats[i*4+1] = vec4s[i].Y()
|
|
||||||
floats[i*4+2] = vec4s[i].Z()
|
|
||||||
floats[i*4+3] = vec4s[i].W()
|
|
||||||
}
|
|
||||||
|
|
||||||
return floats
|
|
||||||
}
|
|
||||||
|
|
||||||
func flattenFaces(faces []asig.Face) []uint32 {
|
func flattenFaces(faces []asig.Face) []uint32 {
|
||||||
|
|
||||||
asserts.T(len(faces[0].Indices) == 3, fmt.Sprintf("Face doesn't have 3 indices. Index count: %v\n", len(faces[0].Indices)))
|
assert.T(len(faces[0].Indices) == 3, "Face doesn't have 3 indices. Index count: %v\n", len(faces[0].Indices))
|
||||||
|
|
||||||
uints := make([]uint32, len(faces)*3)
|
uints := make([]uint32, len(faces)*3)
|
||||||
for i := 0; i < len(faces); i++ {
|
for i := 0; i < len(faces); i++ {
|
||||||
|
|||||||
73
registry/iterator.go
Executable file
@ -0,0 +1,73 @@
|
|||||||
|
package registry
|
||||||
|
|
||||||
|
// Iterator goes through the entire registry it was created from and
|
||||||
|
// returns all alive items, and nil after its done.
|
||||||
|
//
|
||||||
|
// The iterator will still work if items are added/removed to the registry
|
||||||
|
// after it was created, but the following conditions apply:
|
||||||
|
// - If items are removed, iterator will not show the removed items (assuming it didn't return them before their removal)
|
||||||
|
// - If items are added, the iterator will either only return older items (i.e. is not affected), or only return newer items (i.e. items that were going to be returned before will now not get returned in favor of newly inserted items), or a mix of old and new items.
|
||||||
|
// However, in all cases the iterator will *never* returns more items than were alive at the time of the iterator's creation.
|
||||||
|
// - If items were both added and removed, the iterator might follow either of the previous 2 cases or a combination of them
|
||||||
|
//
|
||||||
|
// To summarize: The iterator will *never* return more items than were alive at the time of its creation, and will *never* return freed items
|
||||||
|
//
|
||||||
|
// Example usage:
|
||||||
|
//
|
||||||
|
// for item, handle := it.Next(); !it.IsDone(); item, handle = it.Next() {
|
||||||
|
// // Do stuff
|
||||||
|
// }
|
||||||
|
type Iterator[T any] struct {
|
||||||
|
registry *Registry[T]
|
||||||
|
remainingItems uint
|
||||||
|
currIndex int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *Iterator[T]) Next() (*T, Handle) {
|
||||||
|
|
||||||
|
if it.IsDone() {
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// If IsDone() only checked 'remainingItems', then when Next() returns the last item IsDone() will immediately be true which will cause loops to exit before processing the last item!
|
||||||
|
// However, with this check IsDone will remain false until Next() is called at least one more time after returning the last item which ensures the last item is processed in the loop.
|
||||||
|
//
|
||||||
|
// In cases where iterator is created on an empty registry, IsDone() will report true and the above check will return early
|
||||||
|
if it.remainingItems == 0 {
|
||||||
|
it.currIndex = -1
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
for ; it.currIndex < len(it.registry.Handles); it.currIndex++ {
|
||||||
|
|
||||||
|
handle := it.registry.Handles[it.currIndex]
|
||||||
|
if !handle.HasFlag(HandleFlag_Alive) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
item := &it.registry.Items[it.currIndex]
|
||||||
|
it.currIndex++
|
||||||
|
it.remainingItems--
|
||||||
|
return item, handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we reached here means we iterated to the end and didn't find anything, which probably
|
||||||
|
// means that the registry changed since we were created, and that remainingItems is not accurate.
|
||||||
|
//
|
||||||
|
// As such, we zero remaining items so that this iterator is considered done
|
||||||
|
it.currIndex = -1
|
||||||
|
it.remainingItems = 0
|
||||||
|
return nil, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (it *Iterator[T]) IsDone() bool {
|
||||||
|
|
||||||
|
if it.remainingItems != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have two cases here:
|
||||||
|
// 1. Index of zero means Next() never returned an item. Remaining items of zero without returning anything means we have an empty registry and so its safe to report done
|
||||||
|
// 2. Negative index means Next() has detected we reached the end and that its safe to report being done
|
||||||
|
return it.currIndex <= 0
|
||||||
|
}
|
||||||
135
registry/registry.go
Executable file
@ -0,0 +1,135 @@
|
|||||||
|
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 {
|
||||||
|
|
||||||
|
if id.IsZero() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
43
registry/registry_handle.go
Executable file
@ -0,0 +1,43 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
// IsZero reports whether the handle is in its default 'zero' state.
|
||||||
|
// A zero handle is an invalid handle that does NOT point to any entity
|
||||||
|
func (h Handle) IsZero() bool {
|
||||||
|
return h == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
@ -18,7 +18,7 @@ type Rend3DGL struct {
|
|||||||
func (r3d *Rend3DGL) Draw(mesh *meshes.Mesh, trMat *gglm.TrMat, mat *materials.Material) {
|
func (r3d *Rend3DGL) Draw(mesh *meshes.Mesh, trMat *gglm.TrMat, mat *materials.Material) {
|
||||||
|
|
||||||
if mesh != r3d.BoundMesh {
|
if mesh != r3d.BoundMesh {
|
||||||
mesh.Buf.Bind()
|
mesh.Vao.Bind()
|
||||||
r3d.BoundMesh = mesh
|
r3d.BoundMesh = mesh
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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/cube.fbx
Executable 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
|
|
||||||
BIN
res/models/plane.fbx
Executable file
38
res/models/skybox-cube.obj
Executable 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
|
||||||
BIN
res/models/sphere.fbx
Executable file
@ -1,3 +1,4 @@
|
|||||||
|
//shader:vertex
|
||||||
#version 410
|
#version 410
|
||||||
|
|
||||||
layout(location=0) in vec3 vertPosIn;
|
layout(location=0) in vec3 vertPosIn;
|
||||||
@ -23,4 +24,29 @@ void main()
|
|||||||
fragPos = vec3(modelMat * vec4(vertPosIn, 1.0));
|
fragPos = vec3(modelMat * vec4(vertPosIn, 1.0));
|
||||||
|
|
||||||
gl_Position = projMat * viewMat * 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);
|
||||||
|
}
|
||||||
@ -1,13 +0,0 @@
|
|||||||
#version 410
|
|
||||||
|
|
||||||
uniform sampler2D Texture;
|
|
||||||
|
|
||||||
in vec2 Frag_UV;
|
|
||||||
in vec4 Frag_Color;
|
|
||||||
|
|
||||||
out vec4 Out_Color;
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
Out_Color = vec4(Frag_Color.rgb, Frag_Color.a * texture(Texture, Frag_UV.st).r);
|
|
||||||
}
|
|
||||||
47
res/shaders/imgui.glsl
Executable file
@ -0,0 +1,47 @@
|
|||||||
|
//shader:vertex
|
||||||
|
#version 410
|
||||||
|
|
||||||
|
uniform mat4 ProjMtx;
|
||||||
|
|
||||||
|
in vec2 Position;
|
||||||
|
in vec2 UV;
|
||||||
|
in vec4 Color;
|
||||||
|
|
||||||
|
out vec2 Frag_UV;
|
||||||
|
out vec4 Frag_Color;
|
||||||
|
|
||||||
|
// Imgui doesn't handle srgb correctly, and looks too bright and wrong in srgb buffers (see: https://github.com/ocornut/imgui/issues/578).
|
||||||
|
// While not a complete fix (that would require changes in imgui itself), moving incoming srgba colors to linear in the vertex shader helps make things look better.
|
||||||
|
vec4 srgba_to_linear(vec4 srgbaColor){
|
||||||
|
|
||||||
|
#define gamma_correction 2.2
|
||||||
|
|
||||||
|
return vec4(
|
||||||
|
pow(srgbaColor.r, gamma_correction),
|
||||||
|
pow(srgbaColor.g, gamma_correction),
|
||||||
|
pow(srgbaColor.b, gamma_correction),
|
||||||
|
srgbaColor.a
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
Frag_UV = UV;
|
||||||
|
Frag_Color = srgba_to_linear(Color);
|
||||||
|
gl_Position = ProjMtx * vec4(Position.xy, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//shader:fragment
|
||||||
|
#version 410
|
||||||
|
|
||||||
|
uniform sampler2D Texture;
|
||||||
|
|
||||||
|
in vec2 Frag_UV;
|
||||||
|
in vec4 Frag_Color;
|
||||||
|
|
||||||
|
out vec4 Out_Color;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
Out_Color = vec4(Frag_Color.rgb, Frag_Color.a * texture(Texture, Frag_UV.st).r);
|
||||||
|
}
|
||||||
@ -1,17 +0,0 @@
|
|||||||
#version 410
|
|
||||||
|
|
||||||
uniform mat4 ProjMtx;
|
|
||||||
|
|
||||||
in vec2 Position;
|
|
||||||
in vec2 UV;
|
|
||||||
in vec4 Color;
|
|
||||||
|
|
||||||
out vec2 Frag_UV;
|
|
||||||
out vec4 Frag_Color;
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
Frag_UV = UV;
|
|
||||||
Frag_Color = Color;
|
|
||||||
gl_Position = ProjMtx * vec4(Position.xy, 0, 1);
|
|
||||||
}
|
|
||||||
45
res/shaders/screen-quad.glsl
Executable file
@ -0,0 +1,45 @@
|
|||||||
|
//shader:vertex
|
||||||
|
#version 410
|
||||||
|
|
||||||
|
out vec2 vertUV0;
|
||||||
|
|
||||||
|
// Hardcoded vertex positions for a fullscreen quad.
|
||||||
|
// Format: vec4(pos.x, pos.y, uv0.x, uv0.y)
|
||||||
|
vec4 quadData[6] = vec4[](
|
||||||
|
vec4(-1.0, 1.0, 0.0, 1.0),
|
||||||
|
vec4(-1.0, -1.0, 0.0, 0.0),
|
||||||
|
vec4(1.0, -1.0, 1.0, 0.0),
|
||||||
|
vec4(-1.0, 1.0, 0.0, 1.0),
|
||||||
|
vec4(1.0, -1.0, 1.0, 0.0),
|
||||||
|
vec4(1.0, 1.0, 1.0, 1.0)
|
||||||
|
);
|
||||||
|
|
||||||
|
uniform vec2 scale = vec2(1, 1);
|
||||||
|
uniform vec2 offset = vec2(0, 0);
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 vertData = quadData[gl_VertexID];
|
||||||
|
|
||||||
|
vertUV0 = vertData.zw;
|
||||||
|
gl_Position = vec4((vertData.xy * scale) + offset, 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
//shader:fragment
|
||||||
|
#version 410
|
||||||
|
|
||||||
|
struct Material {
|
||||||
|
sampler2D diffuse;
|
||||||
|
};
|
||||||
|
|
||||||
|
uniform Material material;
|
||||||
|
|
||||||
|
in vec2 vertUV0;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 diffuseTexColor = texture(material.diffuse, vertUV0);
|
||||||
|
fragColor = vec4(diffuseTexColor.rgb, 1);
|
||||||
|
}
|
||||||
54
res/shaders/simple-unlit.glsl
Executable file
@ -0,0 +1,54 @@
|
|||||||
|
//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()
|
||||||
|
{
|
||||||
|
// @TODO: Calculate this on the CPU and send it as a uniform
|
||||||
|
|
||||||
|
// This produces the normal matrix that multiplies with the model normal to produce the
|
||||||
|
// world space normal. Based on 'One last thing' section from: https://learnopengl.com/Lighting/Basic-Lighting
|
||||||
|
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
|
||||||
|
|
||||||
|
struct Material {
|
||||||
|
sampler2D diffuse;
|
||||||
|
};
|
||||||
|
|
||||||
|
uniform Material material;
|
||||||
|
|
||||||
|
in vec3 vertColor;
|
||||||
|
in vec3 vertNormal;
|
||||||
|
in vec2 vertUV0;
|
||||||
|
in vec3 fragPos;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
vec4 diffuseTexColor = texture(material.diffuse, vertUV0);
|
||||||
|
fragColor = vec4(diffuseTexColor.rgb, 1);
|
||||||
|
}
|
||||||
@ -1,26 +0,0 @@
|
|||||||
#version 410
|
|
||||||
|
|
||||||
uniform float ambientStrength = 0.1;
|
|
||||||
uniform vec3 ambientLightColor = vec3(1, 1, 1);
|
|
||||||
|
|
||||||
uniform vec3 lightPos1;
|
|
||||||
uniform vec3 lightColor1;
|
|
||||||
|
|
||||||
uniform sampler2D diffTex;
|
|
||||||
|
|
||||||
in vec3 vertColor;
|
|
||||||
in vec3 vertNormal;
|
|
||||||
in vec2 vertUV0;
|
|
||||||
in vec3 fragPos;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main()
|
|
||||||
{
|
|
||||||
vec3 lightDir = normalize(lightPos1 - fragPos);
|
|
||||||
float diffStrength = max(0.0, dot(normalize(vertNormal), lightDir));
|
|
||||||
|
|
||||||
vec3 finalAmbientColor = ambientLightColor * ambientStrength;
|
|
||||||
vec4 texColor = texture(diffTex, vertUV0);
|
|
||||||
fragColor = vec4(texColor.rgb * vertColor * (finalAmbientColor + diffStrength*lightColor1) , texColor.a);
|
|
||||||
}
|
|
||||||
193
res/shaders/simple.glsl
Executable file
@ -0,0 +1,193 @@
|
|||||||
|
//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()
|
||||||
|
{
|
||||||
|
// @TODO: Calculate this on the CPU and send it as a uniform
|
||||||
|
|
||||||
|
// This produces the normal matrix that multiplies with the model normal to produce the
|
||||||
|
// world space normal. Based on 'One last thing' section from: https://learnopengl.com/Lighting/Basic-Lighting
|
||||||
|
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
|
||||||
|
|
||||||
|
struct Material {
|
||||||
|
sampler2D diffuse;
|
||||||
|
sampler2D specular;
|
||||||
|
// sampler2D normal;
|
||||||
|
sampler2D emission;
|
||||||
|
float shininess;
|
||||||
|
};
|
||||||
|
|
||||||
|
uniform Material material;
|
||||||
|
|
||||||
|
struct DirLight {
|
||||||
|
vec3 dir;
|
||||||
|
vec3 diffuseColor;
|
||||||
|
vec3 specularColor;
|
||||||
|
};
|
||||||
|
|
||||||
|
uniform DirLight dirLight;
|
||||||
|
|
||||||
|
struct PointLight {
|
||||||
|
vec3 pos;
|
||||||
|
vec3 diffuseColor;
|
||||||
|
vec3 specularColor;
|
||||||
|
float constant;
|
||||||
|
float linear;
|
||||||
|
float quadratic;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define NUM_POINT_LIGHTS 16
|
||||||
|
uniform PointLight pointLights[NUM_POINT_LIGHTS];
|
||||||
|
|
||||||
|
struct SpotLight {
|
||||||
|
vec3 pos;
|
||||||
|
vec3 dir;
|
||||||
|
vec3 diffuseColor;
|
||||||
|
vec3 specularColor;
|
||||||
|
float innerCutoff;
|
||||||
|
float outerCutoff;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define NUM_SPOT_LIGHTS 4
|
||||||
|
uniform SpotLight spotLights[NUM_SPOT_LIGHTS];
|
||||||
|
|
||||||
|
uniform vec3 camPos;
|
||||||
|
uniform vec3 ambientColor = vec3(0.2, 0.2, 0.2);
|
||||||
|
|
||||||
|
in vec3 vertColor;
|
||||||
|
in vec3 vertNormal;
|
||||||
|
in vec2 vertUV0;
|
||||||
|
in vec3 fragPos;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
// Global variables used as cache for lighting calculations
|
||||||
|
vec4 diffuseTexColor;
|
||||||
|
vec4 specularTexColor;
|
||||||
|
vec4 emissionTexColor;
|
||||||
|
vec3 normalizedVertNorm;
|
||||||
|
vec3 viewDir;
|
||||||
|
|
||||||
|
vec3 CalcDirLight()
|
||||||
|
{
|
||||||
|
vec3 lightDir = normalize(-dirLight.dir);
|
||||||
|
|
||||||
|
// Diffuse
|
||||||
|
float diffuseAmount = max(0.0, dot(normalizedVertNorm, lightDir));
|
||||||
|
vec3 finalDiffuse = diffuseAmount * dirLight.diffuseColor * diffuseTexColor.rgb;
|
||||||
|
|
||||||
|
// Specular
|
||||||
|
vec3 halfwayDir = normalize(lightDir + viewDir);
|
||||||
|
float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess);
|
||||||
|
vec3 finalSpecular = specularAmount * dirLight.specularColor * specularTexColor.rgb;
|
||||||
|
|
||||||
|
return finalDiffuse + finalSpecular;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 CalcPointLight(PointLight pointLight)
|
||||||
|
{
|
||||||
|
// Ignore unset lights
|
||||||
|
if (pointLight.constant == 0){
|
||||||
|
return vec3(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 lightDir = normalize(pointLight.pos - fragPos);
|
||||||
|
|
||||||
|
// Diffuse
|
||||||
|
float diffuseAmount = max(0.0, dot(normalizedVertNorm, lightDir));
|
||||||
|
vec3 finalDiffuse = diffuseAmount * pointLight.diffuseColor * diffuseTexColor.rgb;
|
||||||
|
|
||||||
|
// Specular
|
||||||
|
vec3 halfwayDir = normalize(lightDir + viewDir);
|
||||||
|
float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess);
|
||||||
|
vec3 finalSpecular = specularAmount * pointLight.specularColor * specularTexColor.rgb;
|
||||||
|
|
||||||
|
// attenuation
|
||||||
|
float distToLight = length(pointLight.pos - fragPos);
|
||||||
|
float attenuation = 1.0 / (pointLight.constant + pointLight.linear * distToLight + pointLight.quadratic * (distToLight * distToLight));
|
||||||
|
|
||||||
|
return (finalDiffuse + finalSpecular) * attenuation;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 CalcSpotLight(SpotLight light)
|
||||||
|
{
|
||||||
|
if (light.innerCutoff == 0)
|
||||||
|
return vec3(0);
|
||||||
|
|
||||||
|
vec3 fragToLightDir = normalize(light.pos - fragPos);
|
||||||
|
|
||||||
|
// Spot light cone with full intensity within inner cutoff,
|
||||||
|
// and falloff between inner-outer cutoffs, and zero
|
||||||
|
// light after outer cutoff
|
||||||
|
float theta = dot(fragToLightDir, normalize(-light.dir));
|
||||||
|
float epsilon = (light.innerCutoff - light.outerCutoff);
|
||||||
|
float intensity = clamp((theta - light.outerCutoff) / epsilon, 0.0, 1.0);
|
||||||
|
|
||||||
|
if (intensity == 0)
|
||||||
|
return vec3(0);
|
||||||
|
|
||||||
|
// Diffuse
|
||||||
|
float diffuseAmount = max(0.0, dot(normalizedVertNorm, fragToLightDir));
|
||||||
|
vec3 finalDiffuse = diffuseAmount * light.diffuseColor * diffuseTexColor.rgb;
|
||||||
|
|
||||||
|
// Specular
|
||||||
|
vec3 halfwayDir = normalize(fragToLightDir + viewDir);
|
||||||
|
float specularAmount = pow(max(dot(normalizedVertNorm, halfwayDir), 0.0), material.shininess);
|
||||||
|
vec3 finalSpecular = specularAmount * light.specularColor * specularTexColor.rgb;
|
||||||
|
|
||||||
|
return (finalDiffuse + finalSpecular) * intensity;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
// Shared values
|
||||||
|
diffuseTexColor = texture(material.diffuse, vertUV0);
|
||||||
|
specularTexColor = texture(material.specular, vertUV0);
|
||||||
|
emissionTexColor = texture(material.emission, vertUV0);
|
||||||
|
|
||||||
|
normalizedVertNorm = normalize(vertNormal);
|
||||||
|
viewDir = normalize(camPos - fragPos);
|
||||||
|
|
||||||
|
// Light contributions
|
||||||
|
vec3 finalColor = CalcDirLight();
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_POINT_LIGHTS; i++)
|
||||||
|
{
|
||||||
|
finalColor += CalcPointLight(pointLights[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_SPOT_LIGHTS; i++)
|
||||||
|
{
|
||||||
|
finalColor += CalcSpotLight(spotLights[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 finalEmission = emissionTexColor.rgb;
|
||||||
|
vec3 finalAmbient = ambientColor * diffuseTexColor.rgb;
|
||||||
|
|
||||||
|
fragColor = vec4(finalColor + finalAmbient + finalEmission, 1);
|
||||||
|
}
|
||||||
33
res/shaders/skybox.glsl
Executable 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);
|
||||||
|
}
|
||||||
BIN
res/textures/black.png
Executable file
|
After Width: | Height: | Size: 142 B |
BIN
res/textures/container-diffuse.png
Executable file
|
After Width: | Height: | Size: 457 KiB |
BIN
res/textures/container-specular.png
Executable file
|
After Width: | Height: | Size: 141 KiB |
BIN
res/textures/pallete-endesga-64-1x.png
Executable file
|
After Width: | Height: | Size: 355 B |
BIN
res/textures/sb-back.jpg
Executable file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
res/textures/sb-bottom.jpg
Executable file
|
After Width: | Height: | Size: 617 KiB |
BIN
res/textures/sb-front.jpg
Executable file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
res/textures/sb-left.jpg
Executable file
|
After Width: | Height: | Size: 1.4 MiB |
BIN
res/textures/sb-right.jpg
Executable file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
res/textures/sb-top.jpg
Executable file
|
After Width: | Height: | Size: 770 KiB |
BIN
res/textures/white.png
Executable file
|
After Width: | Height: | Size: 232 B |
BIN
rsrc_windows_386.syso
Executable file
BIN
rsrc_windows_amd64.syso
Executable file
@ -1,6 +1,7 @@
|
|||||||
package shaders
|
package shaders
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
@ -28,14 +29,69 @@ func NewShaderProgram() (ShaderProgram, error) {
|
|||||||
return ShaderProgram{ID: id}, nil
|
return ShaderProgram{ID: id}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func LoadAndCompilerShader(shaderPath string, shaderType ShaderType) (Shader, error) {
|
func LoadAndCompileCombinedShader(shaderPath string) (ShaderProgram, error) {
|
||||||
|
|
||||||
shaderSource, err := os.ReadFile(shaderPath)
|
combinedSource, err := os.ReadFile(shaderPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logging.ErrLog.Println("Failed to read shader. Err: ", err)
|
logging.ErrLog.Println("Failed to read shader. Err: ", err)
|
||||||
return Shader{}, err
|
return ShaderProgram{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return LoadAndCompileCombinedShaderSrc(combinedSource)
|
||||||
|
|
||||||
|
}
|
||||||
|
func LoadAndCompileCombinedShaderSrc(shaderSrc []byte) (ShaderProgram, error) {
|
||||||
|
|
||||||
|
shaderSources := bytes.Split(shaderSrc, []byte("//shader:"))
|
||||||
|
if len(shaderSources) == 1 {
|
||||||
|
return ShaderProgram{}, errors.New("failed to read combined shader. Did not find '//shader:vertex' or '//shader:fragment'")
|
||||||
|
}
|
||||||
|
|
||||||
|
shdrProg, err := NewShaderProgram()
|
||||||
|
if err != nil {
|
||||||
|
return ShaderProgram{}, errors.New("failed to create new shader program. Err: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedShdrCount := 0
|
||||||
|
for i := 0; i < len(shaderSources); i++ {
|
||||||
|
|
||||||
|
src := shaderSources[i]
|
||||||
|
|
||||||
|
//This can happen when the shader type is at the start of the file
|
||||||
|
if len(bytes.TrimSpace(src)) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var shdrType ShaderType
|
||||||
|
if bytes.HasPrefix(src, []byte("vertex")) {
|
||||||
|
src = src[6:]
|
||||||
|
shdrType = VertexShaderType
|
||||||
|
} else if bytes.HasPrefix(src, []byte("fragment")) {
|
||||||
|
src = src[8:]
|
||||||
|
shdrType = FragmentShaderType
|
||||||
|
} else {
|
||||||
|
return ShaderProgram{}, errors.New("unknown shader type. Must be '//shader:vertex' or '//shader:fragment'")
|
||||||
|
}
|
||||||
|
|
||||||
|
shdr, err := CompileShaderOfType(src, shdrType)
|
||||||
|
if err != nil {
|
||||||
|
return ShaderProgram{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
loadedShdrCount++
|
||||||
|
shdrProg.AttachShader(shdr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if loadedShdrCount == 0 {
|
||||||
|
return ShaderProgram{}, errors.New("no valid shaders found. Please put '//shader:vertex' or '//shader:fragment' before your shaders")
|
||||||
|
}
|
||||||
|
|
||||||
|
shdrProg.Link()
|
||||||
|
return shdrProg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompileShaderOfType(shaderSource []byte, shaderType ShaderType) (Shader, error) {
|
||||||
|
|
||||||
shaderID := gl.CreateShader(uint32(shaderType))
|
shaderID := gl.CreateShader(uint32(shaderType))
|
||||||
if shaderID == 0 {
|
if shaderID == 0 {
|
||||||
logging.ErrLog.Println("Failed to create shader.")
|
logging.ErrLog.Println("Failed to create shader.")
|
||||||
|
|||||||
@ -1,30 +1,30 @@
|
|||||||
package nmageimgui
|
package nmageimgui
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
imgui "github.com/AllenDang/cimgui-go"
|
||||||
"github.com/bloeys/gglm/gglm"
|
"github.com/bloeys/gglm/gglm"
|
||||||
"github.com/bloeys/nmage/asserts"
|
|
||||||
"github.com/bloeys/nmage/materials"
|
"github.com/bloeys/nmage/materials"
|
||||||
"github.com/bloeys/nmage/timing"
|
"github.com/bloeys/nmage/timing"
|
||||||
"github.com/go-gl/gl/v4.1-core/gl"
|
"github.com/go-gl/gl/v4.1-core/gl"
|
||||||
"github.com/inkyblackness/imgui-go/v4"
|
|
||||||
"github.com/veandco/go-sdl2/sdl"
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ImguiInfo struct {
|
type ImguiInfo struct {
|
||||||
ImCtx *imgui.Context
|
ImCtx imgui.Context
|
||||||
|
|
||||||
Mat *materials.Material
|
Mat *materials.Material
|
||||||
VaoID uint32
|
VaoID uint32
|
||||||
VboID uint32
|
VboID uint32
|
||||||
IndexBufID uint32
|
IndexBufID uint32
|
||||||
TexID uint32
|
// This is a pointer so we can send a stable pointer to C code
|
||||||
|
TexID *uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ImguiInfo) FrameStart(winWidth, winHeight float32) {
|
func (i *ImguiInfo) FrameStart(winWidth, winHeight float32) {
|
||||||
|
|
||||||
if err := i.ImCtx.SetCurrent(); err != nil {
|
// if err := i.ImCtx.SetCurrent(); err != nil {
|
||||||
asserts.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
|
// assert.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
|
||||||
}
|
// }
|
||||||
|
|
||||||
imIO := imgui.CurrentIO()
|
imIO := imgui.CurrentIO()
|
||||||
imIO.SetDisplaySize(imgui.Vec2{X: float32(winWidth), Y: float32(winHeight)})
|
imIO.SetDisplaySize(imgui.Vec2{X: float32(winWidth), Y: float32(winHeight)})
|
||||||
@ -35,9 +35,9 @@ func (i *ImguiInfo) FrameStart(winWidth, winHeight float32) {
|
|||||||
|
|
||||||
func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32) {
|
func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32) {
|
||||||
|
|
||||||
if err := i.ImCtx.SetCurrent(); err != nil {
|
// if err := i.ImCtx.SetCurrent(); err != nil {
|
||||||
asserts.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
|
// assert.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
|
||||||
}
|
// }
|
||||||
|
|
||||||
imgui.Render()
|
imgui.Render()
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
drawData := imgui.RenderedDrawData()
|
drawData := imgui.CurrentDrawData()
|
||||||
drawData.ScaleClipRects(imgui.Vec2{
|
drawData.ScaleClipRects(imgui.Vec2{
|
||||||
X: float32(fbWidth) / float32(winWidth),
|
X: float32(fbWidth) / float32(winWidth),
|
||||||
Y: float32(fbHeight) / float32(winHeight),
|
Y: float32(fbHeight) / float32(winHeight),
|
||||||
@ -66,10 +66,9 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
|
|||||||
// DisplayMin is typically (0,0) for single viewport apps.
|
// DisplayMin is typically (0,0) for single viewport apps.
|
||||||
|
|
||||||
i.Mat.Bind()
|
i.Mat.Bind()
|
||||||
|
i.Mat.SetUnifInt32("Texture", 0)
|
||||||
|
|
||||||
gl.Uniform1i(gl.GetUniformLocation(i.Mat.ShaderProg.ID, gl.Str("Texture\x00")), 0)
|
// @PERF: only update the ortho matrix on window resize
|
||||||
|
|
||||||
//PERF: only update the ortho matrix on window resize
|
|
||||||
orthoMat := gglm.Ortho(0, float32(winWidth), 0, float32(winHeight), 0, 20)
|
orthoMat := gglm.Ortho(0, float32(winWidth), 0, float32(winHeight), 0, 20)
|
||||||
i.Mat.SetUnifMat4("ProjMtx", &orthoMat.Mat4)
|
i.Mat.SetUnifMat4("ProjMtx", &orthoMat.Mat4)
|
||||||
gl.BindSampler(0, 0) // Rely on combined texture/sampler state.
|
gl.BindSampler(0, 0) // Rely on combined texture/sampler state.
|
||||||
@ -97,11 +96,11 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
|
|||||||
// Draw
|
// Draw
|
||||||
for _, list := range drawData.CommandLists() {
|
for _, list := range drawData.CommandLists() {
|
||||||
|
|
||||||
vertexBuffer, vertexBufferSize := list.VertexBuffer()
|
vertexBuffer, vertexBufferSize := list.GetVertexBuffer()
|
||||||
gl.BindBuffer(gl.ARRAY_BUFFER, i.VboID)
|
gl.BindBuffer(gl.ARRAY_BUFFER, i.VboID)
|
||||||
gl.BufferData(gl.ARRAY_BUFFER, vertexBufferSize, vertexBuffer, gl.STREAM_DRAW)
|
gl.BufferData(gl.ARRAY_BUFFER, vertexBufferSize, vertexBuffer, gl.STREAM_DRAW)
|
||||||
|
|
||||||
indexBuffer, indexBufferSize := list.IndexBuffer()
|
indexBuffer, indexBufferSize := list.GetIndexBuffer()
|
||||||
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, i.IndexBufID)
|
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, i.IndexBufID)
|
||||||
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, indexBufferSize, indexBuffer, gl.STREAM_DRAW)
|
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, indexBufferSize, indexBuffer, gl.STREAM_DRAW)
|
||||||
|
|
||||||
@ -110,11 +109,12 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
|
|||||||
cmd.CallUserCallback(list)
|
cmd.CallUserCallback(list)
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
gl.BindTexture(gl.TEXTURE_2D, i.TexID)
|
gl.ActiveTexture(gl.TEXTURE0)
|
||||||
|
gl.BindTexture(gl.TEXTURE_2D, *i.TexID)
|
||||||
clipRect := cmd.ClipRect()
|
clipRect := cmd.ClipRect()
|
||||||
gl.Scissor(int32(clipRect.X), int32(fbHeight)-int32(clipRect.W), int32(clipRect.Z-clipRect.X), int32(clipRect.W-clipRect.Y))
|
gl.Scissor(int32(clipRect.X), int32(fbHeight)-int32(clipRect.W), int32(clipRect.Z-clipRect.X), int32(clipRect.W-clipRect.Y))
|
||||||
|
|
||||||
gl.DrawElementsBaseVertex(gl.TRIANGLES, int32(cmd.ElementCount()), uint32(drawType), gl.PtrOffset(cmd.IndexOffset()*indexSize), int32(cmd.VertexOffset()))
|
gl.DrawElementsBaseVertexWithOffset(gl.TRIANGLES, int32(cmd.ElemCount()), uint32(drawType), uintptr(int(cmd.IdxOffset())*indexSize), int32(cmd.VtxOffset()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,14 +125,14 @@ func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32)
|
|||||||
gl.Enable(gl.DEPTH_TEST)
|
gl.Enable(gl.DEPTH_TEST)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *ImguiInfo) AddFontTTF(fontPath string, fontSize float32, fontConfig *imgui.FontConfig, glyphRanges *imgui.GlyphRanges) imgui.Font {
|
func (i *ImguiInfo) AddFontTTF(fontPath string, fontSize float32, fontConfig *imgui.FontConfig, glyphRanges *imgui.GlyphRange) imgui.Font {
|
||||||
|
|
||||||
fontConfigToUse := imgui.DefaultFontConfig
|
fontConfigToUse := imgui.NewFontConfig()
|
||||||
if fontConfig != nil {
|
if fontConfig != nil {
|
||||||
fontConfigToUse = *fontConfig
|
fontConfigToUse = *fontConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
glyphRangesToUse := imgui.EmptyGlyphRanges
|
glyphRangesToUse := imgui.NewGlyphRange()
|
||||||
if glyphRanges != nil {
|
if glyphRanges != nil {
|
||||||
glyphRangesToUse = *glyphRanges
|
glyphRangesToUse = *glyphRanges
|
||||||
}
|
}
|
||||||
@ -140,41 +140,104 @@ func (i *ImguiInfo) AddFontTTF(fontPath string, fontSize float32, fontConfig *im
|
|||||||
imIO := imgui.CurrentIO()
|
imIO := imgui.CurrentIO()
|
||||||
|
|
||||||
a := imIO.Fonts()
|
a := imIO.Fonts()
|
||||||
f := a.AddFontFromFileTTFV(fontPath, fontSize, fontConfigToUse, glyphRangesToUse)
|
f := a.AddFontFromFileTTFV(fontPath, fontSize, fontConfigToUse, glyphRangesToUse.Data())
|
||||||
image := a.TextureDataAlpha8()
|
pixels, width, height, _ := a.GetTextureDataAsAlpha8()
|
||||||
|
|
||||||
gl.BindTexture(gl.TEXTURE_2D, i.TexID)
|
gl.ActiveTexture(gl.TEXTURE0)
|
||||||
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(image.Width), int32(image.Height), 0, gl.RED, gl.UNSIGNED_BYTE, image.Pixels)
|
gl.BindTexture(gl.TEXTURE_2D, *i.TexID)
|
||||||
|
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(width), int32(height), 0, gl.RED, gl.UNSIGNED_BYTE, pixels)
|
||||||
|
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewImGUI() ImguiInfo {
|
const DefaultImguiShader = `
|
||||||
|
//shader:vertex
|
||||||
|
#version 410
|
||||||
|
|
||||||
imguiInfo := ImguiInfo{
|
uniform mat4 ProjMtx;
|
||||||
ImCtx: imgui.CreateContext(nil),
|
|
||||||
Mat: materials.NewMaterial("ImGUI Mat", "./res/shaders/imgui"),
|
in vec2 Position;
|
||||||
|
in vec2 UV;
|
||||||
|
in vec4 Color;
|
||||||
|
|
||||||
|
out vec2 Frag_UV;
|
||||||
|
out vec4 Frag_Color;
|
||||||
|
|
||||||
|
// Imgui doesn't handle srgb correctly, and looks too bright and wrong in srgb buffers (see: https://github.com/ocornut/imgui/issues/578).
|
||||||
|
// While not a complete fix (that would require changes in imgui itself), moving incoming srgba colors to linear in the vertex shader helps make things look better.
|
||||||
|
vec4 srgba_to_linear(vec4 srgbaColor){
|
||||||
|
|
||||||
|
#define gamma_correction 2.2
|
||||||
|
|
||||||
|
return vec4(
|
||||||
|
pow(srgbaColor.r, gamma_correction),
|
||||||
|
pow(srgbaColor.g, gamma_correction),
|
||||||
|
pow(srgbaColor.b, gamma_correction),
|
||||||
|
srgbaColor.a
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
Frag_UV = UV;
|
||||||
|
Frag_Color = srgba_to_linear(Color);
|
||||||
|
gl_Position = ProjMtx * vec4(Position.xy, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//shader:fragment
|
||||||
|
#version 410
|
||||||
|
|
||||||
|
uniform sampler2D Texture;
|
||||||
|
|
||||||
|
in vec2 Frag_UV;
|
||||||
|
in vec4 Frag_Color;
|
||||||
|
|
||||||
|
out vec4 Out_Color;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
Out_Color = vec4(Frag_Color.rgb, Frag_Color.a * texture(Texture, Frag_UV.st).r);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
// NewImGui setups imgui using the passed shader.
|
||||||
|
// If the path is empty a default nMage shader is used
|
||||||
|
func NewImGui(shaderPath string) ImguiInfo {
|
||||||
|
|
||||||
|
var imguiMat *materials.Material
|
||||||
|
if shaderPath == "" {
|
||||||
|
imguiMat = materials.NewMaterialSrc("ImGUI Mat", []byte(DefaultImguiShader))
|
||||||
|
} else {
|
||||||
|
imguiMat = materials.NewMaterial("ImGUI Mat", shaderPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
imIO := imgui.CurrentIO()
|
imguiInfo := ImguiInfo{
|
||||||
imIO.SetBackendFlags(imIO.GetBackendFlags() | imgui.BackendFlagsRendererHasVtxOffset)
|
ImCtx: imgui.CreateContext(),
|
||||||
|
Mat: imguiMat,
|
||||||
|
TexID: new(uint32),
|
||||||
|
}
|
||||||
|
|
||||||
|
io := imgui.CurrentIO()
|
||||||
|
io.SetConfigFlags(io.ConfigFlags() | imgui.ConfigFlagsDockingEnable)
|
||||||
|
io.SetBackendFlags(io.BackendFlags() | imgui.BackendFlagsRendererHasVtxOffset)
|
||||||
|
|
||||||
gl.GenVertexArrays(1, &imguiInfo.VaoID)
|
gl.GenVertexArrays(1, &imguiInfo.VaoID)
|
||||||
gl.GenBuffers(1, &imguiInfo.VboID)
|
gl.GenBuffers(1, &imguiInfo.VboID)
|
||||||
gl.GenBuffers(1, &imguiInfo.IndexBufID)
|
gl.GenBuffers(1, &imguiInfo.IndexBufID)
|
||||||
gl.GenTextures(1, &imguiInfo.TexID)
|
gl.GenTextures(1, imguiInfo.TexID)
|
||||||
|
|
||||||
// Upload font to gpu
|
// Upload font to gpu
|
||||||
gl.BindTexture(gl.TEXTURE_2D, imguiInfo.TexID)
|
gl.ActiveTexture(gl.TEXTURE0)
|
||||||
|
gl.BindTexture(gl.TEXTURE_2D, *imguiInfo.TexID)
|
||||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
|
||||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
|
||||||
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
|
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
|
||||||
|
|
||||||
image := imIO.Fonts().TextureDataAlpha8()
|
pixels, width, height, _ := io.Fonts().GetTextureDataAsAlpha8()
|
||||||
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(image.Width), int32(image.Height), 0, gl.RED, gl.UNSIGNED_BYTE, image.Pixels)
|
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(width), int32(height), 0, gl.RED, gl.UNSIGNED_BYTE, pixels)
|
||||||
|
|
||||||
// Store our identifier
|
// Store our identifier
|
||||||
imIO.Fonts().SetTextureID(imgui.TextureID(imguiInfo.TexID))
|
io.Fonts().SetTexID(imgui.TextureID(imguiInfo.TexID))
|
||||||
|
|
||||||
//Shader attributes
|
//Shader attributes
|
||||||
imguiInfo.Mat.Bind()
|
imguiInfo.Mat.Bind()
|
||||||
@ -183,35 +246,223 @@ func NewImGUI() ImguiInfo {
|
|||||||
imguiInfo.Mat.EnableAttribute("Color")
|
imguiInfo.Mat.EnableAttribute("Color")
|
||||||
imguiInfo.Mat.UnBind()
|
imguiInfo.Mat.UnBind()
|
||||||
|
|
||||||
//Init imgui input mapping
|
|
||||||
keys := map[int]int{
|
|
||||||
imgui.KeyTab: sdl.SCANCODE_TAB,
|
|
||||||
imgui.KeyLeftArrow: sdl.SCANCODE_LEFT,
|
|
||||||
imgui.KeyRightArrow: sdl.SCANCODE_RIGHT,
|
|
||||||
imgui.KeyUpArrow: sdl.SCANCODE_UP,
|
|
||||||
imgui.KeyDownArrow: sdl.SCANCODE_DOWN,
|
|
||||||
imgui.KeyPageUp: sdl.SCANCODE_PAGEUP,
|
|
||||||
imgui.KeyPageDown: sdl.SCANCODE_PAGEDOWN,
|
|
||||||
imgui.KeyHome: sdl.SCANCODE_HOME,
|
|
||||||
imgui.KeyEnd: sdl.SCANCODE_END,
|
|
||||||
imgui.KeyInsert: sdl.SCANCODE_INSERT,
|
|
||||||
imgui.KeyDelete: sdl.SCANCODE_DELETE,
|
|
||||||
imgui.KeyBackspace: sdl.SCANCODE_BACKSPACE,
|
|
||||||
imgui.KeySpace: sdl.SCANCODE_BACKSPACE,
|
|
||||||
imgui.KeyEnter: sdl.SCANCODE_RETURN,
|
|
||||||
imgui.KeyEscape: sdl.SCANCODE_ESCAPE,
|
|
||||||
imgui.KeyA: sdl.SCANCODE_A,
|
|
||||||
imgui.KeyC: sdl.SCANCODE_C,
|
|
||||||
imgui.KeyV: sdl.SCANCODE_V,
|
|
||||||
imgui.KeyX: sdl.SCANCODE_X,
|
|
||||||
imgui.KeyY: sdl.SCANCODE_Y,
|
|
||||||
imgui.KeyZ: sdl.SCANCODE_Z,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array.
|
|
||||||
for imguiKey, nativeKey := range keys {
|
|
||||||
imIO.KeyMap(imguiKey, nativeKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
return imguiInfo
|
return imguiInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SdlScancodeToImGuiKey(scancode sdl.Scancode) imgui.Key {
|
||||||
|
|
||||||
|
switch scancode {
|
||||||
|
case sdl.SCANCODE_TAB:
|
||||||
|
return imgui.KeyTab
|
||||||
|
case sdl.SCANCODE_LEFT:
|
||||||
|
return imgui.KeyLeftArrow
|
||||||
|
case sdl.SCANCODE_RIGHT:
|
||||||
|
return imgui.KeyRightArrow
|
||||||
|
case sdl.SCANCODE_UP:
|
||||||
|
return imgui.KeyUpArrow
|
||||||
|
case sdl.SCANCODE_DOWN:
|
||||||
|
return imgui.KeyDownArrow
|
||||||
|
case sdl.SCANCODE_PAGEUP:
|
||||||
|
return imgui.KeyPageUp
|
||||||
|
case sdl.SCANCODE_PAGEDOWN:
|
||||||
|
return imgui.KeyPageDown
|
||||||
|
case sdl.SCANCODE_HOME:
|
||||||
|
return imgui.KeyHome
|
||||||
|
case sdl.SCANCODE_END:
|
||||||
|
return imgui.KeyEnd
|
||||||
|
case sdl.SCANCODE_INSERT:
|
||||||
|
return imgui.KeyInsert
|
||||||
|
case sdl.SCANCODE_DELETE:
|
||||||
|
return imgui.KeyDelete
|
||||||
|
case sdl.SCANCODE_BACKSPACE:
|
||||||
|
return imgui.KeyBackspace
|
||||||
|
case sdl.SCANCODE_SPACE:
|
||||||
|
return imgui.KeySpace
|
||||||
|
case sdl.SCANCODE_RETURN:
|
||||||
|
return imgui.KeyEnter
|
||||||
|
case sdl.SCANCODE_ESCAPE:
|
||||||
|
return imgui.KeyEscape
|
||||||
|
case sdl.SCANCODE_APOSTROPHE:
|
||||||
|
return imgui.KeyApostrophe
|
||||||
|
case sdl.SCANCODE_COMMA:
|
||||||
|
return imgui.KeyComma
|
||||||
|
case sdl.SCANCODE_MINUS:
|
||||||
|
return imgui.KeyMinus
|
||||||
|
case sdl.SCANCODE_PERIOD:
|
||||||
|
return imgui.KeyPeriod
|
||||||
|
case sdl.SCANCODE_SLASH:
|
||||||
|
return imgui.KeySlash
|
||||||
|
case sdl.SCANCODE_SEMICOLON:
|
||||||
|
return imgui.KeySemicolon
|
||||||
|
case sdl.SCANCODE_EQUALS:
|
||||||
|
return imgui.KeyEqual
|
||||||
|
case sdl.SCANCODE_LEFTBRACKET:
|
||||||
|
return imgui.KeyLeftBracket
|
||||||
|
case sdl.SCANCODE_BACKSLASH:
|
||||||
|
return imgui.KeyBackslash
|
||||||
|
case sdl.SCANCODE_RIGHTBRACKET:
|
||||||
|
return imgui.KeyRightBracket
|
||||||
|
case sdl.SCANCODE_GRAVE:
|
||||||
|
return imgui.KeyGraveAccent
|
||||||
|
case sdl.SCANCODE_CAPSLOCK:
|
||||||
|
return imgui.KeyCapsLock
|
||||||
|
case sdl.SCANCODE_SCROLLLOCK:
|
||||||
|
return imgui.KeyScrollLock
|
||||||
|
case sdl.SCANCODE_NUMLOCKCLEAR:
|
||||||
|
return imgui.KeyNumLock
|
||||||
|
case sdl.SCANCODE_PRINTSCREEN:
|
||||||
|
return imgui.KeyPrintScreen
|
||||||
|
case sdl.SCANCODE_PAUSE:
|
||||||
|
return imgui.KeyPause
|
||||||
|
case sdl.SCANCODE_KP_0:
|
||||||
|
return imgui.KeyKeypad0
|
||||||
|
case sdl.SCANCODE_KP_1:
|
||||||
|
return imgui.KeyKeypad1
|
||||||
|
case sdl.SCANCODE_KP_2:
|
||||||
|
return imgui.KeyKeypad2
|
||||||
|
case sdl.SCANCODE_KP_3:
|
||||||
|
return imgui.KeyKeypad3
|
||||||
|
case sdl.SCANCODE_KP_4:
|
||||||
|
return imgui.KeyKeypad4
|
||||||
|
case sdl.SCANCODE_KP_5:
|
||||||
|
return imgui.KeyKeypad5
|
||||||
|
case sdl.SCANCODE_KP_6:
|
||||||
|
return imgui.KeyKeypad6
|
||||||
|
case sdl.SCANCODE_KP_7:
|
||||||
|
return imgui.KeyKeypad7
|
||||||
|
case sdl.SCANCODE_KP_8:
|
||||||
|
return imgui.KeyKeypad8
|
||||||
|
case sdl.SCANCODE_KP_9:
|
||||||
|
return imgui.KeyKeypad9
|
||||||
|
case sdl.SCANCODE_KP_PERIOD:
|
||||||
|
return imgui.KeyKeypadDecimal
|
||||||
|
case sdl.SCANCODE_KP_DIVIDE:
|
||||||
|
return imgui.KeyKeypadDivide
|
||||||
|
case sdl.SCANCODE_KP_MULTIPLY:
|
||||||
|
return imgui.KeyKeypadMultiply
|
||||||
|
case sdl.SCANCODE_KP_MINUS:
|
||||||
|
return imgui.KeyKeypadSubtract
|
||||||
|
case sdl.SCANCODE_KP_PLUS:
|
||||||
|
return imgui.KeyKeypadAdd
|
||||||
|
case sdl.SCANCODE_KP_ENTER:
|
||||||
|
return imgui.KeyKeypadEnter
|
||||||
|
case sdl.SCANCODE_KP_EQUALS:
|
||||||
|
return imgui.KeyKeypadEqual
|
||||||
|
case sdl.SCANCODE_LSHIFT:
|
||||||
|
return imgui.KeyLeftShift
|
||||||
|
case sdl.SCANCODE_LCTRL:
|
||||||
|
return imgui.KeyLeftCtrl
|
||||||
|
case sdl.SCANCODE_LALT:
|
||||||
|
return imgui.KeyLeftAlt
|
||||||
|
case sdl.SCANCODE_LGUI:
|
||||||
|
return imgui.KeyLeftSuper
|
||||||
|
case sdl.SCANCODE_RSHIFT:
|
||||||
|
return imgui.KeyRightShift
|
||||||
|
case sdl.SCANCODE_RCTRL:
|
||||||
|
return imgui.KeyRightCtrl
|
||||||
|
case sdl.SCANCODE_RALT:
|
||||||
|
return imgui.KeyRightAlt
|
||||||
|
case sdl.SCANCODE_RGUI:
|
||||||
|
return imgui.KeyRightSuper
|
||||||
|
case sdl.SCANCODE_MENU:
|
||||||
|
return imgui.KeyMenu
|
||||||
|
case sdl.SCANCODE_0:
|
||||||
|
return imgui.Key0
|
||||||
|
case sdl.SCANCODE_1:
|
||||||
|
return imgui.Key1
|
||||||
|
case sdl.SCANCODE_2:
|
||||||
|
return imgui.Key2
|
||||||
|
case sdl.SCANCODE_3:
|
||||||
|
return imgui.Key3
|
||||||
|
case sdl.SCANCODE_4:
|
||||||
|
return imgui.Key4
|
||||||
|
case sdl.SCANCODE_5:
|
||||||
|
return imgui.Key5
|
||||||
|
case sdl.SCANCODE_6:
|
||||||
|
return imgui.Key6
|
||||||
|
case sdl.SCANCODE_7:
|
||||||
|
return imgui.Key7
|
||||||
|
case sdl.SCANCODE_8:
|
||||||
|
return imgui.Key8
|
||||||
|
case sdl.SCANCODE_9:
|
||||||
|
return imgui.Key9
|
||||||
|
case sdl.SCANCODE_A:
|
||||||
|
return imgui.KeyA
|
||||||
|
case sdl.SCANCODE_B:
|
||||||
|
return imgui.KeyB
|
||||||
|
case sdl.SCANCODE_C:
|
||||||
|
return imgui.KeyC
|
||||||
|
case sdl.SCANCODE_D:
|
||||||
|
return imgui.KeyD
|
||||||
|
case sdl.SCANCODE_E:
|
||||||
|
return imgui.KeyE
|
||||||
|
case sdl.SCANCODE_F:
|
||||||
|
return imgui.KeyF
|
||||||
|
case sdl.SCANCODE_G:
|
||||||
|
return imgui.KeyG
|
||||||
|
case sdl.SCANCODE_H:
|
||||||
|
return imgui.KeyH
|
||||||
|
case sdl.SCANCODE_I:
|
||||||
|
return imgui.KeyI
|
||||||
|
case sdl.SCANCODE_J:
|
||||||
|
return imgui.KeyJ
|
||||||
|
case sdl.SCANCODE_K:
|
||||||
|
return imgui.KeyK
|
||||||
|
case sdl.SCANCODE_L:
|
||||||
|
return imgui.KeyL
|
||||||
|
case sdl.SCANCODE_M:
|
||||||
|
return imgui.KeyM
|
||||||
|
case sdl.SCANCODE_N:
|
||||||
|
return imgui.KeyN
|
||||||
|
case sdl.SCANCODE_O:
|
||||||
|
return imgui.KeyO
|
||||||
|
case sdl.SCANCODE_P:
|
||||||
|
return imgui.KeyP
|
||||||
|
case sdl.SCANCODE_Q:
|
||||||
|
return imgui.KeyQ
|
||||||
|
case sdl.SCANCODE_R:
|
||||||
|
return imgui.KeyR
|
||||||
|
case sdl.SCANCODE_S:
|
||||||
|
return imgui.KeyS
|
||||||
|
case sdl.SCANCODE_T:
|
||||||
|
return imgui.KeyT
|
||||||
|
case sdl.SCANCODE_U:
|
||||||
|
return imgui.KeyU
|
||||||
|
case sdl.SCANCODE_V:
|
||||||
|
return imgui.KeyV
|
||||||
|
case sdl.SCANCODE_W:
|
||||||
|
return imgui.KeyW
|
||||||
|
case sdl.SCANCODE_X:
|
||||||
|
return imgui.KeyX
|
||||||
|
case sdl.SCANCODE_Y:
|
||||||
|
return imgui.KeyY
|
||||||
|
case sdl.SCANCODE_Z:
|
||||||
|
return imgui.KeyZ
|
||||||
|
case sdl.SCANCODE_F1:
|
||||||
|
return imgui.KeyF1
|
||||||
|
case sdl.SCANCODE_F2:
|
||||||
|
return imgui.KeyF2
|
||||||
|
case sdl.SCANCODE_F3:
|
||||||
|
return imgui.KeyF3
|
||||||
|
case sdl.SCANCODE_F4:
|
||||||
|
return imgui.KeyF4
|
||||||
|
case sdl.SCANCODE_F5:
|
||||||
|
return imgui.KeyF5
|
||||||
|
case sdl.SCANCODE_F6:
|
||||||
|
return imgui.KeyF6
|
||||||
|
case sdl.SCANCODE_F7:
|
||||||
|
return imgui.KeyF7
|
||||||
|
case sdl.SCANCODE_F8:
|
||||||
|
return imgui.KeyF8
|
||||||
|
case sdl.SCANCODE_F9:
|
||||||
|
return imgui.KeyF9
|
||||||
|
case sdl.SCANCODE_F10:
|
||||||
|
return imgui.KeyF10
|
||||||
|
case sdl.SCANCODE_F11:
|
||||||
|
return imgui.KeyF11
|
||||||
|
case sdl.SCANCODE_F12:
|
||||||
|
return imgui.KeyF12
|
||||||
|
default:
|
||||||
|
return imgui.KeyNone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||