Compare commits

..

36 Commits

Author SHA1 Message Date
f1b6f3a7c0 Start function in Game interface 2022-02-27 11:21:15 +04:00
d1f47316ae Allow imgui within init 2022-02-27 11:11:20 +04:00
709dc062cc Day 15: Basic renderer+improve material system+lockosthread on init 2022-02-26 22:07:59 +04:00
660c41bc06 ensure dt is never zero 2022-02-24 14:34:21 +04:00
99f5548ce2 Fix imgui dt 2022-02-24 14:22:23 +04:00
5a54b1b465 Make all ImguiInfo public 2022-02-24 08:58:25 +04:00
36ac96d641 Allow configuration when loading fonts 2022-02-24 07:50:35 +04:00
577e6250a8 Return imgui font after set 2022-02-24 06:59:19 +04:00
c311a0981c Imgui setfont func 2022-02-24 06:51:24 +04:00
064a932037 Allow app to receive window event callbacks 2022-02-24 05:48:35 +04:00
841a6e989c Remove unused variables 2022-02-23 09:53:09 +04:00
94942e55a1 Cache uniform/attrib locations+display fps averages over a second 2022-02-23 09:05:21 +04:00
15087ac542 Specify RGBA8 as internal opengl format 2022-02-23 07:35:49 +04:00
f16407629a Correct loading of png textures to match opengl coords 2022-02-23 07:28:12 +04:00
592208d5c9 Complete engine game loop+abstract imgui 2022-02-12 22:20:38 +04:00
fd74d58ad3 Debug mode by default 2022-02-07 11:55:29 +04:00
4c2fca48b3 Ignore temp files 2022-02-07 11:47:34 +04:00
50c2ab650f Load texture from res folder 2022-02-05 23:59:41 +04:00
8e96cf7050 Reduce objects 2022-02-05 23:13:23 +04:00
56e10049e9 Textures + basic asset loading system+ uvs 2022-02-05 23:00:19 +04:00
2bfba880a9 Textured model 2022-02-05 19:27:14 +04:00
ffc9b6aa7c Fix crash 2022-02-05 19:24:55 +04:00
f49c6bc9bb Simplify interleave code 2022-01-27 10:34:51 +04:00
c989505aa7 Remove done todos 2022-01-27 09:01:16 +04:00
9ff1149191 workflow edit 2022-01-27 05:55:22 +04:00
42d99b3cc7 Badge 2022-01-27 05:51:05 +04:00
29832b9708 go build not go run 2022-01-27 05:50:27 +04:00
9a621d0669 Type in workflow 2022-01-27 05:48:11 +04:00
e893880f3b Update to assimp-go v0.4.2 2022-01-27 05:45:46 +04:00
cbe3d5111f Update readme and workflow to mention sdl2 2022-01-27 05:43:22 +04:00
3aa53852f3 Triggers 2022-01-27 05:37:58 +04:00
46483352c7 Github action 2022-01-27 05:33:37 +04:00
e38cd90a84 Cleaning naming and usage of buffers package 2022-01-26 08:48:50 +04:00
1109caef43 Support interleaved buffers 2022-01-26 08:04:50 +04:00
e1e617e4e4 Reset gl state after drawing UI in drawUI 2022-01-23 07:50:50 +04:00
6dee7b0f1d Starting work on Engine+Mesh+Material systems/packages 2022-01-22 22:23:44 +04:00
30 changed files with 1425 additions and 882 deletions

22
.github/workflows/run-nmage.yml vendored Executable file
View File

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

3
.gitignore vendored
View File

@ -14,4 +14,5 @@
# Dependency directories (remove the comment below to include it)
vendor/
.vscode/
imgui.ini
imgui.ini
*~

View File

@ -1,5 +1,7 @@
# nMage
[![build](https://github.com/bloeys/nmage/actions/workflows/run-nmage.yml/badge.svg)](https://github.com/bloeys/nmage/actions/workflows/run-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).
This project is being built with the goals being (in no particular order):
@ -17,6 +19,7 @@ To run the project you need:
* A C/C++ compiler installed and in your path
* Windows: [MingW](https://www.mingw-w64.org/downloads/#mingw-builds) or similar
* Mac/Linux: Should be installed by default, but if not try [GCC](https://gcc.gnu.org/) or [Clang](https://releases.llvm.org/download.html)
* Install SDL2 by following their [requirements](https://github.com/veandco/go-sdl2#requirements).
* Get the required [assimp-go](https://github.com/bloeys/assimp-go) DLLs/DyLibs and place them correctly by following the assimp-go [README](https://github.com/bloeys/assimp-go#using-assimp-go).
Then you can start nMage with `go run .`

View File

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

27
assets/assets.go Executable file
View File

@ -0,0 +1,27 @@
package assets
var (
Textures map[uint32]Texture = make(map[uint32]Texture)
TexturePaths map[string]uint32 = make(map[string]uint32)
)
func SetTexture(t Texture) {
if _, ok := TexturePaths[t.Path]; ok {
return
}
println("Loaded texture:", t.Path)
Textures[t.TexID] = t
TexturePaths[t.Path] = t.TexID
}
func GetTexture(texID uint32) (Texture, bool) {
tex, ok := Textures[texID]
return tex, ok
}
func GetTexturePath(path string) (Texture, bool) {
tex, ok := Textures[TexturePaths[path]]
return tex, ok
}

85
assets/textures.go Executable file
View File

@ -0,0 +1,85 @@
package assets
import (
"bytes"
"image/color"
"image/png"
"os"
"unsafe"
"github.com/go-gl/gl/v4.1-core/gl"
)
type ColorFormat int
const (
ColorFormat_RGBA8 ColorFormat = iota
)
type Texture struct {
Path string
TexID uint32
Width int32
Height int32
Pixels []byte
}
func LoadPNGTexture(file string) (Texture, error) {
if tex, ok := GetTexturePath(file); ok {
return tex, nil
}
//Load from disk
fileBytes, err := os.ReadFile(file)
if err != nil {
return Texture{}, err
}
img, err := png.Decode(bytes.NewReader(fileBytes))
if err != nil {
return Texture{}, err
}
tex := Texture{
Path: file,
Width: int32(img.Bounds().Dx()),
Height: int32(img.Bounds().Dy()),
Pixels: make([]byte, img.Bounds().Dx()*img.Bounds().Dy()*4),
}
//NOTE: Load bottom left to top right because this is the texture coordinate system used by OpenGL
//NOTE: We only support 8-bit channels (32-bit colors) for now
i := 0
for y := img.Bounds().Dy() - 1; y >= 0; y-- {
for x := 0; x < img.Bounds().Dx(); x++ {
c := color.NRGBAModel.Convert(img.At(x, y)).(color.NRGBA)
tex.Pixels[i] = c.R
tex.Pixels[i+1] = c.G
tex.Pixels[i+2] = c.B
tex.Pixels[i+3] = c.A
i += 4
}
}
//Prepare opengl stuff
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_MIPMAP_LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
// load and generate the texture
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, tex.Width, tex.Height, 0, gl.RGBA, gl.UNSIGNED_BYTE, unsafe.Pointer(&tex.Pixels[0]))
gl.GenerateMipmap(gl.TEXTURE_2D)
SetTexture(tex)
return tex, nil
}

33
buffers/buf_usage.go Executable file
View File

@ -0,0 +1,33 @@
package buffers
import (
"fmt"
"github.com/bloeys/nmage/asserts"
"github.com/go-gl/gl/v4.1-core/gl"
)
type BufUsage int
const (
//Buffer is set only once and used many times
BufUsage_Static BufUsage = iota
//Buffer is changed a lot and used many times
BufUsage_Dynamic
//Buffer is set only once and used by the GPU at most a few times
BufUsage_Stream
)
func (b BufUsage) ToGL() uint32 {
switch b {
case BufUsage_Static:
return gl.STATIC_DRAW
case BufUsage_Dynamic:
return gl.DYNAMIC_DRAW
case BufUsage_Stream:
return gl.STREAM_DRAW
}
asserts.T(false, fmt.Sprintf("Unexpected BufUsage value '%v'", b))
return 0
}

View File

@ -5,169 +5,85 @@ import (
"github.com/go-gl/gl/v4.1-core/gl"
)
type BufGLType int
const (
BufGLTypeUnknown BufGLType = 0
//Generic array of data. Should be used for most data like vertex positions, vertex colors etc.
BufGLTypeArray BufGLType = gl.ARRAY_BUFFER
BufGLTypeIndices BufGLType = gl.ELEMENT_ARRAY_BUFFER
)
type BufType int
const (
BufTypeUnknown BufType = iota
BufTypeVertPos
BufTypeColor
BufTypeIndex
BufTypeNormal
)
func (bt BufType) GetBufferGLType() BufGLType {
switch bt {
case BufTypeNormal:
fallthrough
case BufTypeColor:
fallthrough
case BufTypeVertPos:
return BufGLTypeArray
case BufTypeIndex:
return BufGLTypeIndices
default:
logging.WarnLog.Println("Unknown BufferType. BufferType: ", bt)
return BufGLTypeUnknown
}
}
type BufUsage int
const (
//Buffer is set only once and used many times
BufUsageStatic BufUsage = gl.STATIC_DRAW
//Buffer is changed a lot and used many times
BufUsageDynamic BufUsage = gl.DYNAMIC_DRAW
//Buffer is set only once and used by the GPU at most a few times
BufUsageStream BufUsage = gl.STREAM_DRAW
)
type Buffer struct {
ID uint32
Type BufType
GLType BufGLType
DataTypeInfo
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
//DataLen is the number of elements in the uploaded to the buffer
DataLen int32
layout []Element
}
func (b *Buffer) Activate() {
gl.BindBuffer(uint32(b.GLType), b.ID)
func (b *Buffer) Bind() {
gl.BindVertexArray(b.VAOID)
}
func (b *Buffer) Deactivate() {
gl.BindBuffer(uint32(b.GLType), 0)
}
type BufferObject struct {
VAOID uint32
VertPosBuf *Buffer
NormalBuf *Buffer
ColorBuf *Buffer
IndexBuf *Buffer
}
func (bo *BufferObject) GenBuffer(data []float32, bufUsage BufUsage, bufType BufType, bufDataType DataType) {
gl.BindVertexArray(bo.VAOID)
//Create vertex buffer object
var vboID uint32
gl.GenBuffers(1, &vboID)
if vboID == 0 {
logging.ErrLog.Println("Failed to create openGL buffer")
}
buf := &Buffer{
ID: vboID,
Type: bufType,
GLType: bufType.GetBufferGLType(),
DataTypeInfo: GetDataTypeInfo(bufDataType),
DataLen: int32(len(data)),
}
bo.SetBuffer(buf)
//Fill buffer with data
gl.BindBuffer(uint32(buf.GLType), buf.ID)
gl.BufferData(uint32(buf.GLType), int(buf.DataTypeInfo.ElementSize)*len(data), gl.Ptr(data), uint32(bufUsage))
//Unbind everything
gl.BindVertexArray(0)
gl.BindBuffer(uint32(buf.GLType), 0)
}
func (bo *BufferObject) GenBufferUint32(data []uint32, bufUsage BufUsage, bufType BufType, bufDataType DataType) {
gl.BindVertexArray(bo.VAOID)
//Create vertex buffer object
var vboID uint32
gl.GenBuffers(1, &vboID)
if vboID == 0 {
logging.ErrLog.Println("Failed to create openGL buffer")
}
buf := &Buffer{
ID: vboID,
Type: bufType,
GLType: bufType.GetBufferGLType(),
DataTypeInfo: GetDataTypeInfo(bufDataType),
DataLen: int32(len(data)),
}
bo.SetBuffer(buf)
//Fill buffer with data
gl.BindBuffer(uint32(buf.GLType), buf.ID)
gl.BufferData(uint32(buf.GLType), int(buf.DataTypeInfo.ElementSize)*len(data), gl.Ptr(data), uint32(bufUsage))
//Unbind everything
gl.BindVertexArray(0)
gl.BindBuffer(uint32(buf.GLType), 0)
}
func (bo *BufferObject) SetBuffer(buf *Buffer) {
switch buf.Type {
case BufTypeVertPos:
bo.VertPosBuf = buf
case BufTypeNormal:
bo.NormalBuf = buf
case BufTypeColor:
bo.ColorBuf = buf
case BufTypeIndex:
bo.IndexBuf = buf
default:
logging.WarnLog.Println("Unknown buffer type in SetBuffer. Type:", buf.Type)
}
}
func (bo *BufferObject) Activate() {
gl.BindVertexArray(bo.VAOID)
}
func (bo *BufferObject) Deactivate() {
func (b *Buffer) UnBind() {
gl.BindVertexArray(0)
}
func NewBufferObject() *BufferObject {
func (b *Buffer) SetData(values []float32) {
var vaoID uint32
gl.GenVertexArrays(1, &vaoID)
if vaoID == 0 {
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
}
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()
}
}
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")
}
return &BufferObject{VAOID: vaoID}
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
}

View File

@ -1,92 +0,0 @@
package buffers
import (
"github.com/bloeys/nmage/logging"
"github.com/go-gl/gl/v4.1-core/gl"
)
type DataType int
const (
DataTypeUnknown DataType = iota
DataTypeUint32
DataTypeInt32
DataTypeFloat32
DataTypeFloat64
DataTypeVec2
DataTypeVec3
DataTypeVec4
)
type DataTypeInfo struct {
//ElementSize is size in bytes of one element (e.g. for vec3 its 4)
ElementSize int32
//ElementCount is number of elements (e.g. for vec3 its 3)
ElementCount int32
//ElementType is the type of each primitive (e.g. for vec3 its gl.FLOAT)
ElementType uint32
//GLType is the type of the variable represented (e.g. for vec3 its gl.FLOAT_VEC2)
GLType uint32
}
//GetSize returns the total size in bytes (e.g. for vec3 its 4*3)
func (dti *DataTypeInfo) GetSize() int32 {
return dti.ElementSize * dti.ElementCount
}
func GetDataTypeInfo(dt DataType) DataTypeInfo {
switch dt {
case DataTypeUint32:
fallthrough
case DataTypeInt32:
return DataTypeInfo{
ElementSize: 4,
ElementCount: 1,
ElementType: gl.INT,
GLType: gl.INT,
}
case DataTypeFloat32:
return DataTypeInfo{
ElementSize: 4,
ElementCount: 1,
ElementType: gl.FLOAT,
GLType: gl.FLOAT,
}
case DataTypeFloat64:
return DataTypeInfo{
ElementSize: 8,
ElementCount: 1,
ElementType: gl.DOUBLE,
GLType: gl.DOUBLE,
}
case DataTypeVec2:
return DataTypeInfo{
ElementSize: 4,
ElementCount: 2,
ElementType: gl.FLOAT,
GLType: gl.FLOAT_VEC2,
}
case DataTypeVec3:
return DataTypeInfo{
ElementSize: 4,
ElementCount: 3,
ElementType: gl.FLOAT,
GLType: gl.FLOAT_VEC3,
}
case DataTypeVec4:
return DataTypeInfo{
ElementSize: 4,
ElementCount: 4,
ElementType: gl.FLOAT,
GLType: gl.FLOAT_VEC4,
}
default:
logging.WarnLog.Println("Unknown data type passed. DataType:", dt)
return DataTypeInfo{}
}
}

122
buffers/element.go Executable file
View File

@ -0,0 +1,122 @@
package buffers
import (
"fmt"
"github.com/bloeys/nmage/asserts"
"github.com/go-gl/gl/v4.1-core/gl"
)
//Element represents an element that makes up a buffer (e.g. Vec3 at an offset of 12 bytes)
type Element struct {
Offset int
ElementType
}
//ElementType is the type of an element thats makes up a buffer (e.g. Vec3)
type ElementType int
const (
DataTypeUnknown ElementType = iota
DataTypeUint32
DataTypeInt32
DataTypeFloat32
DataTypeVec2
DataTypeVec3
DataTypeVec4
)
func (dt ElementType) GLType() uint32 {
switch dt {
case DataTypeUint32:
return gl.UNSIGNED_INT
case DataTypeInt32:
return gl.INT
case DataTypeFloat32:
fallthrough
case DataTypeVec2:
fallthrough
case DataTypeVec3:
fallthrough
case DataTypeVec4:
return gl.FLOAT
default:
asserts.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
return 0
}
}
//CompSize returns the size in bytes for one component of the type (e.g. for Vec2 its 4)
func (dt ElementType) CompSize() int32 {
switch dt {
case DataTypeUint32:
fallthrough
case DataTypeFloat32:
fallthrough
case DataTypeInt32:
fallthrough
case DataTypeVec2:
fallthrough
case DataTypeVec3:
fallthrough
case DataTypeVec4:
return 4
default:
asserts.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
return 0
}
}
//CompCount returns the number of components in the element (e.g. for Vec2 its 2)
func (dt ElementType) CompCount() int32 {
switch dt {
case DataTypeUint32:
fallthrough
case DataTypeFloat32:
fallthrough
case DataTypeInt32:
return 1
case DataTypeVec2:
return 2
case DataTypeVec3:
return 3
case DataTypeVec4:
return 4
default:
asserts.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
return 0
}
}
//Size returns the total size in bytes (e.g. for vec3 its 3*4=12 bytes)
func (dt ElementType) Size() int32 {
switch dt {
case DataTypeUint32:
fallthrough
case DataTypeFloat32:
fallthrough
case DataTypeInt32:
return 4
case DataTypeVec2:
return 2 * 4
case DataTypeVec3:
return 3 * 4
case DataTypeVec4:
return 4 * 4
default:
asserts.T(false, fmt.Sprintf("Unknown data type passed. DataType '%v'", dt))
return 0
}
}

View File

@ -1,4 +1,4 @@
//go:build debug
//go:build !release
package consts

View File

@ -1,4 +1,4 @@
//go:build !debug
//go:build release
package consts

191
engine/engine.go Executable file
View File

@ -0,0 +1,191 @@
package engine
import (
"runtime"
"github.com/bloeys/nmage/input"
"github.com/bloeys/nmage/renderer"
"github.com/bloeys/nmage/timing"
"github.com/go-gl/gl/v4.1-core/gl"
"github.com/inkyblackness/imgui-go/v4"
"github.com/veandco/go-sdl2/sdl"
)
type Window struct {
SDLWin *sdl.Window
GlCtx sdl.GLContext
EventCallbacks []func(sdl.Event)
Rend renderer.Render
}
func (w *Window) handleInputs() {
input.EventLoopStart()
imIO := imgui.CurrentIO()
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
//Fire callbacks
for i := 0; i < len(w.EventCallbacks); i++ {
w.EventCallbacks[i](event)
}
//Internal processing
switch e := event.(type) {
case *sdl.MouseWheelEvent:
input.HandleMouseWheelEvent(e)
xDelta, yDelta := input.GetMouseWheelMotion()
imIO.AddMouseWheelDelta(float32(xDelta), float32(yDelta))
case *sdl.KeyboardEvent:
input.HandleKeyboardEvent(e)
if e.Type == sdl.KEYDOWN {
imIO.KeyPress(int(e.Keysym.Scancode))
} else if e.Type == sdl.KEYUP {
imIO.KeyRelease(int(e.Keysym.Scancode))
}
case *sdl.TextInputEvent:
imIO.AddInputCharacters(string(e.Text[:]))
case *sdl.MouseButtonEvent:
input.HandleMouseBtnEvent(e)
case *sdl.MouseMotionEvent:
input.HandleMouseMotionEvent(e)
case *sdl.WindowEvent:
if e.Event == sdl.WINDOWEVENT_SIZE_CHANGED {
w.handleWindowResize()
}
case *sdl.QuitEvent:
input.HandleQuitEvent(e)
}
}
// If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame.
x, y, _ := sdl.GetMouseState()
imIO.SetMousePosition(imgui.Vec2{X: float32(x), Y: float32(y)})
imIO.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() {
fbWidth, fbHeight := w.SDLWin.GLGetDrawableSize()
if fbWidth <= 0 || fbHeight <= 0 {
return
}
gl.Viewport(0, 0, fbWidth, fbHeight)
}
func (w *Window) Destroy() error {
return w.SDLWin.Destroy()
}
func Init() error {
runtime.LockOSThread()
timing.Init()
err := initSDL()
return err
}
func initSDL() error {
err := sdl.Init(sdl.INIT_EVERYTHING)
if err != nil {
return err
}
sdl.ShowCursor(1)
sdl.GLSetAttribute(sdl.MAJOR_VERSION, 4)
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_GREEN_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_BLUE_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_DOUBLEBUFFER, 1)
sdl.GLSetAttribute(sdl.GL_DEPTH_SIZE, 24)
sdl.GLSetAttribute(sdl.GL_STENCIL_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_CONTEXT_PROFILE_MASK, sdl.GL_CONTEXT_PROFILE_CORE)
return nil
}
func CreateOpenGLWindow(title string, x, y, width, height int32, flags WindowFlags, rend renderer.Render) (*Window, error) {
return createWindow(title, x, y, width, height, WindowFlags_OPENGL|flags, rend)
}
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)
}
func createWindow(title string, x, y, width, height int32, flags WindowFlags, rend renderer.Render) (*Window, error) {
if x == -1 && y == -1 {
x = sdl.WINDOWPOS_CENTERED
y = sdl.WINDOWPOS_CENTERED
}
sdlWin, err := sdl.CreateWindow(title, x, y, width, height, uint32(flags))
if err != nil {
return nil, err
}
win := &Window{
SDLWin: sdlWin,
EventCallbacks: make([]func(sdl.Event), 0),
Rend: rend,
}
win.GlCtx, err = sdlWin.GLCreateContext()
if err != nil {
return nil, err
}
err = initOpenGL()
if err != nil {
return nil, err
}
return win, err
}
func initOpenGL() error {
if err := gl.Init(); err != nil {
return err
}
gl.Enable(gl.DEPTH_TEST)
gl.Enable(gl.CULL_FACE)
gl.CullFace(gl.BACK)
gl.FrontFace(gl.CCW)
gl.ClearColor(0, 0, 0, 1)
return nil
}
func SetVSync(enabled bool) {
if enabled {
sdl.GLSetSwapInterval(1)
} else {
sdl.GLSetSwapInterval(0)
}
}

64
engine/game.go Executable file
View File

@ -0,0 +1,64 @@
package engine
import (
"github.com/bloeys/nmage/timing"
nmageimgui "github.com/bloeys/nmage/ui/imgui"
"github.com/go-gl/gl/v4.1-core/gl"
)
type Game interface {
Init()
Start()
FrameStart()
Update()
Render()
FrameEnd()
ShouldRun() bool
GetWindow() *Window
GetImGUI() nmageimgui.ImguiInfo
Deinit()
}
func Run(g Game) {
w := g.GetWindow()
ui := g.GetImGUI()
g.Init()
//Simulate an imgui frame during init so any imgui calls are allowed within init
tempWidth, tempHeight := w.SDLWin.GetSize()
tempFBWidth, tempFBHeight := w.SDLWin.GLGetDrawableSize()
ui.FrameStart(float32(tempWidth), float32(tempHeight))
g.Start()
ui.Render(float32(tempWidth), float32(tempHeight), tempFBWidth, tempFBHeight)
for g.ShouldRun() {
//PERF: Cache these
width, height := w.SDLWin.GetSize()
fbWidth, fbHeight := w.SDLWin.GLGetDrawableSize()
timing.FrameStarted()
w.handleInputs()
ui.FrameStart(float32(width), float32(height))
g.FrameStart()
g.Update()
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
g.Render()
ui.Render(float32(width), float32(height), fbWidth, fbHeight)
w.SDLWin.GLSwap()
g.FrameEnd()
w.Rend.FrameEnd()
timing.FrameEnded()
}
g.Deinit()
}

29
engine/windowflags.go Executable file
View File

@ -0,0 +1,29 @@
package engine
import "github.com/veandco/go-sdl2/sdl"
type WindowFlags int
const (
WindowFlags_FULLSCREEN WindowFlags = sdl.WINDOW_FULLSCREEN
WindowFlags_OPENGL WindowFlags = sdl.WINDOW_OPENGL
WindowFlags_SHOWN WindowFlags = sdl.WINDOW_SHOWN
WindowFlags_HIDDEN WindowFlags = sdl.WINDOW_HIDDEN
WindowFlags_BORDERLESS WindowFlags = sdl.WINDOW_BORDERLESS
WindowFlags_RESIZABLE WindowFlags = sdl.WINDOW_RESIZABLE
WindowFlags_MINIMIZED WindowFlags = sdl.WINDOW_MINIMIZED
WindowFlags_MAXIMIZED WindowFlags = sdl.WINDOW_MAXIMIZED
WindowFlags_INPUT_GRABBED WindowFlags = sdl.WINDOW_INPUT_GRABBED
WindowFlags_INPUT_FOCUS WindowFlags = sdl.WINDOW_INPUT_FOCUS
WindowFlags_MOUSE_FOCUS WindowFlags = sdl.WINDOW_MOUSE_FOCUS
WindowFlags_FULLSCREEN_DESKTOP WindowFlags = sdl.WINDOW_FULLSCREEN_DESKTOP
WindowFlags_FOREIGN WindowFlags = sdl.WINDOW_FOREIGN
WindowFlags_ALLOW_HIGHDPI WindowFlags = sdl.WINDOW_ALLOW_HIGHDPI
WindowFlags_MOUSE_CAPTURE WindowFlags = sdl.WINDOW_MOUSE_CAPTURE
WindowFlags_ALWAYS_ON_TOP WindowFlags = sdl.WINDOW_ALWAYS_ON_TOP
WindowFlags_SKIP_TASKBAR WindowFlags = sdl.WINDOW_SKIP_TASKBAR
WindowFlags_UTILITY WindowFlags = sdl.WINDOW_UTILITY
WindowFlags_TOOLTIP WindowFlags = sdl.WINDOW_TOOLTIP
WindowFlags_POPUP_MENU WindowFlags = sdl.WINDOW_POPUP_MENU
// WindowFlags_VULKAN WindowFlags = sdl.WINDOW_VULKAN
)

2
go.mod
View File

@ -7,7 +7,7 @@ require github.com/veandco/go-sdl2 v0.4.10
require github.com/go-gl/gl v0.0.0-20211025173605-bda47ffaa784
require (
github.com/bloeys/assimp-go v0.3.3
github.com/bloeys/assimp-go v0.4.2
github.com/bloeys/gglm v0.3.1
github.com/inkyblackness/imgui-go/v4 v4.3.0
)

8
go.sum
View File

@ -1,9 +1,5 @@
github.com/bloeys/assimp-go v0.3.1 h1:GANPXH8ER/4B/XsxZOw03GLZi2qKiWapSJ9dntrGoic=
github.com/bloeys/assimp-go v0.3.1/go.mod h1:my3yRxT7CfOztmvi+0svmwbaqw0KFrxaHxncoyaEIP0=
github.com/bloeys/assimp-go v0.3.2 h1:CsKnLloWZyn6uYNNaQE2Jq2Q+yH4d71A+CxbpU2flng=
github.com/bloeys/assimp-go v0.3.2/go.mod h1:my3yRxT7CfOztmvi+0svmwbaqw0KFrxaHxncoyaEIP0=
github.com/bloeys/assimp-go v0.3.3 h1:36Cqdsv/vVWg7mx6Kvu++1Z0SftdAQF4a+ApllpVT4M=
github.com/bloeys/assimp-go v0.3.3/go.mod h1:my3yRxT7CfOztmvi+0svmwbaqw0KFrxaHxncoyaEIP0=
github.com/bloeys/assimp-go v0.4.2 h1:ArVK74BCFcTO/rCGj2NgZG9xtbjnJdEn5npIeJx1Z04=
github.com/bloeys/assimp-go v0.4.2/go.mod h1:my3yRxT7CfOztmvi+0svmwbaqw0KFrxaHxncoyaEIP0=
github.com/bloeys/gglm v0.3.1 h1:Sy9upW7SBsBfDXrSmEhid3aQ+7J7itej+upwcxOnPMQ=
github.com/bloeys/gglm v0.3.1/go.mod h1:qwJQ0WzV191wAMwlGicbfbChbKoSedMk7gFFX6GnyOk=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=

View File

@ -31,10 +31,11 @@ type mouseWheelState struct {
}
var (
keyMap = make(map[sdl.Keycode]*keyState)
mouseBtnMap = make(map[int]*mouseBtnState)
mouseMotion = mouseMotionState{}
mouseWheel = mouseWheelState{}
keyMap = make(map[sdl.Keycode]*keyState)
mouseBtnMap = make(map[int]*mouseBtnState)
mouseMotion = mouseMotionState{}
mouseWheel = mouseWheelState{}
quitRequested bool
)
func EventLoopStart() {
@ -55,6 +56,16 @@ func EventLoopStart() {
mouseWheel.XDelta = 0
mouseWheel.YDelta = 0
quitRequested = false
}
func HandleQuitEvent(e *sdl.QuitEvent) {
quitRequested = true
}
func IsQuitClicked() bool {
return quitRequested
}
func HandleKeyboardEvent(e *sdl.KeyboardEvent) {

649
main.go
View File

@ -2,103 +2,81 @@ package main
import (
"fmt"
"runtime"
"github.com/bloeys/assimp-go/asig"
"github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/asserts"
"github.com/bloeys/nmage/buffers"
"github.com/bloeys/nmage/assets"
"github.com/bloeys/nmage/engine"
"github.com/bloeys/nmage/input"
"github.com/bloeys/nmage/logging"
"github.com/bloeys/nmage/shaders"
"github.com/bloeys/nmage/materials"
"github.com/bloeys/nmage/meshes"
"github.com/bloeys/nmage/renderer/rend3dgl"
"github.com/bloeys/nmage/timing"
"github.com/go-gl/gl/v4.1-core/gl"
nmageimgui "github.com/bloeys/nmage/ui/imgui"
"github.com/inkyblackness/imgui-go/v4"
"github.com/veandco/go-sdl2/sdl"
)
//TODO:
//Make a window/engine class
//Mesh class
//Object
//Abstract UI
//Textures
//Proper Asset loading
//Rework buffers package
//Interleaved or packed buffers (xyzxyzxyz OR xxxyyyzzz)
//Audio
//TODO: Tasks:
// Build simple game
// Integrate physx
// Entities and components
// Camera class
type ImguiInfo struct {
imCtx *imgui.Context
vaoID uint32
vboID uint32
indexBufID uint32
texID uint32
}
//Low Priority:
// Renderer batching
// Scene graph
// Separate engine loop from rendering loop? or leave it to the user?
// Abstract keys enum away from sdl
// Proper Asset loading
// Audio
// Frustum culling
// Material system editor with fields automatically extracted from the shader
var (
winWidth int32 = 1280
winHeight int32 = 720
isRunning bool = true
window *sdl.Window
window *engine.Window
simpleShader shaders.ShaderProgram
imShader shaders.ShaderProgram
bo *buffers.BufferObject
modelMat = gglm.NewTrMatId()
projMat = &gglm.Mat4{}
imguiInfo *ImguiInfo
simpleMat *materials.Material
cubeMesh *meshes.Mesh
modelMat = gglm.NewTrMatId()
projMat = &gglm.Mat4{}
camPos = gglm.NewVec3(0, 0, -10)
camForward = gglm.NewVec3(0, 0, 1)
lightPos1 = gglm.NewVec3(2, 2, 0)
lightColor1 = gglm.NewVec3(1, 1, 1)
)
func main() {
type OurGame struct {
Win *engine.Window
ImGUIInfo nmageimgui.ImguiInfo
Quitting bool
}
runtime.LockOSThread()
func (g *OurGame) Init() {
err := initSDL()
//Create materials
simpleMat = materials.NewMaterial("Simple Mat", "./res/shaders/simple")
//Load meshes
var err error
cubeMesh, err = meshes.NewMesh("Cube", "./res/models/tex-cube.fbx", asig.PostProcess(0))
if err != nil {
logging.ErrLog.Fatalln("Failed to init SDL. Err:", err)
logging.ErrLog.Fatalln("Failed to load cube mesh. Err: ", err)
}
window, err = sdl.CreateWindow("Go SDL Engine", sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, winWidth, winHeight, sdl.WINDOW_OPENGL|sdl.WINDOW_RESIZABLE)
//Load textures
tex, err := assets.LoadPNGTexture("./res/textures/Low poly planet.png")
if err != nil {
logging.ErrLog.Fatalln("Failed to create window. Err: ", err)
}
defer window.Destroy()
glCtx, err := window.GLCreateContext()
if err != nil {
logging.ErrLog.Fatalln("Failed to create OpenGL context. Err: ", err)
}
defer sdl.GLDeleteContext(glCtx)
err = initOpenGL()
if err != nil {
logging.ErrLog.Fatalln(err)
logging.ErrLog.Fatalln("Failed to load texture. Err: ", err)
}
//Set vsync
sdl.GLSetSwapInterval(0)
loadShaders()
loadBuffers()
initImGUI()
//Enable vertex attributes
simpleShader.SetAttribute("vertPosIn", bo, bo.VertPosBuf)
simpleShader.EnableAttribute("vertPosIn")
simpleShader.SetAttribute("vertColorIn", bo, bo.ColorBuf)
simpleShader.EnableAttribute("vertColorIn")
simpleShader.SetAttribute("vertNormalIn", bo, bo.NormalBuf)
simpleShader.EnableAttribute("vertNormalIn")
//Configure material
simpleMat.DiffuseTex = tex.TexID
simpleMat.SetAttribute(cubeMesh.Buf)
//Movement, scale and rotation
translationMat := gglm.NewTranslationMat(gglm.NewVec3(0, 0, 0))
@ -106,344 +84,38 @@ func main() {
rotMat := gglm.NewRotMat(gglm.NewQuatEuler(gglm.NewVec3(0, 0, 0).AsRad()))
modelMat.Mul(translationMat.Mul(rotMat.Mul(scaleMat)))
simpleShader.SetUnifMat4("modelMat", &modelMat.Mat4)
simpleMat.SetUnifMat4("modelMat", &modelMat.Mat4)
//Moves objects into the cameras view
updateViewMat()
//Perspective/Depth
projMat := gglm.Perspective(45*gglm.Deg2Rad, float32(winWidth)/float32(winHeight), 0.1, 500)
simpleShader.SetUnifMat4("projMat", projMat)
projMat := gglm.Perspective(45*gglm.Deg2Rad, float32(1280)/float32(720), 0.1, 500)
simpleMat.SetUnifMat4("projMat", projMat)
//Lights
simpleShader.SetUnifVec3("lightPos1", &lightPos1)
simpleShader.SetUnifVec3("lightColor1", &lightColor1)
//Game loop
timing.Init()
for isRunning {
timing.FrameStarted()
handleInputs()
runGameLogic()
draw()
timing.FrameEnded()
window.SetTitle(fmt.Sprintf("FPS: %.0f; Elapsed: %v", 1/timing.DT(), timing.ElapsedTime()))
}
simpleMat.SetUnifVec3("lightPos1", lightPos1)
simpleMat.SetUnifVec3("lightColor1", lightColor1)
}
func updateViewMat() {
targetPos := camPos.Clone().Add(camForward)
viewMat := gglm.LookAt(camPos, targetPos, gglm.NewVec3(0, 1, 0))
simpleShader.SetUnifMat4("viewMat", &viewMat.Mat4)
func (g *OurGame) Start() {
}
func initSDL() error {
err := sdl.Init(sdl.INIT_EVERYTHING)
if err != nil {
return err
}
sdl.ShowCursor(1)
sdl.GLSetAttribute(sdl.MAJOR_VERSION, 4)
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_GREEN_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_BLUE_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_DOUBLEBUFFER, 1)
sdl.GLSetAttribute(sdl.GL_DEPTH_SIZE, 24)
sdl.GLSetAttribute(sdl.GL_STENCIL_SIZE, 8)
sdl.GLSetAttribute(sdl.GL_CONTEXT_PROFILE_MASK, sdl.GL_CONTEXT_PROFILE_CORE)
return nil
func (g *OurGame) FrameStart() {
}
func initOpenGL() error {
func (g *OurGame) Update() {
if err := gl.Init(); err != nil {
return err
if input.IsQuitClicked() {
g.Quitting = true
}
gl.Enable(gl.DEPTH_TEST)
gl.Enable(gl.CULL_FACE)
gl.CullFace(gl.BACK)
gl.FrontFace(gl.CCW)
gl.ClearColor(0, 0, 0, 1)
return nil
}
func loadShaders() {
var err error
simpleShader, err = shaders.NewShaderProgram()
if err != nil {
logging.ErrLog.Fatalln("Failed to create new shader program. Err: ", err)
}
vertShader, err := shaders.LoadAndCompilerShader("./res/shaders/simple.vert.glsl", shaders.VertexShaderType)
if err != nil {
logging.ErrLog.Fatalln("Failed to create new shader. Err: ", err)
}
fragShader, err := shaders.LoadAndCompilerShader("./res/shaders/simple.frag.glsl", shaders.FragmentShaderType)
if err != nil {
logging.ErrLog.Fatalln("Failed to create new shader. Err: ", err)
}
simpleShader.AttachShader(vertShader)
simpleShader.AttachShader(fragShader)
simpleShader.Link()
//ImGUI shader
imShader, err = shaders.NewShaderProgram()
if err != nil {
logging.ErrLog.Fatalln("Failed to create new shader program. Err: ", err)
}
imguiVertShader, err := shaders.LoadAndCompilerShader("./res/shaders/imgui.vert.glsl", shaders.VertexShaderType)
if err != nil {
logging.ErrLog.Fatalln("Failed to create new shader. Err: ", err)
}
imguiFragShader, err := shaders.LoadAndCompilerShader("./res/shaders/imgui.frag.glsl", shaders.FragmentShaderType)
if err != nil {
logging.ErrLog.Fatalln("Failed to create new shader. Err: ", err)
}
imShader.AttachShader(imguiVertShader)
imShader.AttachShader(imguiFragShader)
imShader.Link()
}
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 {
asserts.True(len(faces[0].Indices) == 3, fmt.Sprintf("Face doesn't have 3 indices. Index count: %v\n", len(faces[0].Indices)))
uints := make([]uint32, len(faces)*3)
for i := 0; i < len(faces); i++ {
uints[i*3+0] = uint32(faces[i].Indices[0])
uints[i*3+1] = uint32(faces[i].Indices[1])
uints[i*3+2] = uint32(faces[i].Indices[2])
}
return uints
}
func loadBuffers() {
// scene, release, err := asig.ImportFile("./res/models/weird-cube.fbx", asig.PostProcessTriangulate)
scene, release, err := asig.ImportFile("./res/models/color-cube.fbx", asig.PostProcessTriangulate)
if err != nil {
logging.ErrLog.Panicln("Failed to load model. Err: " + err.Error())
}
release()
bo = buffers.NewBufferObject()
bo.GenBuffer(flattenVec3(scene.Meshes[0].Vertices), buffers.BufUsageStatic, buffers.BufTypeVertPos, buffers.DataTypeVec3)
bo.GenBuffer(flattenVec3(scene.Meshes[0].Normals), buffers.BufUsageStatic, buffers.BufTypeNormal, buffers.DataTypeVec3)
bo.GenBuffer(flattenVec4(scene.Meshes[0].ColorSets[0]), buffers.BufUsageStatic, buffers.BufTypeColor, buffers.DataTypeVec4)
bo.GenBufferUint32(flattenFaces(scene.Meshes[0].Faces), buffers.BufUsageStatic, buffers.BufTypeIndex, buffers.DataTypeUint32)
}
func initImGUI() {
imguiInfo = &ImguiInfo{
imCtx: imgui.CreateContext(nil),
}
imIO := imgui.CurrentIO()
imIO.SetBackendFlags(imIO.GetBackendFlags() | imgui.BackendFlagsRendererHasVtxOffset)
gl.GenVertexArrays(1, &imguiInfo.vaoID)
gl.GenBuffers(1, &imguiInfo.vboID)
gl.GenBuffers(1, &imguiInfo.indexBufID)
gl.GenTextures(1, &imguiInfo.texID)
// Upload font to gpu
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_MAG_FILTER, gl.LINEAR)
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
image := imIO.Fonts().TextureDataAlpha8()
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(image.Width), int32(image.Height), 0, gl.RED, gl.UNSIGNED_BYTE, image.Pixels)
// Store our identifier
imIO.Fonts().SetTextureID(imgui.TextureID(imguiInfo.texID))
//Shader attributes
imShader.Activate()
imShader.EnableAttribute("Position")
imShader.EnableAttribute("UV")
imShader.EnableAttribute("Color")
imShader.Deactivate()
//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)
}
}
func handleInputs() {
input.EventLoopStart()
imIO := imgui.CurrentIO()
for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() {
switch e := event.(type) {
case *sdl.MouseWheelEvent:
input.HandleMouseWheelEvent(e)
xDelta, yDelta := input.GetMouseWheelMotion()
imIO.AddMouseWheelDelta(float32(xDelta), float32(yDelta))
case *sdl.KeyboardEvent:
input.HandleKeyboardEvent(e)
if e.Type == sdl.KEYDOWN {
imIO.KeyPress(int(e.Keysym.Scancode))
} else if e.Type == sdl.KEYUP {
imIO.KeyRelease(int(e.Keysym.Scancode))
}
case *sdl.TextInputEvent:
imIO.AddInputCharacters(string(e.Text[:]))
case *sdl.MouseButtonEvent:
input.HandleMouseBtnEvent(e)
case *sdl.MouseMotionEvent:
input.HandleMouseMotionEvent(e)
case *sdl.WindowEvent:
//NOTE: SDL is not firing window resize, but is resizing the window by itself
// if e.Type != sdl.WINDOWEVENT_SIZE_CHANGED {
// continue
// }
// winWidth = e.Data1
// winHeight = e.Data2
// window.SetSize(int32(winWidth), int32(winHeight))
// projMat = gglm.Perspective(45*gglm.Deg2Rad, float32(winWidth)/float32(winHeight), 0.1, 20)
// simpleShader.SetUnifMat4("projMat", projMat)
case *sdl.QuitEvent:
isRunning = false
}
}
currWinWidth, currWinHeight := window.GetSize()
if winWidth != currWinWidth || winHeight != currWinHeight {
handleWindowResize(currWinWidth, currWinHeight)
}
// If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame.
x, y, _ := sdl.GetMouseState()
imIO.SetMousePosition(imgui.Vec2{X: float32(x), Y: float32(y)})
imIO.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 handleWindowResize(newWinWidth, newWinHeight int32) {
winWidth = newWinWidth
winHeight = newWinHeight
fbWidth, fbHeight := window.GLGetDrawableSize()
if fbWidth <= 0 || fbHeight <= 0 {
return
}
gl.Viewport(0, 0, fbWidth, fbHeight)
winWidth, winHeight := g.Win.SDLWin.GetSize()
projMat = gglm.Perspective(45*gglm.Deg2Rad, float32(winWidth)/float32(winHeight), 0.1, 20)
simpleShader.SetUnifMat4("projMat", projMat)
}
simpleMat.SetUnifMat4("projMat", projMat)
var time uint64 = 0
var name string = ""
var ambientColor gglm.Vec3 = *gglm.NewVec3(1, 1, 1)
var ambientColorStrength float32 = 0.1
var lightPos1 gglm.Vec3 = *gglm.NewVec3(2, 2, 0)
var lightColor1 gglm.Vec3 = *gglm.NewVec3(1, 1, 1)
func runGameLogic() {
var camSpeed float32 = 15.0
//Camera movement
var camSpeed float32 = 15
if input.KeyDown(sdl.K_w) {
camPos.Data[1] += camSpeed * timing.DT()
updateViewMat()
@ -469,157 +141,78 @@ func runGameLogic() {
updateViewMat()
}
modelMat.Rotate(10*timing.DT()*gglm.Deg2Rad, gglm.NewVec3(1, 1, 1).Normalize())
simpleShader.SetUnifMat4("modelMat", &modelMat.Mat4)
//ImGUI
imIO := imgui.CurrentIO()
imIO.SetDisplaySize(imgui.Vec2{X: float32(winWidth), Y: float32(winHeight)})
// Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution)
frequency := sdl.GetPerformanceFrequency()
currentTime := sdl.GetPerformanceCounter()
if time > 0 {
imIO.SetDeltaTime(float32(currentTime-time) / float32(frequency))
} else {
imIO.SetDeltaTime(1.0 / 60.0)
}
time = currentTime
imgui.NewFrame()
if imgui.SliderFloat3("Ambient Color", &ambientColor.Data, 0, 1) {
simpleShader.SetUnifVec3("ambientLightColor", &ambientColor)
//Rotating cubes
if input.KeyDown(sdl.K_SPACE) {
modelMat.Rotate(10*timing.DT()*gglm.Deg2Rad, gglm.NewVec3(1, 1, 1).Normalize())
simpleMat.SetUnifMat4("modelMat", &modelMat.Mat4)
}
if imgui.SliderFloat("Ambient Color Strength", &ambientColorStrength, 0, 1) {
simpleShader.SetUnifFloat32("ambientStrength", ambientColorStrength)
}
if imgui.SliderFloat3("Light Pos 1", &lightPos1.Data, -10, 10) {
simpleShader.SetUnifVec3("lightPos1", &lightPos1)
}
if imgui.SliderFloat3("Light Color 1", &lightColor1.Data, 0, 1) {
simpleShader.SetUnifVec3("lightColor1", &lightColor1)
}
imgui.Render()
imgui.DragFloat3("Cam Pos", &camPos.Data)
}
func draw() {
func (g *OurGame) Render() {
gl.Disable(gl.SCISSOR_TEST)
gl.Enable(gl.CULL_FACE)
gl.Enable(gl.DEPTH_TEST)
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
simpleShader.Activate()
//DRAW
bo.Activate()
tempModelMat := modelMat.Clone()
rowSize := 10
rowSize := 100
for y := 0; y < rowSize; y++ {
for x := 0; x < rowSize; x++ {
simpleShader.SetUnifMat4("modelMat", &tempModelMat.Translate(gglm.NewVec3(-1, 0, 0)).Mat4)
gl.DrawElements(gl.TRIANGLES, int32(bo.IndexBuf.DataLen), gl.UNSIGNED_INT, gl.PtrOffset(0))
tempModelMat.Translate(gglm.NewVec3(-1, 0, 0))
window.Rend.Draw(cubeMesh, tempModelMat, simpleMat)
}
simpleShader.SetUnifMat4("modelMat", &tempModelMat.Translate(gglm.NewVec3(float32(rowSize), -1, 0)).Mat4)
tempModelMat.Translate(gglm.NewVec3(float32(rowSize), -1, 0))
}
simpleShader.SetUnifMat4("modelMat", &modelMat.Mat4)
bo.Deactivate()
drawUI()
window.GLSwap()
g.GetWindow().SDLWin.SetTitle(fmt.Sprint("nMage (", timing.GetAvgFPS(), " fps)"))
}
func drawUI() {
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
fbWidth, fbHeight := window.GLGetDrawableSize()
if fbWidth <= 0 || fbHeight <= 0 {
return
}
drawData := imgui.RenderedDrawData()
drawData.ScaleClipRects(imgui.Vec2{
X: float32(fbWidth) / float32(winWidth),
Y: float32(fbHeight) / float32(winHeight),
})
// Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill
gl.Enable(gl.BLEND)
gl.BlendEquation(gl.FUNC_ADD)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
gl.Disable(gl.CULL_FACE)
gl.Disable(gl.DEPTH_TEST)
gl.Enable(gl.SCISSOR_TEST)
gl.PolygonMode(gl.FRONT_AND_BACK, gl.FILL)
// Setup viewport, orthographic projection matrix
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right).
// DisplayMin is typically (0,0) for single viewport apps.
imShader.Activate()
gl.Uniform1i(gl.GetUniformLocation(imShader.ID, gl.Str("Texture\x00")), 0)
//PERF: only update the ortho matrix on window resize
orthoMat := gglm.Ortho(0, float32(winWidth), 0, float32(winHeight), 0, 20)
imShader.SetUnifMat4("ProjMtx", &orthoMat.Mat4)
gl.BindSampler(0, 0) // Rely on combined texture/sampler state.
// Recreate the VAO every time
// (This is to easily allow multiple GL contexts. VAO are not shared among GL contexts, and
// we don't track creation/deletion of windows so we don't have an obvious key to use to cache them.)
gl.BindVertexArray(imguiInfo.vaoID)
gl.BindBuffer(gl.ARRAY_BUFFER, imguiInfo.vboID)
vertexSize, vertexOffsetPos, vertexOffsetUv, vertexOffsetCol := imgui.VertexBufferLayout()
imShader.EnableAttribute("Position")
imShader.EnableAttribute("UV")
imShader.EnableAttribute("Color")
gl.VertexAttribPointerWithOffset(uint32(imShader.GetAttribLoc("Position")), 2, gl.FLOAT, false, int32(vertexSize), uintptr(vertexOffsetPos))
gl.VertexAttribPointerWithOffset(uint32(imShader.GetAttribLoc("UV")), 2, gl.FLOAT, false, int32(vertexSize), uintptr(vertexOffsetUv))
gl.VertexAttribPointerWithOffset(uint32(imShader.GetAttribLoc("Color")), 4, gl.UNSIGNED_BYTE, true, int32(vertexSize), uintptr(vertexOffsetCol))
indexSize := imgui.IndexBufferLayout()
drawType := gl.UNSIGNED_SHORT
if indexSize == 4 {
drawType = gl.UNSIGNED_INT
}
// Draw
for _, list := range drawData.CommandLists() {
vertexBuffer, vertexBufferSize := list.VertexBuffer()
gl.BindBuffer(gl.ARRAY_BUFFER, imguiInfo.vboID)
gl.BufferData(gl.ARRAY_BUFFER, vertexBufferSize, vertexBuffer, gl.STREAM_DRAW)
indexBuffer, indexBufferSize := list.IndexBuffer()
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, imguiInfo.indexBufID)
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, indexBufferSize, indexBuffer, gl.STREAM_DRAW)
for _, cmd := range list.Commands() {
if cmd.HasUserCallback() {
cmd.CallUserCallback(list)
} else {
gl.BindTexture(gl.TEXTURE_2D, imguiInfo.texID)
// gl.BindTexture(gl.TEXTURE_2D, uint32(cmd.TextureID()))
clipRect := cmd.ClipRect()
gl.Scissor(int32(clipRect.X), int32(fbHeight)-int32(clipRect.W), int32(clipRect.Z-clipRect.X), int32(clipRect.W-clipRect.Y))
gl.DrawElementsBaseVertex(gl.TRIANGLES, int32(cmd.ElementCount()), uint32(drawType), gl.PtrOffset(cmd.IndexOffset()*indexSize), int32(cmd.VertexOffset()))
}
}
}
gl.BindVertexArray(imguiInfo.vaoID)
gl.BindBuffer(gl.ARRAY_BUFFER, imguiInfo.vboID)
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, imguiInfo.indexBufID)
func (g *OurGame) FrameEnd() {
}
func (g *OurGame) ShouldRun() bool {
return !g.Quitting
}
func (g *OurGame) GetWindow() *engine.Window {
return g.Win
}
func (g *OurGame) GetImGUI() nmageimgui.ImguiInfo {
return g.ImGUIInfo
}
func (g *OurGame) Deinit() {
g.Win.Destroy()
}
func main() {
//Init engine
err := engine.Init()
if err != nil {
logging.ErrLog.Fatalln("Failed to init nMage. Err:", err)
}
//Create window
window, err = engine.CreateOpenGLWindowCentered("nMage", 1280, 720, engine.WindowFlags_RESIZABLE, rend3dgl.NewRend3DGL())
if err != nil {
logging.ErrLog.Fatalln("Failed to create window. Err: ", err)
}
defer window.Destroy()
engine.SetVSync(false)
game := &OurGame{
Win: window,
ImGUIInfo: nmageimgui.NewImGUI(),
}
engine.Run(game)
return
}
func updateViewMat() {
targetPos := camPos.Clone().Add(camForward)
viewMat := gglm.LookAt(camPos, targetPos, gglm.NewVec3(0, 1, 0))
simpleMat.SetUnifMat4("viewMat", &viewMat.Mat4)
}

147
materials/material.go Executable file
View File

@ -0,0 +1,147 @@
package materials
import (
"github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/asserts"
"github.com/bloeys/nmage/buffers"
"github.com/bloeys/nmage/logging"
"github.com/bloeys/nmage/shaders"
"github.com/go-gl/gl/v4.1-core/gl"
)
type Material struct {
Name string
ShaderProg shaders.ShaderProgram
DiffuseTex uint32
UnifLocs map[string]int32
AttribLocs map[string]int32
}
func (m *Material) Bind() {
gl.UseProgram(m.ShaderProg.ID)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, m.DiffuseTex)
}
func (m *Material) UnBind() {
gl.UseProgram(0)
//TODO: Should we unbind textures here? Are these two lines needed?
// gl.ActiveTexture(gl.TEXTURE0)
// gl.BindTexture(gl.TEXTURE_2D, 0)
}
func (m *Material) GetAttribLoc(attribName string) int32 {
loc, ok := m.AttribLocs[attribName]
if ok {
return loc
}
loc = gl.GetAttribLocation(m.ShaderProg.ID, gl.Str(attribName+"\x00"))
asserts.T(loc != -1, "Attribute '"+attribName+"' doesn't exist on material "+m.Name)
m.AttribLocs[attribName] = loc
return loc
}
func (m *Material) GetUnifLoc(uniformName string) int32 {
loc, ok := m.UnifLocs[uniformName]
if ok {
return loc
}
loc = gl.GetUniformLocation(m.ShaderProg.ID, gl.Str(uniformName+"\x00"))
asserts.T(loc != -1, "Uniform '"+uniformName+"' doesn't exist on material "+m.Name)
m.UnifLocs[uniformName] = loc
return loc
}
func (m *Material) SetAttribute(bufObj buffers.Buffer) {
bufObj.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, bufObj.BufID)
layout := bufObj.GetLayout()
for i := 0; i < len(layout); i++ {
gl.EnableVertexAttribArray(uint32(i))
gl.VertexAttribPointer(uint32(i), layout[i].ElementType.CompCount(), layout[i].ElementType.GLType(), false, bufObj.Stride, gl.PtrOffset(layout[i].Offset))
}
bufObj.UnBind()
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
}
func (m *Material) EnableAttribute(attribName string) {
gl.EnableVertexAttribArray(uint32(m.GetAttribLoc(attribName)))
}
func (m *Material) DisableAttribute(attribName string) {
gl.DisableVertexAttribArray(uint32(m.GetAttribLoc(attribName)))
}
func (m *Material) SetUnifInt32(uniformName string, val int32) {
gl.ProgramUniform1i(m.ShaderProg.ID, m.GetUnifLoc(uniformName), val)
}
func (m *Material) SetUnifFloat32(uniformName string, val float32) {
gl.ProgramUniform1f(m.ShaderProg.ID, m.GetUnifLoc(uniformName), val)
}
func (m *Material) SetUnifVec2(uniformName string, vec2 *gglm.Vec2) {
gl.ProgramUniform2fv(m.ShaderProg.ID, m.GetUnifLoc(uniformName), 1, &vec2.Data[0])
}
func (m *Material) SetUnifVec3(uniformName string, vec3 *gglm.Vec3) {
gl.ProgramUniform3fv(m.ShaderProg.ID, m.GetUnifLoc(uniformName), 1, &vec3.Data[0])
}
func (m *Material) SetUnifVec4(uniformName string, vec4 *gglm.Vec4) {
gl.ProgramUniform4fv(m.ShaderProg.ID, m.GetUnifLoc(uniformName), 1, &vec4.Data[0])
}
func (m *Material) SetUnifMat2(uniformName string, mat2 *gglm.Mat2) {
gl.ProgramUniformMatrix2fv(m.ShaderProg.ID, m.GetUnifLoc(uniformName), 1, false, &mat2.Data[0][0])
}
func (m *Material) SetUnifMat3(uniformName string, mat3 *gglm.Mat3) {
gl.ProgramUniformMatrix3fv(m.ShaderProg.ID, m.GetUnifLoc(uniformName), 1, false, &mat3.Data[0][0])
}
func (m *Material) SetUnifMat4(uniformName string, mat4 *gglm.Mat4) {
gl.ProgramUniformMatrix4fv(m.ShaderProg.ID, m.GetUnifLoc(uniformName), 1, false, &mat4.Data[0][0])
}
func (m *Material) Delete() {
gl.DeleteProgram(m.ShaderProg.ID)
}
func NewMaterial(matName, shaderPath string) *Material {
shdrProg, err := shaders.NewShaderProgram()
if err != nil {
logging.ErrLog.Fatalln("Failed to create new shader program. 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)}
}

165
meshes/mesh.go Executable file
View File

@ -0,0 +1,165 @@
package meshes
import (
"errors"
"fmt"
"github.com/bloeys/assimp-go/asig"
"github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/asserts"
"github.com/bloeys/nmage/buffers"
)
type Mesh struct {
Name string
Buf buffers.Buffer
}
func NewMesh(name, modelPath string, postProcessFlags asig.PostProcess) (*Mesh, error) {
scene, release, err := asig.ImportFile(modelPath, asig.PostProcessTriangulate|postProcessFlags)
if err != nil {
return nil, errors.New("Failed to load model. Err: " + err.Error())
}
defer release()
if len(scene.Meshes) == 0 {
return nil, errors.New("No meshes found in file: " + modelPath)
}
mesh := &Mesh{Name: name}
sceneMesh := scene.Meshes[0]
mesh.Buf = buffers.NewBuffer()
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...)
mesh.Buf.SetData(values)
mesh.Buf.SetIndexBufData(flattenFaces(sceneMesh.Faces))
return mesh, nil
}
func v3sToV2s(v3s []gglm.Vec3) []gglm.Vec2 {
v2s := make([]gglm.Vec2, len(v3s))
for i := 0; i < len(v3s); i++ {
v2s[i] = gglm.Vec2{
Data: [2]float32{v3s[i].X(), v3s[i].Y()},
}
}
return v2s
}
type arrToInterleave struct {
V2s []gglm.Vec2
V3s []gglm.Vec3
V4s []gglm.Vec4
}
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")
asserts.T(len(a.V2s) == 0 || len(a.V4s) == 0, "One array should be set in arrToInterleave, but both 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")
if len(a.V2s) > 0 {
return a.V2s[i].Data[:]
} else if len(a.V3s) > 0 {
return a.V3s[i].Data[:]
} else {
return a.V4s[i].Data[:]
}
}
func interleave(arrs ...arrToInterleave) []float32 {
asserts.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")
elementCount := 0
if len(arrs[0].V2s) > 0 {
elementCount = len(arrs[0].V2s)
} else if len(arrs[0].V3s) > 0 {
elementCount = len(arrs[0].V3s)
} else {
elementCount = len(arrs[0].V4s)
}
//Calculate final size of the float buffer
totalSize := 0
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")
if len(arrs[i].V2s) > 0 {
totalSize += len(arrs[i].V2s) * 2
} else if len(arrs[i].V3s) > 0 {
totalSize += len(arrs[i].V3s) * 3
} else {
totalSize += len(arrs[i].V4s) * 4
}
}
out := make([]float32, 0, totalSize)
for i := 0; i < elementCount; i++ {
for arrToUse := 0; arrToUse < len(arrs); arrToUse++ {
out = append(out, arrs[arrToUse].get(i)...)
}
}
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 {
asserts.T(len(faces[0].Indices) == 3, fmt.Sprintf("Face doesn't have 3 indices. Index count: %v\n", len(faces[0].Indices)))
uints := make([]uint32, len(faces)*3)
for i := 0; i < len(faces); i++ {
uints[i*3+0] = uint32(faces[i].Indices[0])
uints[i*3+1] = uint32(faces[i].Indices[1])
uints[i*3+2] = uint32(faces[i].Indices[2])
}
return uints
}

41
renderer/rend3dgl/rend3dgl.go Executable file
View File

@ -0,0 +1,41 @@
package rend3dgl
import (
"github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/materials"
"github.com/bloeys/nmage/meshes"
"github.com/bloeys/nmage/renderer"
"github.com/go-gl/gl/v4.1-core/gl"
)
var _ renderer.Render = &Rend3DGL{}
type Rend3DGL struct {
BoundMesh *meshes.Mesh
BoundMat *materials.Material
}
func (r3d *Rend3DGL) Draw(mesh *meshes.Mesh, trMat *gglm.TrMat, mat *materials.Material) {
if mesh != r3d.BoundMesh {
mesh.Buf.Bind()
r3d.BoundMesh = mesh
}
if mat != r3d.BoundMat {
mat.Bind()
r3d.BoundMat = mat
}
mat.SetUnifMat4("modelMat", &trMat.Mat4)
gl.DrawElements(gl.TRIANGLES, mesh.Buf.IndexBufCount, gl.UNSIGNED_INT, gl.PtrOffset(0))
}
func (r3d *Rend3DGL) FrameEnd() {
r3d.BoundMesh = nil
r3d.BoundMat = nil
}
func NewRend3DGL() *Rend3DGL {
return &Rend3DGL{}
}

12
renderer/renderer.go Executable file
View File

@ -0,0 +1,12 @@
package renderer
import (
"github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/materials"
"github.com/bloeys/nmage/meshes"
)
type Render interface {
Draw(mesh *meshes.Mesh, trMat *gglm.TrMat, mat *materials.Material)
FrameEnd()
}

BIN
res/models/tex-cube.fbx Executable file

Binary file not shown.

View File

@ -1,23 +1,26 @@
#version 410
in vec3 vertColor;
in vec3 vertNormal;
in vec3 fragPos;
out vec4 fragColor;
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 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos1 - fragPos);
float diffStrength = max(0.0, dot(normalize(vertNormal), lightDir));
vec3 finalAmbientColor = ambientLightColor * ambientStrength;
fragColor = vec4(vertColor * (finalAmbientColor + diffStrength*lightColor1), 1.0);
vec4 texColor = texture(diffTex, vertUV0);
fragColor = vec4(texColor.rgb * vertColor * (finalAmbientColor + diffStrength*lightColor1) , texColor.a);
}

View File

@ -1,11 +1,13 @@
#version 410
in vec3 vertPosIn;
in vec3 vertColorIn;
in vec3 vertNormalIn;
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 vertColor;
out vec3 vertNormal;
out vec2 vertUV0;
out vec3 vertColor;
out vec3 fragPos;
//MVP = Model View Projection
@ -15,8 +17,9 @@ uniform mat4 projMat;
void main()
{
vertColor = vertColorIn;
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);

BIN
res/textures/Low poly planet.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 468 KiB

View File

@ -1,8 +1,6 @@
package shaders
import (
"github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/buffers"
"github.com/bloeys/nmage/logging"
"github.com/go-gl/gl/v4.1-core/gl"
)
@ -37,74 +35,3 @@ func (sp *ShaderProgram) Link() {
gl.DeleteShader(sp.FragShaderID)
}
}
func (sp *ShaderProgram) Activate() {
gl.UseProgram(sp.ID)
}
func (sp *ShaderProgram) Deactivate() {
gl.UseProgram(0)
}
func (sp *ShaderProgram) GetAttribLoc(attribName string) int32 {
return gl.GetAttribLocation(sp.ID, gl.Str(attribName+"\x00"))
}
func (sp *ShaderProgram) SetAttribute(attribName string, bufObj *buffers.BufferObject, buf *buffers.Buffer) {
bufObj.Activate()
buf.Activate()
attribLoc := sp.GetAttribLoc(attribName)
gl.VertexAttribPointer(uint32(attribLoc), buf.ElementCount, buf.ElementType, false, buf.GetSize(), gl.PtrOffset(0))
bufObj.Activate()
buf.Deactivate()
}
func (sp *ShaderProgram) EnableAttribute(attribName string) {
gl.EnableVertexAttribArray(uint32(sp.GetAttribLoc(attribName)))
}
func (sp *ShaderProgram) DisableAttribute(attribName string) {
gl.DisableVertexAttribArray(uint32(sp.GetAttribLoc(attribName)))
}
func (sp *ShaderProgram) SetUnifFloat32(uniformName string, val float32) {
loc := gl.GetUniformLocation(sp.ID, gl.Str(uniformName+"\x00"))
gl.ProgramUniform1f(sp.ID, loc, val)
}
func (sp *ShaderProgram) SetUnifVec2(uniformName string, vec2 *gglm.Vec2) {
loc := gl.GetUniformLocation(sp.ID, gl.Str(uniformName+"\x00"))
gl.ProgramUniform2fv(sp.ID, loc, 1, &vec2.Data[0])
}
func (sp *ShaderProgram) SetUnifVec3(uniformName string, vec3 *gglm.Vec3) {
loc := gl.GetUniformLocation(sp.ID, gl.Str(uniformName+"\x00"))
gl.ProgramUniform3fv(sp.ID, loc, 1, &vec3.Data[0])
}
func (sp *ShaderProgram) SetUnifVec4(uniformName string, vec4 *gglm.Vec4) {
loc := gl.GetUniformLocation(sp.ID, gl.Str(uniformName+"\x00"))
gl.ProgramUniform4fv(sp.ID, loc, 1, &vec4.Data[0])
}
func (sp *ShaderProgram) SetUnifMat2(uniformName string, mat2 *gglm.Mat2) {
loc := gl.GetUniformLocation(sp.ID, gl.Str(uniformName+"\x00"))
gl.ProgramUniformMatrix2fv(sp.ID, loc, 1, false, &mat2.Data[0][0])
}
func (sp *ShaderProgram) SetUnifMat3(uniformName string, mat3 *gglm.Mat3) {
loc := gl.GetUniformLocation(sp.ID, gl.Str(uniformName+"\x00"))
gl.ProgramUniformMatrix3fv(sp.ID, loc, 1, false, &mat3.Data[0][0])
}
func (sp *ShaderProgram) SetUnifMat4(uniformName string, mat4 *gglm.Mat4) {
loc := gl.GetUniformLocation(sp.ID, gl.Str(uniformName+"\x00"))
gl.ProgramUniformMatrix4fv(sp.ID, loc, 1, false, &mat4.Data[0][0])
}
func (sp *ShaderProgram) Delete() {
gl.DeleteProgram(sp.ID)
}

View File

@ -1,11 +1,19 @@
package timing
import "time"
import (
"time"
)
var (
dt float32 = 0.01
frameStart time.Time
startTime time.Time
//fps calculator vars
dtAccum float32 = 1
lastElapsedTime uint64 = 0
framesSinceLastFPSUpdate uint = 0
avgFps float32 = 1
)
func Init() {
@ -13,18 +21,42 @@ func Init() {
}
func FrameStarted() {
frameStart = time.Now()
//fps stuff
dtAccum += dt
framesSinceLastFPSUpdate++
et := ElapsedTime()
if et > lastElapsedTime {
avgDT := dtAccum / float32(framesSinceLastFPSUpdate)
avgFps = 1 / avgDT
dtAccum = 0
framesSinceLastFPSUpdate = 0
}
lastElapsedTime = et
}
func FrameEnded() {
//Calculate new dt
dt = float32(time.Since(frameStart).Seconds())
if dt == 0 {
dt = float32(time.Microsecond)
}
}
//DT is frame deltatime in milliseconds
//DT is frame deltatime in seconds
func DT() float32 {
return dt
}
//GetAvgFPS returns the fps averaged over 1 second
func GetAvgFPS() float32 {
return avgFps
}
//ElapsedTime is time since game start
func ElapsedTime() uint64 {
return uint64(time.Since(startTime).Seconds())

218
ui/imgui/imgui.go Executable file
View File

@ -0,0 +1,218 @@
package nmageimgui
import (
"github.com/bloeys/gglm/gglm"
"github.com/bloeys/nmage/asserts"
"github.com/bloeys/nmage/materials"
"github.com/bloeys/nmage/timing"
"github.com/go-gl/gl/v4.1-core/gl"
"github.com/inkyblackness/imgui-go/v4"
"github.com/veandco/go-sdl2/sdl"
)
type ImguiInfo struct {
ImCtx *imgui.Context
Mat *materials.Material
VaoID uint32
VboID uint32
IndexBufID uint32
TexID uint32
}
func (i *ImguiInfo) FrameStart(winWidth, winHeight float32) {
if err := i.ImCtx.SetCurrent(); err != nil {
asserts.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
}
imIO := imgui.CurrentIO()
imIO.SetDisplaySize(imgui.Vec2{X: float32(winWidth), Y: float32(winHeight)})
imIO.SetDeltaTime(timing.DT())
imgui.NewFrame()
}
func (i *ImguiInfo) Render(winWidth, winHeight float32, fbWidth, fbHeight int32) {
if err := i.ImCtx.SetCurrent(); err != nil {
asserts.T(false, "Setting imgui ctx as current failed. Err: "+err.Error())
}
imgui.Render()
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
if fbWidth <= 0 || fbHeight <= 0 {
return
}
drawData := imgui.RenderedDrawData()
drawData.ScaleClipRects(imgui.Vec2{
X: float32(fbWidth) / float32(winWidth),
Y: float32(fbHeight) / float32(winHeight),
})
// Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, polygon fill
gl.Enable(gl.BLEND)
gl.BlendEquation(gl.FUNC_ADD)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
gl.Disable(gl.CULL_FACE)
gl.Disable(gl.DEPTH_TEST)
gl.Enable(gl.SCISSOR_TEST)
gl.PolygonMode(gl.FRONT_AND_BACK, gl.FILL)
// Setup viewport, orthographic projection matrix
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right).
// DisplayMin is typically (0,0) for single viewport apps.
i.Mat.Bind()
gl.Uniform1i(gl.GetUniformLocation(i.Mat.ShaderProg.ID, gl.Str("Texture\x00")), 0)
//PERF: only update the ortho matrix on window resize
orthoMat := gglm.Ortho(0, float32(winWidth), 0, float32(winHeight), 0, 20)
i.Mat.SetUnifMat4("ProjMtx", &orthoMat.Mat4)
gl.BindSampler(0, 0) // Rely on combined texture/sampler state.
// Recreate the VAO every time
// (This is to easily allow multiple GL contexts. VAO are not shared among GL contexts, and
// we don't track creation/deletion of windows so we don't have an obvious key to use to cache them.)
gl.BindVertexArray(i.VaoID)
gl.BindBuffer(gl.ARRAY_BUFFER, i.VboID)
vertexSize, vertexOffsetPos, vertexOffsetUv, vertexOffsetCol := imgui.VertexBufferLayout()
i.Mat.EnableAttribute("Position")
i.Mat.EnableAttribute("UV")
i.Mat.EnableAttribute("Color")
gl.VertexAttribPointerWithOffset(uint32(i.Mat.GetAttribLoc("Position")), 2, gl.FLOAT, false, int32(vertexSize), uintptr(vertexOffsetPos))
gl.VertexAttribPointerWithOffset(uint32(i.Mat.GetAttribLoc("UV")), 2, gl.FLOAT, false, int32(vertexSize), uintptr(vertexOffsetUv))
gl.VertexAttribPointerWithOffset(uint32(i.Mat.GetAttribLoc("Color")), 4, gl.UNSIGNED_BYTE, true, int32(vertexSize), uintptr(vertexOffsetCol))
indexSize := imgui.IndexBufferLayout()
drawType := gl.UNSIGNED_SHORT
if indexSize == 4 {
drawType = gl.UNSIGNED_INT
}
// Draw
for _, list := range drawData.CommandLists() {
vertexBuffer, vertexBufferSize := list.VertexBuffer()
gl.BindBuffer(gl.ARRAY_BUFFER, i.VboID)
gl.BufferData(gl.ARRAY_BUFFER, vertexBufferSize, vertexBuffer, gl.STREAM_DRAW)
indexBuffer, indexBufferSize := list.IndexBuffer()
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, i.IndexBufID)
gl.BufferData(gl.ELEMENT_ARRAY_BUFFER, indexBufferSize, indexBuffer, gl.STREAM_DRAW)
for _, cmd := range list.Commands() {
if cmd.HasUserCallback() {
cmd.CallUserCallback(list)
} else {
gl.BindTexture(gl.TEXTURE_2D, i.TexID)
clipRect := cmd.ClipRect()
gl.Scissor(int32(clipRect.X), int32(fbHeight)-int32(clipRect.W), int32(clipRect.Z-clipRect.X), int32(clipRect.W-clipRect.Y))
gl.DrawElementsBaseVertex(gl.TRIANGLES, int32(cmd.ElementCount()), uint32(drawType), gl.PtrOffset(cmd.IndexOffset()*indexSize), int32(cmd.VertexOffset()))
}
}
}
//Reset gl state
gl.Disable(gl.BLEND)
gl.Disable(gl.SCISSOR_TEST)
gl.Enable(gl.CULL_FACE)
gl.Enable(gl.DEPTH_TEST)
}
func (i *ImguiInfo) AddFontTTF(fontPath string, fontSize float32, fontConfig *imgui.FontConfig, glyphRanges *imgui.GlyphRanges) imgui.Font {
fontConfigToUse := imgui.DefaultFontConfig
if fontConfig != nil {
fontConfigToUse = *fontConfig
}
glyphRangesToUse := imgui.EmptyGlyphRanges
if glyphRanges != nil {
glyphRangesToUse = *glyphRanges
}
imIO := imgui.CurrentIO()
a := imIO.Fonts()
f := a.AddFontFromFileTTFV(fontPath, fontSize, fontConfigToUse, glyphRangesToUse)
image := a.TextureDataAlpha8()
gl.BindTexture(gl.TEXTURE_2D, i.TexID)
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(image.Width), int32(image.Height), 0, gl.RED, gl.UNSIGNED_BYTE, image.Pixels)
return f
}
func NewImGUI() ImguiInfo {
imguiInfo := ImguiInfo{
ImCtx: imgui.CreateContext(nil),
Mat: materials.NewMaterial("ImGUI Mat", "./res/shaders/imgui"),
}
imIO := imgui.CurrentIO()
imIO.SetBackendFlags(imIO.GetBackendFlags() | imgui.BackendFlagsRendererHasVtxOffset)
gl.GenVertexArrays(1, &imguiInfo.VaoID)
gl.GenBuffers(1, &imguiInfo.VboID)
gl.GenBuffers(1, &imguiInfo.IndexBufID)
gl.GenTextures(1, &imguiInfo.TexID)
// Upload font to gpu
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_MAG_FILTER, gl.LINEAR)
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
image := imIO.Fonts().TextureDataAlpha8()
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RED, int32(image.Width), int32(image.Height), 0, gl.RED, gl.UNSIGNED_BYTE, image.Pixels)
// Store our identifier
imIO.Fonts().SetTextureID(imgui.TextureID(imguiInfo.TexID))
//Shader attributes
imguiInfo.Mat.Bind()
imguiInfo.Mat.EnableAttribute("Position")
imguiInfo.Mat.EnableAttribute("UV")
imguiInfo.Mat.EnableAttribute("Color")
imguiInfo.Mat.UnBind()
//Init imgui input mapping
keys := map[int]int{
imgui.KeyTab: sdl.SCANCODE_TAB,
imgui.KeyLeftArrow: sdl.SCANCODE_LEFT,
imgui.KeyRightArrow: sdl.SCANCODE_RIGHT,
imgui.KeyUpArrow: sdl.SCANCODE_UP,
imgui.KeyDownArrow: sdl.SCANCODE_DOWN,
imgui.KeyPageUp: sdl.SCANCODE_PAGEUP,
imgui.KeyPageDown: sdl.SCANCODE_PAGEDOWN,
imgui.KeyHome: sdl.SCANCODE_HOME,
imgui.KeyEnd: sdl.SCANCODE_END,
imgui.KeyInsert: sdl.SCANCODE_INSERT,
imgui.KeyDelete: sdl.SCANCODE_DELETE,
imgui.KeyBackspace: sdl.SCANCODE_BACKSPACE,
imgui.KeySpace: sdl.SCANCODE_BACKSPACE,
imgui.KeyEnter: sdl.SCANCODE_RETURN,
imgui.KeyEscape: sdl.SCANCODE_ESCAPE,
imgui.KeyA: sdl.SCANCODE_A,
imgui.KeyC: sdl.SCANCODE_C,
imgui.KeyV: sdl.SCANCODE_V,
imgui.KeyX: sdl.SCANCODE_X,
imgui.KeyY: sdl.SCANCODE_Y,
imgui.KeyZ: sdl.SCANCODE_Z,
}
// Keyboard mapping. ImGui will use those indices to peek into the io.KeysDown[] array.
for imguiKey, nativeKey := range keys {
imIO.KeyMap(imguiKey, nativeKey)
}
return imguiInfo
}