commit b44d41ec666ef5c95be94ee5bd5a9d5aaec8afaf Author: Edgaru089 Date: Thu Jan 20 21:58:50 2022 +0800 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..82b02b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +.DS_Store +._* + +*.gob + +gl01 +gl01.exe +*.exe +*.dll + +cmd/cmd +cmd/cmd.exe +cmd/imgui.ini diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..adb00b3 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,82 @@ +package main + +import ( + "fmt" + "runtime" + "time" + + "edgaru089.ml/go/gl01/internal/game" + _ "edgaru089.ml/go/gl01/internal/render/gpu_preference" + "github.com/go-gl/gl/all-core/gl" + "github.com/go-gl/glfw/v3.3/glfw" +) + +const ( + //windowWidth = 852 + //windowHeight = 480 + windowWidth = 1600 + windowHeight = 900 +) + +func init() { + runtime.LockOSThread() +} +func main() { + + if err := glfw.Init(); err != nil { + panic(err) + } + defer glfw.Terminate() + + glfw.WindowHint(glfw.Resizable, 1) + glfw.WindowHint(glfw.ContextVersionMajor, 3) + glfw.WindowHint(glfw.ContextVersionMinor, 3) + glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) + glfw.WindowHint(glfw.OpenGLForwardCompatible, 1) + win, err := glfw.CreateWindow(windowWidth, windowHeight, "Gl01", nil, nil) + if err != nil { + panic(err) + } + win.MakeContextCurrent() + glfw.SwapInterval(1) + + err = gl.Init() + if err != nil { + panic(err) + } + + fmt.Println("OpenGL Version: ", gl.GoStr(gl.GetString(gl.VERSION))) + + game := game.NewGame() + game.Init(win) + + gl.ClearColor(0.6, 0.8, 1.0, 1) + gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) + win.SwapBuffers() + winClock := time.Now() + + fpsClock := time.Now() + fpsCounter := 0 + + for !win.ShouldClose() { + + deltaTime := time.Since(winClock) + winClock = time.Now() + game.Update(win, deltaTime) + + gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) + + game.Render(win) + + win.SwapBuffers() + glfw.PollEvents() + + if time.Since(fpsClock) >= time.Second { + win.SetTitle(fmt.Sprintf("Gl01 %dFPS", fpsCounter)) + fpsCounter = 0 + fpsClock = time.Now() + } else { + fpsCounter++ + } + } +} diff --git a/cmd/main.go.old b/cmd/main.go.old new file mode 100644 index 0000000..08c052a --- /dev/null +++ b/cmd/main.go.old @@ -0,0 +1,138 @@ +package main + +import ( + "fmt" + "runtime" + "time" + "unsafe" + + "github.com/Edgaru089/gl01/internal/asset" + "github.com/Edgaru089/gl01/internal/render" + "github.com/Edgaru089/gl01/internal/world" + "github.com/go-gl/gl/all-core/gl" + "github.com/go-gl/glfw/v3.3/glfw" + "github.com/go-gl/mathgl/mgl32" +) + +const ( + windowWidth = 800 + windowHeight = 600 +) + +func init() { + runtime.LockOSThread() +} +func main() { + + asset.InitWorldTextureAtlas() + + if err := glfw.Init(); err != nil { + panic(err) + } + defer glfw.Terminate() + + glfw.WindowHint(glfw.Resizable, 0) + glfw.WindowHint(glfw.ContextVersionMajor, 4) + glfw.WindowHint(glfw.ContextVersionMinor, 1) + glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) + glfw.WindowHint(glfw.OpenGLForwardCompatible, 1) + win, err := glfw.CreateWindow(windowWidth, windowHeight, "Cube", nil, nil) + if err != nil { + panic(err) + } + win.MakeContextCurrent() + glfw.SwapInterval(1) + + err = gl.Init() + if err != nil { + panic(err) + } + + fmt.Println("OpenGL Version: ", gl.GoStr(gl.GetString(gl.VERSION))) + + shader, err := render.NewShader(asset.WorldShaderVert, asset.WorldShaderFrag) + if err != nil { + panic(err) + } + + model := mgl32.Ident4() + shader.SetUniformMat4("model", model) + + view := mgl32.LookAtV(mgl32.Vec3{3, 3, 3}, mgl32.Vec3{0, 0, 0}, mgl32.Vec3{0, 1, 0}) + shader.SetUniformMat4("view", view) + + projection := mgl32.Perspective(mgl32.DegToRad(45), float32(windowWidth)/float32(windowHeight), 0.1, 10) + shader.SetUniformMat4("projection", projection) + + // Texture + asset.InitWorldTextureAtlas() + worldTexture := render.NewTextureRGBA(asset.WorldTextureAtlas.Image) + shader.SetUniformTexture("tex", worldTexture) + + gl.BindFragDataLocation(shader.Handle(), 0, gl.Str("outputColor\x00")) + + var c, cm world.Chunk + c.SetBlock(0, 0, 0, 2) + c.SetBlock(0, 0, 2, 1) + cm.SetChunkID(-1, -1) + cm.SetBlock(15, 0, 15, 1) + cm.SetBlock(15, 1, 15, 2) + + var arr []world.Vertex + arr = c.AppendVertex(arr) + arr = cm.AppendVertex(arr) + + // Vertex Array + var vao uint32 + gl.GenVertexArrays(1, &vao) + gl.BindVertexArray(vao) + + // Vertex Buffer + var vbo uint32 + gl.GenBuffers(1, &vbo) + gl.BindBuffer(gl.ARRAY_BUFFER, vbo) + gl.BufferData(gl.ARRAY_BUFFER, int(unsafe.Sizeof(world.Vertex{}))*len(arr), gl.Ptr(arr), gl.STATIC_DRAW) + + vertAttrib := uint32(gl.GetAttribLocation(shader.Handle(), gl.Str("vert\x00"))) + gl.EnableVertexAttribArray(vertAttrib) + gl.VertexAttribPointer(vertAttrib, 3, gl.FLOAT, false, int32(unsafe.Sizeof(world.Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(world.Vertex{}.World)))) + + texCoordAttrib := uint32(gl.GetAttribLocation(shader.Handle(), gl.Str("vertTexCoord\x00"))) + gl.EnableVertexAttribArray(texCoordAttrib) + gl.VertexAttribPointer(texCoordAttrib, 2, gl.FLOAT, false, int32(unsafe.Sizeof(world.Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(world.Vertex{}.Texture)))) + + gl.Enable(gl.CULL_FACE) + gl.Enable(gl.DEPTH_TEST) + gl.DepthFunc(gl.LESS) + gl.ClearColor(1, 1, 1, 1) + + angle := 0.0 + prevTime := time.Now() + + for !win.ShouldClose() { + + gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) + + // Angle update + angle += time.Since(prevTime).Seconds() + prevTime = time.Now() + + model = mgl32.HomogRotate3D(float32(angle), mgl32.Vec3{0, 1, 0}) + + // Render + // Shader variable + shader.UseProgram() + shader.BindTextures() + shader.SetUniformMat4("model", model) + + gl.BindVertexArray(vao) + + //gl.ActiveTexture(gl.TEXTURE1) + //gl.BindTexture(gl.TEXTURE_2D, texture) + + gl.DrawArrays(gl.TRIANGLES, 0, int32(len(arr))) + + win.SwapBuffers() + glfw.PollEvents() + } +} diff --git a/cmd/main.go.old2 b/cmd/main.go.old2 new file mode 100644 index 0000000..5004af4 --- /dev/null +++ b/cmd/main.go.old2 @@ -0,0 +1,94 @@ +package main + +import ( + "fmt" + "runtime" + "time" + + "github.com/Edgaru089/gl01/internal/render" + "github.com/Edgaru089/gl01/internal/util/itype" + "github.com/Edgaru089/gl01/internal/world" + "github.com/go-gl/gl/all-core/gl" + "github.com/go-gl/glfw/v3.3/glfw" +) + +const ( + windowWidth = 852 + windowHeight = 480 +) + +func init() { + runtime.LockOSThread() +} +func main() { + + if err := glfw.Init(); err != nil { + panic(err) + } + defer glfw.Terminate() + + glfw.WindowHint(glfw.Resizable, 0) + glfw.WindowHint(glfw.ContextVersionMajor, 4) + glfw.WindowHint(glfw.ContextVersionMinor, 1) + glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) + glfw.WindowHint(glfw.OpenGLForwardCompatible, 1) + win, err := glfw.CreateWindow(windowWidth, windowHeight, "Cube", nil, nil) + if err != nil { + panic(err) + } + win.MakeContextCurrent() + glfw.SwapInterval(1) + + err = gl.Init() + if err != nil { + panic(err) + } + + fmt.Println("OpenGL Version: ", gl.GoStr(gl.GetString(gl.VERSION))) + + var c, cm world.Chunk + c.SetBlock(0, 0, 0, 2) + c.SetBlock(1, 0, 2, 1) + c.SetBlock(2, 0, 2, 3) + cm.SetChunkID(-1, -1) + cm.SetBlock(15, 0, 15, 5) + cm.SetBlock(15, 1, 15, 6) + + world.DefaultWorld.SetChunk(0, 0, &c) + world.DefaultWorld.SetChunk(-1, -1, &cm) + + var rotY, rotZ float32 = 135, -45 + render.DefaultWorldRenderer.LookAt(itype.Vec3f{3, 3, 3}, rotY, rotZ) + render.DefaultWorldRenderer.SetAspect(float32(windowWidth) / float32(windowHeight)) + render.DefaultWorldRenderer.SetFovY(45) + render.DefaultWorldRenderer.Init(&world.DefaultWorld) + + gl.ClearColor(1, 1, 1, 1) + winClock := time.Now() + for !win.ShouldClose() { + + deltaTime := time.Since(winClock) + winClock = time.Now() + + if win.GetKey(glfw.KeyLeft) == glfw.Press { + rotY += float32(deltaTime.Seconds()) * 100 + } + if win.GetKey(glfw.KeyRight) == glfw.Press { + rotY -= float32(deltaTime.Seconds()) * 100 + } + if win.GetKey(glfw.KeyUp) == glfw.Press { + rotZ += float32(deltaTime.Seconds()) * 100 + } + if win.GetKey(glfw.KeyDown) == glfw.Press { + rotZ -= float32(deltaTime.Seconds()) * 100 + } + render.DefaultWorldRenderer.LookAt(itype.Vec3f{3, 3, 3}, rotY, rotZ) + + gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) + + render.DefaultWorldRenderer.Render(&world.DefaultWorld) + + win.SwapBuffers() + glfw.PollEvents() + } +} diff --git a/embed.go b/embed.go new file mode 100644 index 0000000..7e59fa6 --- /dev/null +++ b/embed.go @@ -0,0 +1,65 @@ +package main + +import ( + _ "embed" +) + +//go:embed shader.frag +var shaderFrag string + +//go:embed shader.vert +var shaderVert string + +//go:embed square.png +var textureData []byte + +var cubeVertices = []float32{ + // X, Y, Z, U, V + // Bottom + -1.0, -1.0, -1.0, 0.0, 0.0, + 1.0, -1.0, -1.0, 1.0, 0.0, + -1.0, -1.0, 1.0, 0.0, 1.0, + 1.0, -1.0, -1.0, 1.0, 0.0, + 1.0, -1.0, 1.0, 1.0, 1.0, + -1.0, -1.0, 1.0, 0.0, 1.0, + + // Top + -1.0, 1.0, -1.0, 0.0, 0.0, + -1.0, 1.0, 1.0, 0.0, 1.0, + 1.0, 1.0, -1.0, 1.0, 0.0, + 1.0, 1.0, -1.0, 1.0, 0.0, + -1.0, 1.0, 1.0, 0.0, 1.0, + 1.0, 1.0, 1.0, 1.0, 1.0, + + // Front + -1.0, -1.0, 1.0, 1.0, 0.0, + 1.0, -1.0, 1.0, 0.0, 0.0, + -1.0, 1.0, 1.0, 1.0, 1.0, + 1.0, -1.0, 1.0, 0.0, 0.0, + 1.0, 1.0, 1.0, 0.0, 1.0, + -1.0, 1.0, 1.0, 1.0, 1.0, + + // Back + -1.0, -1.0, -1.0, 0.0, 0.0, + -1.0, 1.0, -1.0, 0.0, 1.0, + 1.0, -1.0, -1.0, 1.0, 0.0, + 1.0, -1.0, -1.0, 1.0, 0.0, + -1.0, 1.0, -1.0, 0.0, 1.0, + 1.0, 1.0, -1.0, 1.0, 1.0, + + // Left + -1.0, -1.0, 1.0, 0.0, 1.0, + -1.0, 1.0, -1.0, 1.0, 0.0, + -1.0, -1.0, -1.0, 0.0, 0.0, + -1.0, -1.0, 1.0, 0.0, 1.0, + -1.0, 1.0, 1.0, 1.0, 1.0, + -1.0, 1.0, -1.0, 1.0, 0.0, + + // Right + 1.0, -1.0, 1.0, 1.0, 1.0, + 1.0, -1.0, -1.0, 1.0, 0.0, + 1.0, 1.0, -1.0, 0.0, 0.0, + 1.0, -1.0, 1.0, 1.0, 1.0, + 1.0, 1.0, -1.0, 0.0, 0.0, + 1.0, 1.0, 1.0, 0.0, 1.0, +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1618c4c --- /dev/null +++ b/go.mod @@ -0,0 +1,13 @@ +module edgaru089.ml/go/gl01 + +go 1.17 + +require ( + github.com/aquilax/go-perlin v1.1.0 + github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec + github.com/go-gl/mathgl v1.0.0 + github.com/inkyblackness/imgui-go/v4 v4.4.0 +) + +require golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..961495b --- /dev/null +++ b/go.sum @@ -0,0 +1,20 @@ +github.com/aquilax/go-perlin v1.1.0 h1:Gg+3jQ24wT4Y5GI7TCRLmYarzUG0k+n/JATFqOimb7s= +github.com/aquilax/go-perlin v1.1.0/go.mod h1:z9Rl7EM4BZY0Ikp2fEN1I5mKSOJ26HQpk0O2TBdN2HE= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6 h1:zDw5v7qm4yH7N8C8uWd+8Ii9rROdgWxQuGoJ9WDXxfk= +github.com/go-gl/gl v0.0.0-20211210172815-726fda9656d6/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec h1:3FLiRYO6PlQFDpUU7OEFlWgjGD1jnBIVSJ5SYRWk+9c= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211213063430-748e38ca8aec/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/mathgl v1.0.0 h1:t9DznWJlXxxjeeKLIdovCOVJQk/GzDEL7h/h+Ro2B68= +github.com/go-gl/mathgl v1.0.0/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ= +github.com/inkyblackness/imgui-go/v4 v4.4.0 h1:jY32Xl18aRwTBXaDfyefCmPDxJOtGM8kGfu/kMNJpbE= +github.com/inkyblackness/imgui-go/v4 v4.4.0/go.mod h1:g8SAGtOYUP7rYaOB2AsVKCEHmPMDmJKgt4z6d+flhb0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f h1:FO4MZ3N56GnxbqxGKqh+YTzUWQ2sDwtFQEZgLOxh9Jc= +golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/asset/embed.go b/internal/asset/embed.go new file mode 100644 index 0000000..95672b3 --- /dev/null +++ b/internal/asset/embed.go @@ -0,0 +1,9 @@ +package asset + +import "embed" + +//go:embed texture +var FS embed.FS + +//go:embed font/unifont-11.0.02.2.ttf +var Unifont []byte diff --git a/internal/asset/font/unifont-11.0.02.2.ttf b/internal/asset/font/unifont-11.0.02.2.ttf new file mode 100755 index 0000000..00834c4 Binary files /dev/null and b/internal/asset/font/unifont-11.0.02.2.ttf differ diff --git a/internal/asset/shader.go b/internal/asset/shader.go new file mode 100644 index 0000000..2795cd8 --- /dev/null +++ b/internal/asset/shader.go @@ -0,0 +1,21 @@ +package asset + +import _ "embed" + +//go:embed shader/world.frag +var WorldShaderFrag string + +//go:embed shader/world.vert +var WorldShaderVert string + +//go:embed shader/world.shadowmap.frag +var WorldShaderShadowmapFrag string + +//go:embed shader/world.shadowmap.vert +var WorldShaderShadowmapVert string + +//go:embed shader/framewire.frag +var FramewireShaderFrag string + +//go:embed shader/framewire.vert +var FramewireShaderVert string diff --git a/internal/asset/shader/framewire.frag b/internal/asset/shader/framewire.frag new file mode 100644 index 0000000..46b2f4c --- /dev/null +++ b/internal/asset/shader/framewire.frag @@ -0,0 +1,9 @@ +#version 330 + +in vec4 fragColor; + +out vec4 outputColor; + +void main() { + outputColor = fragColor; +} diff --git a/internal/asset/shader/framewire.vert b/internal/asset/shader/framewire.vert new file mode 100644 index 0000000..f4a04a4 --- /dev/null +++ b/internal/asset/shader/framewire.vert @@ -0,0 +1,16 @@ +#version 330 + +uniform mat4 projection; +uniform mat4 view; +uniform mat4 model; + +in vec3 vert; +in vec4 vertColor; + +out vec4 fragColor; + +void main() { + fragColor = vertColor; + gl_Position = projection * view * model * vec4(vert, 1); +} + diff --git a/internal/asset/shader/world.frag b/internal/asset/shader/world.frag new file mode 100644 index 0000000..721817e --- /dev/null +++ b/internal/asset/shader/world.frag @@ -0,0 +1,87 @@ +#version 330 + +uniform sampler2D tex; +uniform sampler2D shadowmap; +uniform vec3 viewPos; +uniform vec3 sun; +uniform vec4 fogColor; + + +in vec4 fragPos; +in vec4 fragPosLightspace; +in vec2 fragTexCoord; +in vec3 fragNormal; + +out vec4 outputColor; + + +const float gamma = 2.2; + +const float ambient = 0.3, specularStrength = 0.5, specularShininess = 32; +const float fogDensity = .00003; + + +float light; +vec4 texpixel, color; +void lightSun(); +float lightSunShadow(); +void lightPoint(int i); + + +void main() { + + texpixel = texture(tex, fragTexCoord); + if (texpixel.a < 1e-3) + discard; + texpixel.rgb = pow(texpixel.rgb, vec3(gamma)); + + light = ambient; + + lightSun(); + + color += vec4(texpixel.rgb * light, 0.0f); + color.a = texpixel.a; + + float z = gl_FragCoord.z / gl_FragCoord.w; + float fog = clamp(exp(-fogDensity * z * z), 0.2, 1); + + outputColor = mix(fogColor, color, fog); + //outputColor.rgb = pow(outputColor.rgb, vec3(1.0/gamma)); +} + +void lightSun() { + /* Diffuse */ + vec3 lightDir = sun; + float diffuse = max(dot(fragNormal, lightDir), 0.0f); + + /* Specular */ + vec3 viewDir = normalize(viewPos - fragPos.xyz); + vec3 reflectDir = reflect(-lightDir, fragNormal); + float specular = specularStrength * pow(max(dot(viewDir, reflectDir), 0.0), specularShininess); + + float shadow = lightSunShadow(); + light += diffuse * shadow; + color += vec4(vec3(specular), 0.0f) * shadow; +} + +float lightSunShadow() { + /* Shadow */ + float bias = max(0.005 * (1.0 - dot(fragNormal, sun)), 0.0005); + vec3 projCoords = fragPosLightspace.xyz / fragPosLightspace.w; + projCoords = projCoords*0.5 + 0.5; + //float closestDepth = texture(shadowmap, projCoords.xy).r; + float currentDepth = projCoords.z; + float shadow = 0; + + if (currentDepth > 1.0f || currentDepth < 0.0f) + return 1.0f; + + vec2 texelSize = 1.0 / textureSize(shadowmap, 0); + for (int x=-1; x<=1; ++x) + for (int y=-1; y<=1; ++y) { + float pcfDepth = texture(shadowmap, projCoords.xy + vec2(x,y)*texelSize).r; + shadow += currentDepth-bias < pcfDepth ? 1.0f : 0.0f; + } + shadow /= 9.0f; + return shadow; +} diff --git a/internal/asset/shader/world.shadowmap.frag b/internal/asset/shader/world.shadowmap.frag new file mode 100644 index 0000000..e5874e3 --- /dev/null +++ b/internal/asset/shader/world.shadowmap.frag @@ -0,0 +1,6 @@ +#version 330 + +void main() { + // gl_FragDepth = gl.FragCoord.z; +} + diff --git a/internal/asset/shader/world.shadowmap.vert b/internal/asset/shader/world.shadowmap.vert new file mode 100644 index 0000000..96b1eb4 --- /dev/null +++ b/internal/asset/shader/world.shadowmap.vert @@ -0,0 +1,14 @@ +#version 330 + +uniform mat4 model; +uniform mat4 lightspace; + +layout (location = 0) in vec3 vert; +layout (location = 1) in vec3 normal; +layout (location = 2) in vec2 vertTexCoord; +layout (location = 3) in float light; + +void main() { + gl_Position = lightspace * model * vec4(vert, 1.0); +} + diff --git a/internal/asset/shader/world.vert b/internal/asset/shader/world.vert new file mode 100644 index 0000000..1b40525 --- /dev/null +++ b/internal/asset/shader/world.vert @@ -0,0 +1,28 @@ +#version 330 + +uniform mat4 projection; +uniform mat4 view; +uniform mat4 model; +uniform mat4 lightspace; + + +layout (location = 0) in vec3 vert; +layout (location = 1) in vec3 normal; +layout (location = 2) in vec2 vertTexCoord; +layout (location = 3) in float light; + +out vec4 fragPos; +out vec4 fragPosLightspace; +out vec2 fragTexCoord; +out vec3 fragNormal; + +void main() { + + fragTexCoord = vertTexCoord; + fragNormal = normalize(normal); + + gl_Position = projection * view * model * vec4(vert, 1); + fragPos = model * vec4(vert, 1); + fragPosLightspace = lightspace * fragPos; +} + diff --git a/internal/asset/texture.go b/internal/asset/texture.go new file mode 100644 index 0000000..8a00317 --- /dev/null +++ b/internal/asset/texture.go @@ -0,0 +1,43 @@ +package asset + +import ( + _ "image/png" + + "log" + + "edgaru089.ml/go/gl01/internal/util" +) + +// WorldTextureAtlas holds all the world block textures. +var WorldTextureAtlas util.Atlas + +func InitWorldTextureAtlas() { + if WorldTextureAtlas.HasBuilt() { + return + } + + files, err := FS.ReadDir("texture/world") + if err != nil { + panic("InitWorldTextureAtlas: embed.FS.ReadDir(\"texture/world\"): " + err.Error()) + } + + for _, f := range files { + if f.IsDir() { + continue + } + + name := f.Name() + + file, err := FS.Open("texture/world/" + name) + if err != nil { // Shouldn't be error? + panic(err) + } + + err = WorldTextureAtlas.AddFile(name, file) + if err != nil { + log.Printf("WARN: InitWorldTextureAtlas: img %s failed to decode: %s", name, err) + } + } + + WorldTextureAtlas.BuildTexture( /*false*/ ) +} diff --git a/internal/asset/texture/world/bedrock.png b/internal/asset/texture/world/bedrock.png new file mode 100644 index 0000000..1643c99 Binary files /dev/null and b/internal/asset/texture/world/bedrock.png differ diff --git a/internal/asset/texture/world/cobblestone.png b/internal/asset/texture/world/cobblestone.png new file mode 100644 index 0000000..da3498c Binary files /dev/null and b/internal/asset/texture/world/cobblestone.png differ diff --git a/internal/asset/texture/world/cobblestone_mossy.png b/internal/asset/texture/world/cobblestone_mossy.png new file mode 100644 index 0000000..29449e3 Binary files /dev/null and b/internal/asset/texture/world/cobblestone_mossy.png differ diff --git a/internal/asset/texture/world/command_block.png b/internal/asset/texture/world/command_block.png new file mode 100644 index 0000000..4459675 Binary files /dev/null and b/internal/asset/texture/world/command_block.png differ diff --git a/internal/asset/texture/world/debug.png b/internal/asset/texture/world/debug.png new file mode 100644 index 0000000..09aec72 Binary files /dev/null and b/internal/asset/texture/world/debug.png differ diff --git a/internal/asset/texture/world/debug_dir_x+.png b/internal/asset/texture/world/debug_dir_x+.png new file mode 100644 index 0000000..eea9bd1 Binary files /dev/null and b/internal/asset/texture/world/debug_dir_x+.png differ diff --git a/internal/asset/texture/world/debug_dir_x-.png b/internal/asset/texture/world/debug_dir_x-.png new file mode 100644 index 0000000..5389c8a Binary files /dev/null and b/internal/asset/texture/world/debug_dir_x-.png differ diff --git a/internal/asset/texture/world/debug_dir_y+.png b/internal/asset/texture/world/debug_dir_y+.png new file mode 100644 index 0000000..2df29fe Binary files /dev/null and b/internal/asset/texture/world/debug_dir_y+.png differ diff --git a/internal/asset/texture/world/debug_dir_y-.png b/internal/asset/texture/world/debug_dir_y-.png new file mode 100644 index 0000000..c847371 Binary files /dev/null and b/internal/asset/texture/world/debug_dir_y-.png differ diff --git a/internal/asset/texture/world/debug_dir_z+.png b/internal/asset/texture/world/debug_dir_z+.png new file mode 100644 index 0000000..402ab94 Binary files /dev/null and b/internal/asset/texture/world/debug_dir_z+.png differ diff --git a/internal/asset/texture/world/debug_dir_z-.png b/internal/asset/texture/world/debug_dir_z-.png new file mode 100644 index 0000000..9208e34 Binary files /dev/null and b/internal/asset/texture/world/debug_dir_z-.png differ diff --git a/internal/asset/texture/world/dirt.png b/internal/asset/texture/world/dirt.png new file mode 100644 index 0000000..617d353 Binary files /dev/null and b/internal/asset/texture/world/dirt.png differ diff --git a/internal/asset/texture/world/grass_bot.png b/internal/asset/texture/world/grass_bot.png new file mode 100644 index 0000000..617d353 Binary files /dev/null and b/internal/asset/texture/world/grass_bot.png differ diff --git a/internal/asset/texture/world/grass_side.png b/internal/asset/texture/world/grass_side.png new file mode 100644 index 0000000..a4975e5 Binary files /dev/null and b/internal/asset/texture/world/grass_side.png differ diff --git a/internal/asset/texture/world/grass_top.png b/internal/asset/texture/world/grass_top.png new file mode 100644 index 0000000..c52c59b Binary files /dev/null and b/internal/asset/texture/world/grass_top.png differ diff --git a/internal/asset/texture/world/grass_top_greyscale.png b/internal/asset/texture/world/grass_top_greyscale.png new file mode 100644 index 0000000..eaa7e45 Binary files /dev/null and b/internal/asset/texture/world/grass_top_greyscale.png differ diff --git a/internal/asset/texture/world/leaves_oak.png b/internal/asset/texture/world/leaves_oak.png new file mode 100644 index 0000000..020656e Binary files /dev/null and b/internal/asset/texture/world/leaves_oak.png differ diff --git a/internal/asset/texture/world/log_oak.png b/internal/asset/texture/world/log_oak.png new file mode 100644 index 0000000..914cb5f Binary files /dev/null and b/internal/asset/texture/world/log_oak.png differ diff --git a/internal/asset/texture/world/log_oak_bot.png b/internal/asset/texture/world/log_oak_bot.png new file mode 100644 index 0000000..7a44e77 Binary files /dev/null and b/internal/asset/texture/world/log_oak_bot.png differ diff --git a/internal/asset/texture/world/log_oak_top.png b/internal/asset/texture/world/log_oak_top.png new file mode 100644 index 0000000..7a44e77 Binary files /dev/null and b/internal/asset/texture/world/log_oak_top.png differ diff --git a/internal/asset/texture/world/stone.png b/internal/asset/texture/world/stone.png new file mode 100644 index 0000000..87e19ff Binary files /dev/null and b/internal/asset/texture/world/stone.png differ diff --git a/internal/entity/entities/player.go b/internal/entity/entities/player.go new file mode 100644 index 0000000..03b4a19 --- /dev/null +++ b/internal/entity/entities/player.go @@ -0,0 +1,29 @@ +package entities + +import ( + "time" + + "edgaru089.ml/go/gl01/internal/entity" + "edgaru089.ml/go/gl01/internal/util/itype" + "edgaru089.ml/go/gl01/internal/world" +) + +type PlayerBehaviour struct{} + +func init() { + entity.RegisterEntityBehaviour(PlayerBehaviour{}) +} + +func (PlayerBehaviour) Name() string { return "player" } + +func (PlayerBehaviour) Hitbox(pos itype.Vec3d, dataset itype.Dataset) itype.Vec3d { + return itype.Vec3d{0.6, 1.8, 0.6} +} + +func (PlayerBehaviour) EyeHeight(pos itype.Vec3d, dataset itype.Dataset) float64 { + return 1.65 +} + +func (PlayerBehaviour) Update(pos itype.Vec3d, dataset itype.Dataset, world *world.World, deltaTime time.Duration) { + +} diff --git a/internal/entity/entity.go b/internal/entity/entity.go new file mode 100644 index 0000000..c60bdb4 --- /dev/null +++ b/internal/entity/entity.go @@ -0,0 +1,109 @@ +package entity + +import ( + "time" + + "edgaru089.ml/go/gl01/internal/util" + "edgaru089.ml/go/gl01/internal/util/itype" + "edgaru089.ml/go/gl01/internal/world" +) + +// EntityBehaviour describes the behaviour of a type of entity with the same Name. +type EntityBehaviour interface { + // Name returns the type Name of the behaviour. + Name() string + + // Hitbox gets the size of the hitbox of the entity. + // + // The hitbox is positioned with the position coordinates pointing to + // the center of the bottom of the box. + Hitbox(pos itype.Vec3d, dataset itype.Dataset) itype.Vec3d + + // EyeHeight gets the height of the eye of the entity. + EyeHeight(pos itype.Vec3d, dataset itype.Dataset) float64 + + // Update is called on every frame. + // It should be used to update physics. + Update(pos itype.Vec3d, dataset itype.Dataset, world *world.World, deltaTime time.Duration) +} + +var behaviour = make(map[string]EntityBehaviour) + +// RegisterEntityBehaviour registers behaviour with the name of b.Name(). +// +// If the name is already taken, false is returned and nothing is done. +// Otherwise, true is returned and the entity is registered. +func RegisterEntityBehaviour(b EntityBehaviour) bool { + if _, ok := behaviour[b.Name()]; ok { + return false + } + + behaviour[b.Name()] = b + return true +} + +// Entity is a struct holding a Behaviour, a Position and a Speed. +type Entity struct { + b EntityBehaviour + pos, speed itype.Vec3d // pos has a origin of the **center of the bottom** of the hitbox + ds itype.Dataset + + name string // a shortcut to b.Name(), the typename + + onGround bool + worldbox []itype.Boxd + hp []itype.Vec3d +} + +func NewEntity(typename string, pos itype.Vec3d) *Entity { + var b EntityBehaviour + var ok bool + if b, ok = behaviour[typename]; !ok { + return nil + } + return &Entity{ + b: b, + pos: pos, + name: typename, + } +} + +func (e *Entity) Position() itype.Vec3d { + return e.pos +} + +func (e *Entity) SetPosition(pos itype.Vec3d) { + e.pos = pos +} + +func (e *Entity) Speed() itype.Vec3d { + return e.speed +} + +func (e *Entity) SetSpeed(speed itype.Vec3d) { + e.speed = speed +} + +func (e *Entity) Hitbox() itype.Vec3d { + return e.b.Hitbox(e.pos, e.ds) +} + +func (e *Entity) Accelerate(x, y, z float64) { + /*e.speed[0] += x + e.speed[1] += y + e.speed[2] += z*/ + vec := itype.Vec3d{x, y, z} + e.speed = util.BunnyhopAccelerate(vec, e.speed, vec.Length(), 8) +} + +func (e *Entity) OnGround() bool { + return e.onGround +} + +func (e *Entity) EyeHeight() float64 { + return e.b.EyeHeight(e.pos, e.ds) +} + +func (e *Entity) EyePosition() itype.Vec3d { + return e.pos.Addv(0, e.EyeHeight(), 0) +} diff --git a/internal/entity/manager.go b/internal/entity/manager.go new file mode 100644 index 0000000..bc3d304 --- /dev/null +++ b/internal/entity/manager.go @@ -0,0 +1 @@ +package entity diff --git a/internal/entity/physics.go b/internal/entity/physics.go new file mode 100644 index 0000000..07518d7 --- /dev/null +++ b/internal/entity/physics.go @@ -0,0 +1,298 @@ +package entity + +import ( + "math" + "time" + + "edgaru089.ml/go/gl01/internal/util" + "edgaru089.ml/go/gl01/internal/util/itype" + "edgaru089.ml/go/gl01/internal/world" +) + +func (e *Entity) worldHitbox(hitbox itype.Vec3d) itype.Boxd { + return itype.Boxd{ + OffX: e.pos[0] - hitbox[0]/2, + OffY: e.pos[1], + OffZ: e.pos[2] - hitbox[2]/2, + SizeX: hitbox[0], + SizeY: hitbox[1], + SizeZ: hitbox[2], + } +} + +// WorldHitbox returns the hitbox of the entity, in world coordinates. +func (e *Entity) WorldHitbox() itype.Boxd { + return e.worldHitbox(e.b.Hitbox(e.pos, e.ds)) +} + +// the distance between hitpoints, in blocks +const HitpointMeshLen = 0.4 + +func (e *Entity) boxHitpoints(points []itype.Vec3d, hitbox itype.Boxd) []itype.Vec3d { + + box := hitbox + base := box.MinPoint() + points = points[0:0] + + // add the bounding points first + points = append(points, + base, + base.Add(itype.Vec3d{box.SizeX, 0, 0}), + base.Add(itype.Vec3d{0, box.SizeY, 0}), + base.Add(itype.Vec3d{0, 0, box.SizeZ}), + base.Add(itype.Vec3d{box.SizeX, box.SizeY, 0}), + base.Add(itype.Vec3d{0, box.SizeY, box.SizeZ}), + base.Add(itype.Vec3d{box.SizeX, 0, box.SizeZ}), + base.Add(itype.Vec3d{box.SizeX, box.SizeY, box.SizeZ}), + ) + /* + // add the surface points + // X+ and X- + for y := base[1] + HitpointMeshLen; y < base[1]+hitbox[1]; y += HitpointMeshLen { + for z := base[2] + HitpointMeshLen; z < base[2]+hitbox[2]; z += HitpointMeshLen { + points = append(points, + base.Addv(0, y, z), + base.Addv(box.SizeX, y, z), + ) + } + } + // Y+ and Y- + for x := base[0] + HitpointMeshLen; x < base[0]+hitbox[0]; x += HitpointMeshLen { + for z := base[2] + HitpointMeshLen; z < base[2]+hitbox[2]; z += HitpointMeshLen { + points = append(points, + base.Addv(x, 0, z), + base.Addv(x, box.SizeY, z), + ) + } + } + // Z+ and Z- + for x := base[0] + HitpointMeshLen; x < base[0]+hitbox[0]; x += HitpointMeshLen { + for y := base[1] + HitpointMeshLen; y < base[1]+hitbox[1]; y += HitpointMeshLen { + points = append(points, + base.Addv(x, y, 0), + base.Addv(x, y, box.SizeZ), + ) + } + }*/ + return points +} + +// MoveEps is a small value used to indicate a tiny amount more than zero. +const MoveEps = 1e-7 + +// tells if the point is already stuck inside a block hitbox +func pointStuck(point itype.Vec3d, w *world.World) bool { + blockid := point.Floor() + block := w.Block(blockid) + return block.Id != 0 && + block.Behaviour.Appearance( + point.Floor(), + block.Aux, + block.Dataset, + ).Hitbox.Offset(blockid.ToFloat64()).Contains(point) +} + +// the move series functions should be called with e.hp already filled + +func (e *Entity) moveX(delta float64, hitbox itype.Boxd, w *world.World) { + if math.Abs(delta) < MoveEps*10 { + return + } + + var hit bool = false + var deltaMin float64 = 10000 + + if delta > 1-MoveEps { + delta = 1 - MoveEps + } + if delta < -1+MoveEps { + delta = -1 + MoveEps + } + + // X+ / X- + for _, p := range e.hp { + if pointStuck(p, w) { + continue + } + + dest := p.Addv(delta, 0, 0) + blockid := dest.Floor() + block := w.Block(blockid) + + var deltaDone float64 + if block.Id == 0 { + deltaDone = delta + } else { // block.Id!=0 + app := world.GetBlockAppearance(blockid, block.Id, block.Aux, block.Dataset) + blockBox := app.Hitbox.Offset(blockid.ToFloat64()) + if !app.NotSolid && blockBox.Contains(dest) { // Hit! + hit = true + if delta > 0 { // moving X+, hit on the X- face + deltaDone = blockBox.OffX - (e.pos[0] + hitbox.SizeX/2) - MoveEps + //log.Print("Hit X+: On Block ", blockid, ", delta=", delta, ", coord=", e.pos, ", p=", p, ", dest=", dest, ", deltaDone=", deltaDone) + } else { // moving X-, hit on the X+ face + deltaDone = blockBox.OffX + blockBox.SizeX - (e.pos[0] - hitbox.SizeX/2) + MoveEps + //log.Print("Hit X-: On Block ", blockid, ", delta=", delta, ", coord=", e.pos, ", p=", p, ", dest=", dest, ", deltaDone=", deltaDone) + } + } else { + deltaDone = delta + } + } + + deltaMin = util.AbsMind(deltaMin, deltaDone) + } + + if hit { + e.speed[0] = 0 + } + e.pos[0] += deltaMin +} +func (e *Entity) moveY(delta float64, hitbox itype.Boxd, w *world.World) { + if math.Abs(delta) < MoveEps*10 { + return + } + + var hit bool = false + var deltaMin float64 = 10000 + + if delta > 1-MoveEps { + delta = 1 - MoveEps + } + if delta < -1+MoveEps { + delta = -1 + MoveEps + } + + // Y+ / Y- + for _, p := range e.hp { + if pointStuck(p, w) { + continue + } + + dest := p.Addv(0, delta, 0) + blockid := dest.Floor() + block := w.Block(blockid) + + var deltaDone float64 + if block.Id == 0 { + deltaDone = delta + } else { // block.Id!=0 + app := world.GetBlockAppearance(blockid, block.Id, block.Aux, block.Dataset) + blockBox := app.Hitbox.Offset(blockid.ToFloat64()) + if !app.NotSolid && blockBox.Contains(dest) { // Hit! + hit = true + if delta > 0 { // moving Y+, hit on the Y- face + deltaDone = blockBox.OffY - (e.pos[1] + hitbox.SizeY) - MoveEps + //log.Print("Hit Y+: On Block ", blockid, ", delta=", delta, ", coord=", e.pos, ", p=", p, ", dest=", dest,", deltaDone=",deltaDone) + } else { // moving Y-, hit on the Y+ face (on the ground) + deltaDone = blockBox.OffY + blockBox.SizeY - e.pos[1] + MoveEps + //log.Print("Hit Y-: On Block ", blockid, ", delta=", delta, ", coord=", e.pos, ", p=", p, ", dest=", dest,", deltaDone=",deltaDone) + /*if !e.onGround { + log.Print("onGround = true") + }*/ + e.onGround = true + } + } else { + deltaDone = delta + } + } + + deltaMin = util.AbsMind(deltaMin, deltaDone) + } + + if hit { + e.speed[1] = 0 + } + + if math.Abs(deltaMin) > MoveEps*10 { + /*if e.onGround { + log.Print("onGround = false") + }*/ + e.onGround = false + } + e.pos[1] += deltaMin +} +func (e *Entity) moveZ(delta float64, hitbox itype.Boxd, w *world.World) { + if math.Abs(delta) < MoveEps*10 { + return + } + + var hit bool = false + var deltaMin float64 = 10000 + + if delta > 1-MoveEps { + delta = 1 - MoveEps + } + if delta < -1+MoveEps { + delta = -1 + MoveEps + } + + // Z+ / Z- + for _, p := range e.hp { + if pointStuck(p, w) { + continue + } + + dest := p.Addv(0, 0, delta) + blockid := dest.Floor() + block := w.Block(blockid) + + var deltaDone float64 + if block.Id == 0 { + deltaDone = delta + } else { // block.Id!=0 + app := world.GetBlockAppearance(blockid, block.Id, block.Aux, block.Dataset) + blockBox := app.Hitbox.Offset(blockid.ToFloat64()) + if !app.NotSolid && blockBox.Contains(dest) { // Hit! + hit = true + if delta > 0 { // moving Z+, hit on the Z- face + deltaDone = util.Maxd(blockBox.OffZ-(e.pos[2]+hitbox.SizeZ/2)-MoveEps, 0) + //log.Print("Hit Z+: On Block ", blockid, ", delta=", delta, ", coord=", e.pos, ", p=", p, ", dest=", dest, ", deltaDone=", deltaDone) + } else { // moving Z-, hit on the Z+ face + deltaDone = util.Mind(blockBox.OffZ+blockBox.SizeZ-(e.pos[2]-hitbox.SizeZ/2)+MoveEps, 0) + //log.Print("Hit Z-: On Block ", blockid, ", delta=", delta, ", coord=", e.pos, ", p=", p, ", dest=", dest, ", deltaDone=", deltaDone) + } + } else { + deltaDone = delta + } + } + + deltaMin = util.AbsMind(deltaMin, deltaDone) + } + + if hit { + e.speed[2] = 0 + } + e.pos[2] += deltaMin +} + +const gravity float64 = 26 +const deaclc float64 = 28 + +func (e *Entity) Update(world *world.World, deltaTime time.Duration) { + + hitbox := e.b.Hitbox(e.pos, e.ds) + box := e.worldHitbox(hitbox) + + e.hp = e.hp[0:0] + e.hp = e.boxHitpoints(e.hp, box) + + e.moveX(e.speed[0]*deltaTime.Seconds(), box, world) + e.moveY(e.speed[1]*deltaTime.Seconds(), box, world) + e.moveZ(e.speed[2]*deltaTime.Seconds(), box, world) + + speed := math.Sqrt(e.speed[0]*e.speed[0] + e.speed[2]*e.speed[2]) + if speed > MoveEps { + decrease := deaclc * deltaTime.Seconds() + if !e.onGround { + decrease /= 10 + } + factor := util.Maxd(util.Mind(speed-decrease, 9), 0) / speed + + e.speed[0] *= factor + e.speed[2] *= factor + } + + e.speed[1] -= gravity * deltaTime.Seconds() + + e.b.Update(e.pos, e.ds, world, deltaTime) +} diff --git a/internal/entity/physics.go.new b/internal/entity/physics.go.new new file mode 100644 index 0000000..46b89bf --- /dev/null +++ b/internal/entity/physics.go.new @@ -0,0 +1,233 @@ +package entity + +import ( + "log" + "math" + "time" + + "github.com/Edgaru089/gl01/internal/util" + "github.com/Edgaru089/gl01/internal/util/itype" + "github.com/Edgaru089/gl01/internal/world" +) + +func (e *Entity) worldHitbox(hitbox itype.Vec3d) itype.Boxd { + return itype.Boxd{ + OffX: e.pos[0] - hitbox[0]/2, + OffY: e.pos[1], + OffZ: e.pos[2] - hitbox[2]/2, + SizeX: hitbox[0], + SizeY: hitbox[1], + SizeZ: hitbox[2], + } +} + +// WorldHitbox returns the hitbox of the entity, in world coordinates. +func (e *Entity) WorldHitbox() itype.Boxd { + return e.worldHitbox(e.b.Hitbox(e.pos, e.ds)) +} + +// MoveEps is a small value used to indicate a tiny amount more than zero. +const MoveEps = 1e-7 + +// the move series functions should be called with e.hp already filled + +func (e *Entity) moveX(delta float64, hitbox itype.Boxd, w *world.World) { + if math.Abs(delta) < MoveEps*10 { + return + } + + var hit bool = false + var deltaMin float64 = 10000 + + if delta > 1-MoveEps { + delta = 1 - MoveEps + } + if delta < -1+MoveEps { + delta = -1 + MoveEps + } + + // X+ / X- + if pointStuck(p, w) { + continue + } + + dest := hitbox.Offsetv(delta, 0, 0) + blockid := dest.Floor() + block := w.Block(blockid) + + var deltaDone float64 + if block.Id == 0 { + deltaDone = delta + } else { // block.Id!=0 + app := world.GetBlockAppearance(blockid, block.Id, block.Aux, block.Dataset) + blockBox := app.Hitbox.Offset(blockid.ToFloat64()) + if !app.NotSolid && blockBox.Contains(dest) { // Hit! + hit = true + if delta > 0 { // moving X+, hit on the X- face + deltaDone = blockBox.OffX - (e.pos[0] + hitbox.SizeX/2) - MoveEps + //log.Print("Hit X+: On Block ", blockid, ", delta=", delta, ", coord=", e.pos, ", p=", p, ", dest=", dest, ", deltaDone=", deltaDone) + } else { // moving X-, hit on the X+ face + deltaDone = blockBox.OffX + blockBox.SizeX - (e.pos[0] - hitbox.SizeX/2) + MoveEps + //log.Print("Hit X-: On Block ", blockid, ", delta=", delta, ", coord=", e.pos, ", p=", p, ", dest=", dest, ", deltaDone=", deltaDone) + } + } else { + deltaDone = delta + } + } + + deltaMin = util.AbsMind(deltaMin, deltaDone) + + if hit { + e.speed[0] = 0 + } + e.pos[0] += deltaMin +} +func (e *Entity) moveY(delta float64, hitbox itype.Boxd, w *world.World) { + if math.Abs(delta) < MoveEps*10 { + return + } + + var hit bool = false + var deltaMin float64 = 10000 + + if delta > 1-MoveEps { + delta = 1 - MoveEps + } + if delta < -1+MoveEps { + delta = -1 + MoveEps + } + + // Y+ / Y- + for _, p := range e.hp { + if pointStuck(p, w) { + continue + } + + dest := p.Addv(0, delta, 0) + blockid := dest.Floor() + block := w.Block(blockid) + + var deltaDone float64 + if block.Id == 0 { + deltaDone = delta + } else { // block.Id!=0 + app := world.GetBlockAppearance(blockid, block.Id, block.Aux, block.Dataset) + blockBox := app.Hitbox.Offset(blockid.ToFloat64()) + if !app.NotSolid && blockBox.Contains(dest) { // Hit! + hit = true + if delta > 0 { // moving Y+, hit on the Y- face + deltaDone = blockBox.OffY - (e.pos[1] + hitbox.SizeY) - MoveEps + //log.Print("Hit Y+: On Block ", blockid, ", delta=", delta, ", coord=", e.pos, ", p=", p, ", dest=", dest,", deltaDone=",deltaDone) + } else { // moving Y-, hit on the Y+ face (on the ground) + deltaDone = blockBox.OffY + blockBox.SizeY - e.pos[1] + MoveEps + //log.Print("Hit Y-: On Block ", blockid, ", delta=", delta, ", coord=", e.pos, ", p=", p, ", dest=", dest,", deltaDone=",deltaDone) + if !e.onGround { + log.Print("onGround = true") + } + e.onGround = true + } + } else { + deltaDone = delta + } + } + + deltaMin = util.AbsMind(deltaMin, deltaDone) + } + + if hit { + e.speed[1] = 0 + } + + if math.Abs(deltaMin) > MoveEps*10 { + if e.onGround { + log.Print("onGround = false") + } + e.onGround = false + } + e.pos[1] += deltaMin +} +func (e *Entity) moveZ(delta float64, hitbox itype.Boxd, w *world.World) { + if math.Abs(delta) < MoveEps*10 { + return + } + + var hit bool = false + var deltaMin float64 = 10000 + + if delta > 1-MoveEps { + delta = 1 - MoveEps + } + if delta < -1+MoveEps { + delta = -1 + MoveEps + } + + // Z+ / Z- + for _, p := range e.hp { + if pointStuck(p, w) { + continue + } + + dest := p.Addv(0, 0, delta) + blockid := dest.Floor() + block := w.Block(blockid) + + var deltaDone float64 + if block.Id == 0 { + deltaDone = delta + } else { // block.Id!=0 + app := world.GetBlockAppearance(blockid, block.Id, block.Aux, block.Dataset) + blockBox := app.Hitbox.Offset(blockid.ToFloat64()) + if !app.NotSolid && blockBox.Contains(dest) { // Hit! + hit = true + if delta > 0 { // moving Z+, hit on the Z- face + deltaDone = util.Maxd(blockBox.OffZ-(e.pos[2]+hitbox.SizeZ/2)-MoveEps, 0) + //log.Print("Hit Z+: On Block ", blockid, ", delta=", delta, ", coord=", e.pos, ", p=", p, ", dest=", dest, ", deltaDone=", deltaDone) + } else { // moving Z-, hit on the Z+ face + deltaDone = util.Mind(blockBox.OffZ+blockBox.SizeZ-(e.pos[2]-hitbox.SizeZ/2)+MoveEps, 0) + //log.Print("Hit Z-: On Block ", blockid, ", delta=", delta, ", coord=", e.pos, ", p=", p, ", dest=", dest, ", deltaDone=", deltaDone) + } + } else { + deltaDone = delta + } + } + + deltaMin = util.AbsMind(deltaMin, deltaDone) + } + + if hit { + e.speed[2] = 0 + } + e.pos[2] += deltaMin +} + +const gravity float64 = 26 +const deaclc float64 = 28 + +func (e *Entity) Update(world *world.World, deltaTime time.Duration) { + + hitbox := e.b.Hitbox(e.pos, e.ds) + box := e.worldHitbox(hitbox) + + e.hp = e.hp[0:0] + e.hp = e.boxHitpoints(e.hp, box) + + e.moveX(e.speed[0]*deltaTime.Seconds(), box, world) + e.moveY(e.speed[1]*deltaTime.Seconds(), box, world) + e.moveZ(e.speed[2]*deltaTime.Seconds(), box, world) + + speed := math.Sqrt(e.speed[0]*e.speed[0] + e.speed[2]*e.speed[2]) + if speed > MoveEps { + decrease := deaclc * deltaTime.Seconds() + if !e.onGround { + decrease /= 10 + } + factor := util.Maxd(util.Mind(speed-decrease, 9), 0) / speed + + e.speed[0] *= factor + e.speed[2] *= factor + } + + e.speed[1] -= gravity * deltaTime.Seconds() + + e.b.Update(e.pos, e.ds, world, deltaTime) +} diff --git a/internal/game/game.go b/internal/game/game.go new file mode 100644 index 0000000..e66c9f0 --- /dev/null +++ b/internal/game/game.go @@ -0,0 +1,49 @@ +package game + +import ( + "edgaru089.ml/go/gl01/internal/entity" + "edgaru089.ml/go/gl01/internal/render" + "edgaru089.ml/go/gl01/internal/util/itype" + "edgaru089.ml/go/gl01/internal/world" + "github.com/inkyblackness/imgui-go/v4" + + _ "edgaru089.ml/go/gl01/internal/entity/entities" +) + +// Game holds a game scene. +type Game struct { + world *world.World + + player *entity.Entity + + view *render.View + worldrender *render.WorldRenderer + + prevCursorPos itype.Vec2d + cameraPos itype.Vec3d + rotY itype.Angle + rotZ float32 // Degrees in range (-90, 90) + + fbSize itype.Vec2i + + io imgui.IO + paused bool +} + +// NewGame creates a new, empty Game. +func NewGame() (g *Game) { + return &Game{ + world: world.NewWorld(), + player: entity.NewEntity("player", itype.Vec3d{18, 80, 18}), + worldrender: &render.WorldRenderer{}, + cameraPos: itype.Vec3d{18, 80, 18}, + rotY: 0, + rotZ: 0, + } +} + +// LoadGameFromPath loads a saved game from a filesystem folder in a read-write fashion. +func LoadGameFromPath(path string) (g *Game, err error) { + + return +} diff --git a/internal/game/logic.go b/internal/game/logic.go new file mode 100644 index 0000000..42d6031 --- /dev/null +++ b/internal/game/logic.go @@ -0,0 +1,333 @@ +package game + +import ( + "fmt" + "image/color" + "log" + "os" + "runtime" + "time" + + "edgaru089.ml/go/gl01/internal/asset" + "edgaru089.ml/go/gl01/internal/igwrap" + "edgaru089.ml/go/gl01/internal/io" + "edgaru089.ml/go/gl01/internal/render" + "edgaru089.ml/go/gl01/internal/util/itype" + "edgaru089.ml/go/gl01/internal/world" + "edgaru089.ml/go/gl01/internal/world/worldgen" + "github.com/go-gl/gl/all-core/gl" + "github.com/go-gl/glfw/v3.3/glfw" + "github.com/go-gl/mathgl/mgl64" + "github.com/inkyblackness/imgui-go/v4" +) + +var logs string + +type actions struct { + loadChunkFile string + loadChunkID [2]int32 + + saveChunkFile string + saveChunkID [2]int32 +} + +var action *actions +var logFollow *bool + +func init() { + action = &actions{ + loadChunkFile: "chunk.gob", + loadChunkID: [2]int32{0, 0}, + saveChunkFile: "chunk.gob", + saveChunkID: [2]int32{0, 0}, + } + logFollow = new(bool) + (*logFollow) = true +} + +type logger struct{} + +func (logger) Write(b []byte) (n int, err error) { + logs = logs + string(b) + n, err = os.Stderr.Write(b) + return +} + +func init() { + log.Default().SetOutput(logger{}) +} + +// Init initializes the game. +func (g *Game) Init(win *glfw.Window) { + + g.world = world.NewWorld() + g.view = &render.View{} + g.worldrender = &render.WorldRenderer{} + + err := g.worldrender.Init(g.world) + if err != nil { + panic(err) + } + + var seed int64 = time.Now().Unix() + gensync := make(chan struct{}) + gensynccnt := 0 + for i := -8; i <= 8; i++ { + for j := -8; j <= 8; j++ { + c := &world.Chunk{} + g.world.SetChunk(i, j, c) + go func() { + worldgen.Chunk(c, g.world, seed) + gensync <- struct{}{} + }() + gensynccnt++ + } + } + + for i := 0; i < gensynccnt; i++ { + <-gensync + } + + width, height := win.GetSize() + g.view.Aspect(float32(width)/float32(height)).FovY(itype.Degrees(60)).LookAt(g.cameraPos.ToFloat32(), g.rotY, itype.Degrees(g.rotZ)) + + io.DisplaySize[0], io.DisplaySize[1] = win.GetFramebufferSize() + win.SetSizeCallback(func(w *glfw.Window, width, height int) { + win.SetCursorPos(float64(width)/2, float64(height)/2) + g.view.Aspect(float32(width) / float32(height)) + io.DisplaySize = itype.Vec2i{width, height} + }) + + err = render.Framewire.Init() + if err != nil { + panic(err) + } + + imgui.CreateContext(nil) + g.io = imgui.CurrentIO() + + cfg := imgui.NewFontConfig() + cfg.SetOversampleH(1) + cfg.SetOversampleV(1) + cfg.SetPixelSnapH(true) + g.io.Fonts().AddFontFromMemoryTTFV(asset.Unifont, 16, cfg, g.io.Fonts().GlyphRangesChineseFull()) + igwrap.Init(win) + + win.SetCursorPos(float64(width)/2, float64(height)/2) + + g.paused = true + //win.SetInputMode(glfw.CursorMode, glfw.CursorDisabled) + win.SetCursorPosCallback(func(w *glfw.Window, xpos, ypos float64) { + if g.paused { // GUI + return + } + + width, height := w.GetSize() + centerX, centerY := float64(width)/2, float64(height)/2 + deltaX, deltaY := xpos-centerX, ypos-centerY + g.rotY -= itype.Degrees(float32(deltaX) / 10) + g.rotZ -= float32(deltaY) / 10 + if g.rotZ > 89.99 { + g.rotZ = 89.99 + } + if g.rotZ < -89.99 { + g.rotZ = -89.99 + } + win.SetCursorPos(centerX, centerY) + }) + + win.SetMouseButtonCallback(func(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { + if g.paused { + igwrap.MouseButtonCallback(button, action) + } + }) + + win.SetKeyCallback(func(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { + if g.paused { + igwrap.KeyCallback(key, action) + } + if action == glfw.Press { + if g.paused { + if key == glfw.KeyEscape && !g.io.WantCaptureKeyboard() { + g.paused = false + win.SetInputMode(glfw.CursorMode, glfw.CursorDisabled) + width, height := w.GetSize() + win.SetCursorPos(float64(width)/2, float64(height)/2) + } + } else { + if key == glfw.KeyEscape { + g.paused = true + win.SetInputMode(glfw.CursorMode, glfw.CursorNormal) + width, height := w.GetSize() + win.SetCursorPos(float64(width)/2, float64(height)/2) + } + } + } + }) + + win.SetCharCallback(func(w *glfw.Window, char rune) { + if g.paused { + igwrap.InputCallback(char) + } + }) + + win.SetScrollCallback(func(w *glfw.Window, xpos, ypos float64) { + if g.paused { + igwrap.MouseScrollCallback(xpos, ypos) + } + }) +} + +const airAccel = 0.1 + +// Update updates the game state, not necessarily in the main thread. +func (g *Game) Update(win *glfw.Window, delta time.Duration) { + igwrap.NewFrame() + imgui.ShowDemoWindow(nil) + + if !g.paused { + + if win.GetKey(glfw.KeyLeft) == glfw.Press { + g.rotY += itype.Degrees(float32(delta.Seconds()) * 100) + } + if win.GetKey(glfw.KeyRight) == glfw.Press { + g.rotY -= itype.Degrees(float32(delta.Seconds()) * 100) + } + if win.GetKey(glfw.KeyUp) == glfw.Press { + g.rotZ += float32(delta.Seconds()) * 100 + if g.rotZ > 89.99 { + g.rotZ = 89.99 + } + } + if win.GetKey(glfw.KeyDown) == glfw.Press { + g.rotZ -= float32(delta.Seconds()) * 100 + if g.rotZ < -89.99 { + g.rotZ = -89.99 + } + } + + //forward := itype.Vec3f(mgl32.Rotate3DY(mgl32.DegToRad(g.rotY)).Mul3(mgl32.Rotate3DZ(mgl32.DegToRad(g.rotZ))).Mul3x1(mgl32.Vec3{1, 0, 0})).ToFloat64().Multiply(delta.Seconds()*8) + var walkaccel float64 + if g.player.OnGround() { + walkaccel = 48 + } else { + walkaccel = 16 + } + + forward := itype.Vec3d(mgl64.Rotate3DY(float64(g.rotY.Radians())).Mul3x1(mgl64.Vec3{1, 0, 0})) + right := forward.Cross(itype.Vec3d{0, 1, 0}) + accel := itype.Vec3d{0, 0, 0} + if win.GetKey(glfw.KeyW) == glfw.Press { + accel = accel.Add(forward.Multiply(walkaccel * delta.Seconds())) + } + if win.GetKey(glfw.KeyS) == glfw.Press { + accel = accel.Add(forward.Multiply(walkaccel * delta.Seconds()).Negative()) + } + if win.GetKey(glfw.KeyD) == glfw.Press { + accel = accel.Add(right.Multiply(walkaccel * delta.Seconds())) + } + if win.GetKey(glfw.KeyA) == glfw.Press { + accel = accel.Add(right.Multiply(walkaccel * delta.Seconds()).Negative()) + } + + if win.GetKey(glfw.KeySpace) == glfw.Press && g.player.OnGround() { + //log.Print("Jump!") + accel = accel.Addv(0, 8, 0) + } + + g.player.Accelerate(accel[0], accel[1], accel[2]) + } + + g.player.Update(g.world, delta) + + g.view.LookAt(g.player.EyePosition().ToFloat32(), g.rotY, itype.Degrees(g.rotZ)) + + render.Framewire.PushBox(g.player.WorldHitbox().ToFloat32(), color.White) + + if g.player.Position()[1] < -100 { + g.player.SetPosition(itype.Vec3d{18, 80, 18}) + g.player.SetSpeed(itype.Vec3d{}) + } + + if imgui.BeginV("Player", nil, imgui.WindowFlagsAlwaysAutoResize) { + pos := g.player.Position() + vel := g.player.Speed() + imgui.Text(fmt.Sprintf("Pos: (%.5f, %.5f, %.5f), Vel: (%.5f, %.5f, %.5f)", pos[0], pos[1], pos[2], vel[0], vel[1], vel[2])) + } + imgui.End() + + if imgui.BeginV("Go Runtime", nil, imgui.WindowFlagsAlwaysAutoResize) { + imgui.Text(fmt.Sprintf("%s/%s, compiler: %s", runtime.GOOS, runtime.GOARCH, runtime.Compiler)) + imgui.Text(fmt.Sprintf("NumCPU=%d, NumGOMAXPROCS=%d", runtime.NumCPU(), runtime.GOMAXPROCS(0))) + imgui.Text(fmt.Sprintf("NumCgoCalls=%d, NumGoroutine=%d", runtime.NumCgoCall(), runtime.NumGoroutine())) + imgui.Spacing() + if imgui.ButtonV("!!! PANIC !!!", imgui.Vec2{X: -2, Y: 0}) { + panic("Manual Panic") + } + } + imgui.End() + + if imgui.BeginV("Logs", nil, imgui.WindowFlagsMenuBar) { + if imgui.BeginMenuBar() { + if imgui.Button("Clear") { + logs = "" + } + if imgui.RadioButton("Follow", *logFollow) { + (*logFollow) = !(*logFollow) + } + imgui.EndMenuBar() + } + + var size *float32 + if size == nil { + size = new(float32) + } + imgui.BeginChildV("LogScroll", imgui.Vec2{}, true, 0) + imgui.Text(logs) + if (*size) != imgui.ScrollMaxY() && (*logFollow) { + imgui.SetScrollY(imgui.ScrollMaxY()) + } + (*size) = imgui.ScrollMaxY() + imgui.EndChild() + } + imgui.End() + + if imgui.Begin("Actions") { + + imgui.Text("Chunks") + imgui.Separator() + imgui.InputText("Load Filename", &action.loadChunkFile) + imgui.SliderInt2("Load ID", &action.loadChunkID, -10, 10) + if imgui.ButtonV("Load", imgui.Vec2{X: -2, Y: 0}) { + c := &world.Chunk{} + f, err := os.Open(action.loadChunkFile) + if err != nil { + log.Print("LoadChunk: ", err) + } else { + c.LoadFromGobIndexed(f, int(action.loadChunkID[0]), int(action.loadChunkID[1])) + g.world.SetChunk(int(action.loadChunkID[0]), int(action.loadChunkID[1]), c) + } + } + imgui.Separator() + imgui.InputText("Save Filename", &action.saveChunkFile) + imgui.SliderInt2("Save ID", &action.saveChunkID, -10, 10) + if imgui.ButtonV("Save", imgui.Vec2{X: -2, Y: 0}) { + c := g.world.Chunks[itype.Vec2i{int(action.saveChunkID[0]), int(action.saveChunkID[1])}] + f, _ := os.Create(action.saveChunkFile) + c.WriteToGob(f) + f.Close() + } + imgui.Separator() + + } + imgui.End() +} + +// Render, called with a OpenGL context, renders the game. +func (g *Game) Render(win *glfw.Window) { + gl.Viewport(0, 0, int32(io.DisplaySize[0]), int32(io.DisplaySize[1])) + g.worldrender.Render(g.world, g.view) + render.Framewire.Render(g.view) + + igwrap.Render(win) +} diff --git a/internal/game/logic.go.old b/internal/game/logic.go.old new file mode 100755 index 0000000..019de93 --- /dev/null +++ b/internal/game/logic.go.old @@ -0,0 +1,115 @@ +package game + +import ( + "time" + + "github.com/Edgaru089/gl01/internal/render" + "github.com/Edgaru089/gl01/internal/util/itype" + "github.com/Edgaru089/gl01/internal/world" + "github.com/Edgaru089/gl01/internal/world/worldgen" + "github.com/go-gl/gl/all-core/gl" + "github.com/go-gl/glfw/v3.3/glfw" + "github.com/go-gl/mathgl/mgl32" +) + +// Init initializes the game. +func (g *Game) Init(win *glfw.Window) { + + g.world = world.NewWorld() + g.view = &render.View{} + g.worldrender = &render.WorldRenderer{} + + var seed int64 = time.Now().Unix() + for i := -2; i <= 3; i++ { + for j := -2; j <= 3; j++ { + c := &world.Chunk{} + g.world.SetChunk(i, j, c) + worldgen.Chunk(c, g.world, seed) + } + } + + width, height := win.GetSize() + g.view.Aspect(float32(width)/float32(height)).FovY(60).LookAt(g.cameraPos.ToFloat32(), g.rotY, g.rotZ) + + err := g.worldrender.Init(g.world) + if err != nil { + panic(err) + } + + g.fbSize[0], g.fbSize[1] = win.GetFramebufferSize() + win.SetSizeCallback(func(w *glfw.Window, width, height int) { + win.SetCursorPos(float64(width)/2, float64(height)/2) + g.view.Aspect(float32(width) / float32(height)) + g.fbSize[0], g.fbSize[1] = w.GetFramebufferSize() + }) + + err = render.Framewire.Init() + if err != nil { + panic(err) + } + + win.SetCursorPos(float64(width)/2, float64(height)/2) + + win.SetInputMode(glfw.CursorMode, glfw.CursorDisabled) + win.SetCursorPosCallback(func(w *glfw.Window, xpos, ypos float64) { + width, height := w.GetSize() + centerX, centerY := float64(width)/2, float64(height)/2 + deltaX, deltaY := xpos-centerX, ypos-centerY + g.rotY -= float32(deltaX / 10) + g.rotZ -= float32(deltaY / 10) + if g.rotZ > 89.9 { + g.rotZ = 89.9 + } + if g.rotZ < -89.9 { + g.rotZ = -89.9 + } + win.SetCursorPos(centerX, centerY) + }) +} + +// Update updates the game state, not necessarily in the main thread. +func (g *Game) Update(win *glfw.Window, delta time.Duration) { + if win.GetKey(glfw.KeyLeft) == glfw.Press { + g.rotY += float32(delta.Seconds()) * 100 + } + if win.GetKey(glfw.KeyRight) == glfw.Press { + g.rotY -= float32(delta.Seconds()) * 100 + } + if win.GetKey(glfw.KeyUp) == glfw.Press { + g.rotZ += float32(delta.Seconds()) * 100 + if g.rotZ > 89.9 { + g.rotZ = 89.9 + } + } + if win.GetKey(glfw.KeyDown) == glfw.Press { + g.rotZ -= float32(delta.Seconds()) * 100 + if g.rotZ < -89.9 { + g.rotZ = -89.9 + } + } + + forward := itype.Vec3f(mgl32.Rotate3DY(mgl32.DegToRad(g.rotY)).Mul3(mgl32.Rotate3DZ(mgl32.DegToRad(g.rotZ))).Mul3x1(mgl32.Vec3{1, 0, 0})).ToFloat64().Multiply(delta.Seconds() * 8) + right := forward.Cross(itype.Vec3d{0, 1, 0}) + right = right.Multiply(1 / right.Length()).Multiply(delta.Seconds() * 8) + if win.GetKey(glfw.KeyW) == glfw.Press { + g.cameraPos = g.cameraPos.Add(forward) + } + if win.GetKey(glfw.KeyS) == glfw.Press { + g.cameraPos = g.cameraPos.Add(forward.Negative()) + } + if win.GetKey(glfw.KeyD) == glfw.Press { + g.cameraPos = g.cameraPos.Add(right) + } + if win.GetKey(glfw.KeyA) == glfw.Press { + g.cameraPos = g.cameraPos.Add(right.Negative()) + } + + g.view.LookAt(g.cameraPos.ToFloat32(), g.rotY, g.rotZ) +} + +// Render, called with a OpenGL context, renders the game. +func (g *Game) Render() { + gl.Viewport(0, 0, int32(g.fbSize[0]), int32(g.fbSize[1])) + g.worldrender.Render(g.world, g.view) + render.Framewire.Render(g.view) +} diff --git a/internal/game/logic.go.old2 b/internal/game/logic.go.old2 new file mode 100755 index 0000000..182af9a --- /dev/null +++ b/internal/game/logic.go.old2 @@ -0,0 +1,285 @@ +package game + +import ( + "fmt" + "image/color" + "log" + "os" + "runtime" + "time" + + "github.com/Edgaru089/gl01/internal/asset" + "github.com/Edgaru089/gl01/internal/igwrap" + "github.com/Edgaru089/gl01/internal/render" + "github.com/Edgaru089/gl01/internal/util" + "github.com/Edgaru089/gl01/internal/util/itype" + "github.com/Edgaru089/gl01/internal/world" + "github.com/Edgaru089/gl01/internal/world/worldgen" + "github.com/go-gl/gl/all-core/gl" + "github.com/go-gl/glfw/v3.3/glfw" + "github.com/go-gl/mathgl/mgl64" + "github.com/inkyblackness/imgui-go/v4" +) + +var logs string + +type logger struct{} + +func (logger) Write(b []byte) (n int, err error) { + logs = logs + string(b) + n, err = os.Stderr.Write(b) + return +} + +func init() { + log.Default().SetOutput(logger{}) +} + +// Init initializes the game. +func (g *Game) Init(win *glfw.Window) { + + g.world = world.NewWorld() + g.view = &render.View{} + g.worldrender = &render.WorldRenderer{} + + var seed int64 = time.Now().Unix() + gensync := make(chan struct{}) + gensynccnt := 0 + for i := -1; i <= 1; i++ { + for j := -1; j <= 1; j++ { + c := &world.Chunk{} + g.world.SetChunk(i, j, c) + go func() { + worldgen.Chunk(c, g.world, seed) + gensync <- struct{}{} + }() + gensynccnt++ + } + } + + for i := 0; i < gensynccnt; i++ { + <-gensync + } + + width, height := win.GetSize() + g.view.Aspect(float32(width)/float32(height)).FovY(60).LookAt(g.cameraPos.ToFloat32(), g.rotY, g.rotZ) + + err := g.worldrender.Init(g.world) + if err != nil { + panic(err) + } + + g.fbSize[0], g.fbSize[1] = win.GetFramebufferSize() + win.SetSizeCallback(func(w *glfw.Window, width, height int) { + win.SetCursorPos(float64(width)/2, float64(height)/2) + g.view.Aspect(float32(width) / float32(height)) + g.fbSize[0], g.fbSize[1] = w.GetFramebufferSize() + }) + + err = render.Framewire.Init() + if err != nil { + panic(err) + } + + imgui.CreateContext(nil) + g.io = imgui.CurrentIO() + + cfg := imgui.NewFontConfig() + cfg.SetOversampleH(1) + cfg.SetOversampleV(1) + cfg.SetPixelSnapH(true) + g.io.Fonts().AddFontFromMemoryTTFV(asset.Unifont, 16, cfg, g.io.Fonts().GlyphRangesChineseFull()) + igwrap.Init(win) + + win.SetCursorPos(float64(width)/2, float64(height)/2) + + g.paused = true + //win.SetInputMode(glfw.CursorMode, glfw.CursorDisabled) + win.SetCursorPosCallback(func(w *glfw.Window, xpos, ypos float64) { + if g.paused { // GUI + return + } + + width, height := w.GetSize() + centerX, centerY := float64(width)/2, float64(height)/2 + deltaX, deltaY := xpos-centerX, ypos-centerY + g.rotY -= float32(deltaX / 10) + g.rotZ -= float32(deltaY / 10) + if g.rotZ > 89.9 { + g.rotZ = 89.9 + } + if g.rotZ < -89.9 { + g.rotZ = -89.9 + } + g.rotY = util.FloatModf(g.rotY, 360) + win.SetCursorPos(centerX, centerY) + }) + + win.SetMouseButtonCallback(func(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) { + if g.paused { + igwrap.MouseButtonCallback(button, action) + } + }) + + win.SetKeyCallback(func(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) { + if g.paused { + igwrap.KeyCallback(key, action) + } + if action == glfw.Press { + if g.paused { + if key == glfw.KeyEscape && !g.io.WantCaptureKeyboard() { + g.paused = false + win.SetInputMode(glfw.CursorMode, glfw.CursorDisabled) + width, height := w.GetSize() + win.SetCursorPos(float64(width)/2, float64(height)/2) + } + } else { + if key == glfw.KeyEscape { + g.paused = true + win.SetInputMode(glfw.CursorMode, glfw.CursorNormal) + width, height := w.GetSize() + win.SetCursorPos(float64(width)/2, float64(height)/2) + } + } + } + }) + + win.SetCharCallback(func(w *glfw.Window, char rune) { + if g.paused { + igwrap.InputCallback(char) + } + }) + + win.SetScrollCallback(func(w *glfw.Window, xpos, ypos float64) { + if g.paused { + igwrap.MouseScrollCallback(xpos, ypos) + } + }) +} + +// Update updates the game state, not necessarily in the main thread. +func (g *Game) Update(win *glfw.Window, delta time.Duration) { + igwrap.NewFrame() + imgui.ShowDemoWindow(nil) + + if !g.paused { + + if win.GetKey(glfw.KeyLeft) == glfw.Press { + g.rotY += float32(delta.Seconds()) * 100 + } + if win.GetKey(glfw.KeyRight) == glfw.Press { + g.rotY -= float32(delta.Seconds()) * 100 + } + if win.GetKey(glfw.KeyUp) == glfw.Press { + g.rotZ += float32(delta.Seconds()) * 100 + if g.rotZ > 89.9 { + g.rotZ = 89.9 + } + } + if win.GetKey(glfw.KeyDown) == glfw.Press { + g.rotZ -= float32(delta.Seconds()) * 100 + if g.rotZ < -89.9 { + g.rotZ = -89.9 + } + } + + //forward := itype.Vec3f(mgl32.Rotate3DY(mgl32.DegToRad(g.rotY)).Mul3(mgl32.Rotate3DZ(mgl32.DegToRad(g.rotZ))).Mul3x1(mgl32.Vec3{1, 0, 0})).ToFloat64().Multiply(delta.Seconds()*8) + var walkaccel float64 + if g.player.OnGround() { + walkaccel = 48 + } else { + walkaccel = 8 + } + + forward := itype.Vec3d(mgl64.Rotate3DY(mgl64.DegToRad(float64(g.rotY))).Mul3x1(mgl64.Vec3{1, 0, 0})) + right := forward.Cross(itype.Vec3d{0, 1, 0}) + accel := itype.Vec3d{0, 0, 0} + if win.GetKey(glfw.KeyW) == glfw.Press { + accel = accel.Add(forward.Multiply(walkaccel * delta.Seconds())) + } + if win.GetKey(glfw.KeyS) == glfw.Press { + accel = accel.Add(forward.Multiply(walkaccel * delta.Seconds()).Negative()) + } + if win.GetKey(glfw.KeyD) == glfw.Press { + accel = accel.Add(right.Multiply(walkaccel * delta.Seconds())) + } + if win.GetKey(glfw.KeyA) == glfw.Press { + accel = accel.Add(right.Multiply(walkaccel * delta.Seconds()).Negative()) + } + + if win.GetKey(glfw.KeySpace) == glfw.Press && g.player.OnGround() { + //log.Print("Jump!") + accel = accel.Addv(0, 8, 0) + } + + g.player.Accelerate(accel[0], accel[1], accel[2]) + } + + g.player.Update(g.world, delta) + + g.view.LookAt(g.player.EyePosition().ToFloat32(), g.rotY, g.rotZ) + + render.Framewire.PushBox(g.player.WorldHitbox().ToFloat32(), color.White) + + if g.player.Position()[1] < -100 { + g.player.SetPosition(itype.Vec3d{18, 80, 18}) + g.player.SetSpeed(itype.Vec3d{}) + } + + if imgui.BeginV("Player", nil, imgui.WindowFlagsAlwaysAutoResize) { + pos := g.player.Position() + vel := g.player.Speed() + imgui.Text(fmt.Sprintf("Pos: (%.5f, %.5f, %.5f), Vel: (%.5f, %.5f, %.5f)", pos[0], pos[1], pos[2], vel[0], vel[1], vel[2])) + } + imgui.End() + + if imgui.BeginV("Go Runtime", nil, imgui.WindowFlagsAlwaysAutoResize) { + imgui.Text(fmt.Sprintf("%s/%s, compiler: %s", runtime.GOOS, runtime.GOARCH, runtime.Compiler)) + imgui.Text(fmt.Sprintf("NumCPU=%d, NumGOMAXPROCS=%d", runtime.NumCPU(), runtime.GOMAXPROCS(0))) + imgui.Text(fmt.Sprintf("NumCgoCalls=%d, NumGoroutine=%d", runtime.NumCgoCall(), runtime.NumGoroutine())) + imgui.Spacing() + if imgui.ButtonV("!!! PANIC !!!", imgui.Vec2{X: -2, Y: 0}) { + panic("Manual Panic") + } + } + imgui.End() + + var logFollow *bool + if logFollow == nil { + logFollow = new(bool) + (*logFollow) = true + } + if imgui.BeginV("Logs", nil, imgui.WindowFlagsMenuBar) { + if imgui.BeginMenuBar() { + if imgui.Button("Clear") { + logs = "" + } + if imgui.RadioButton("Follow", *logFollow) { + (*logFollow) = !(*logFollow) + } + imgui.EndMenuBar() + } + + var size *float32 + if size == nil { + size = new(float32) + } + imgui.BeginChildV("LogScroll", imgui.Vec2{}, true, 0) + imgui.Text(logs) + if (*size) != imgui.ScrollMaxY() && (*logFollow) { + imgui.SetScrollY(imgui.ScrollMaxY()) + } + (*size) = imgui.ScrollMaxY() + imgui.EndChild() + } + imgui.End() +} + +// Render, called with a OpenGL context, renders the game. +func (g *Game) Render(win *glfw.Window) { + gl.Viewport(0, 0, int32(g.fbSize[0]), int32(g.fbSize[1])) + g.worldrender.Render(g.world, g.view) + render.Framewire.Render(g.view) + + igwrap.Render(win) +} diff --git a/internal/igwrap/glfw.go b/internal/igwrap/glfw.go new file mode 100644 index 0000000..9f6ee11 --- /dev/null +++ b/internal/igwrap/glfw.go @@ -0,0 +1,122 @@ +package igwrap + +import ( + "time" + + "github.com/go-gl/glfw/v3.3/glfw" + "github.com/inkyblackness/imgui-go/v4" +) + +const ( + mouseButtonPrimary = iota + mouseButtonSecondary + mouseButtonTertiary + mouseButtonCount +) + +var ( + win *glfw.Window + io imgui.IO + + lastframe time.Time + mouseJustPressed [mouseButtonCount]bool +) + +func Init(window *glfw.Window) { + win = window + io = imgui.CurrentIO() + + setKeymap() + lastframe = time.Now() + + renderInit() +} + +// NewFrame marks the begin of a render pass. +func NewFrame() { + dsx, dsy := win.GetSize() + io.SetDisplaySize(imgui.Vec2{X: float32(dsx), Y: float32(dsy)}) + + now := time.Now() + io.SetDeltaTime(float32(time.Since(lastframe).Seconds())) + lastframe = now + + if win.GetAttrib(glfw.Focused) != 0 { + x, y := win.GetCursorPos() + io.SetMousePosition(imgui.Vec2{X: float32(x), Y: float32(y)}) + } + + for i := 0; i < mouseButtonCount; i++ { + down := mouseJustPressed[i] || (win.GetMouseButton(glfwButtonIDByIndex[i]) == glfw.Press) + io.SetMouseButtonDown(i, down) + mouseJustPressed[i] = false + } + imgui.NewFrame() +} + +func setKeymap() { + io.KeyMap(imgui.KeyTab, int(glfw.KeyTab)) + io.KeyMap(imgui.KeyLeftArrow, int(glfw.KeyLeft)) + io.KeyMap(imgui.KeyRightArrow, int(glfw.KeyRight)) + io.KeyMap(imgui.KeyUpArrow, int(glfw.KeyUp)) + io.KeyMap(imgui.KeyDownArrow, int(glfw.KeyDown)) + io.KeyMap(imgui.KeyPageUp, int(glfw.KeyPageUp)) + io.KeyMap(imgui.KeyPageDown, int(glfw.KeyPageDown)) + io.KeyMap(imgui.KeyHome, int(glfw.KeyHome)) + io.KeyMap(imgui.KeyEnd, int(glfw.KeyEnd)) + io.KeyMap(imgui.KeyInsert, int(glfw.KeyInsert)) + io.KeyMap(imgui.KeyDelete, int(glfw.KeyDelete)) + io.KeyMap(imgui.KeyBackspace, int(glfw.KeyBackspace)) + io.KeyMap(imgui.KeySpace, int(glfw.KeySpace)) + io.KeyMap(imgui.KeyEnter, int(glfw.KeyEnter)) + io.KeyMap(imgui.KeyEscape, int(glfw.KeyEscape)) + io.KeyMap(imgui.KeyA, int(glfw.KeyA)) + io.KeyMap(imgui.KeyC, int(glfw.KeyC)) + io.KeyMap(imgui.KeyV, int(glfw.KeyV)) + io.KeyMap(imgui.KeyX, int(glfw.KeyX)) + io.KeyMap(imgui.KeyY, int(glfw.KeyY)) + io.KeyMap(imgui.KeyZ, int(glfw.KeyZ)) +} + +var glfwButtonIndexByID = map[glfw.MouseButton]int{ + glfw.MouseButton1: mouseButtonPrimary, + glfw.MouseButton2: mouseButtonSecondary, + glfw.MouseButton3: mouseButtonTertiary, +} + +var glfwButtonIDByIndex = map[int]glfw.MouseButton{ + mouseButtonPrimary: glfw.MouseButton1, + mouseButtonSecondary: glfw.MouseButton2, + mouseButtonTertiary: glfw.MouseButton3, +} + +// MouseButtonCallback is the callback called when the mouse button changes. +func MouseButtonCallback(button glfw.MouseButton, action glfw.Action) { + if index, known := glfwButtonIndexByID[button]; known && (action == glfw.Press) { + mouseJustPressed[index] = true + } +} + +// MouseScrollCallback is called when scroll status changes. +func MouseScrollCallback(x, y float64) { + io.AddMouseWheelDelta(float32(x), float32(y)) +} + +// KeyCallback is called when a key is pressed or released. +func KeyCallback(key glfw.Key, action glfw.Action) { + if action == glfw.Press { + io.KeyPress(int(key)) + } + if action == glfw.Release { + io.KeyRelease(int(key)) + } + io.KeyCtrl(int(glfw.KeyLeftControl), int(glfw.KeyRightControl)) + io.KeyShift(int(glfw.KeyLeftShift), int(glfw.KeyRightShift)) + io.KeyAlt(int(glfw.KeyLeftAlt), int(glfw.KeyRightAlt)) + io.KeySuper(int(glfw.KeyLeftSuper), int(glfw.KeyRightSuper)) +} + +// InputCallback is called when a char is inputed (CharChange) +func InputCallback(input rune) { + io.AddInputCharacters(string(input)) +} diff --git a/internal/igwrap/render.go b/internal/igwrap/render.go new file mode 100644 index 0000000..f59c7e9 --- /dev/null +++ b/internal/igwrap/render.go @@ -0,0 +1,248 @@ +package igwrap + +import ( + _ "embed" + + "edgaru089.ml/go/gl01/internal/render" + "github.com/go-gl/gl/all-core/gl" + "github.com/go-gl/glfw/v3.3/glfw" + "github.com/go-gl/mathgl/mgl32" + "github.com/inkyblackness/imgui-go/v4" +) + +//go:embed shader.vert +var vertex string + +//go:embed shader.frag +var fragment string + +var ( + shader *render.Shader + texture *render.Texture + + vbo, elem uint32 + attribPosition, attribUV, attribColor uint32 +) + +func renderInit() { + // Backup GL state + var lastTexture int32 + var lastArrayBuffer int32 + var lastVertexArray int32 + gl.GetIntegerv(gl.TEXTURE_BINDING_2D, &lastTexture) + gl.GetIntegerv(gl.ARRAY_BUFFER_BINDING, &lastArrayBuffer) + gl.GetIntegerv(gl.VERTEX_ARRAY_BINDING, &lastVertexArray) + + var err error + shader, err = render.NewShader(vertex, fragment) + if err != nil { + panic("igwrap.renderInit(): " + err.Error()) + } + + gl.BindFragDataLocation(shader.Handle(), 0, gl.Str("outputColor\x00")) + + gl.GenBuffers(1, &vbo) + gl.GenBuffers(1, &elem) + + CreateFontsTexture() + shader.SetUniformTexture("tex", texture) + + attribPosition = uint32(gl.GetAttribLocation(shader.Handle(), gl.Str("pos\x00"))) + attribUV = uint32(gl.GetAttribLocation(shader.Handle(), gl.Str("uv\x00"))) + attribColor = uint32(gl.GetAttribLocation(shader.Handle(), gl.Str("color\x00"))) + + // Restore modified GL state + gl.BindTexture(gl.TEXTURE_2D, uint32(lastTexture)) + gl.BindBuffer(gl.ARRAY_BUFFER, uint32(lastArrayBuffer)) + gl.BindVertexArray(uint32(lastVertexArray)) +} + +func CreateFontsTexture() { + + // build the texture atlas + io := imgui.CurrentIO() + image := io.Fonts().TextureDataAlpha8() + + var lastTexture int32 + gl.GetIntegerv(gl.TEXTURE_BINDING_2D, &lastTexture) + + if texture != nil { + texture.Free() + } + texture = render.NewTexture() + tex := texture.Handle() + + gl.BindTexture(gl.TEXTURE_2D, tex) + gl.TexImage2D( + gl.TEXTURE_2D, + 0, + gl.RED, + int32(image.Width), + int32(image.Height), + 0, + gl.RED, + gl.UNSIGNED_BYTE, + image.Pixels, + ) + + io.Fonts().SetTextureID(imgui.TextureID(tex)) + + gl.BindTexture(gl.TEXTURE_2D, uint32(lastTexture)) +} + +func Render(win *glfw.Window) { + displayWidth, displayHeight := win.GetSize() + fbWidth, fbHeight := win.GetFramebufferSize() + + imgui.Render() + draw := imgui.RenderedDrawData() + + draw.ScaleClipRects(imgui.Vec2{ + X: float32(fbWidth) / float32(displayWidth), + Y: float32(fbHeight) / float32(displayHeight), + }) + + // Backup GL state + var lastActiveTexture int32 + gl.GetIntegerv(gl.ACTIVE_TEXTURE, &lastActiveTexture) + gl.ActiveTexture(gl.TEXTURE0) + var lastProgram int32 + gl.GetIntegerv(gl.CURRENT_PROGRAM, &lastProgram) + var lastTexture int32 + gl.GetIntegerv(gl.TEXTURE_BINDING_2D, &lastTexture) + var lastSampler int32 + gl.GetIntegerv(gl.SAMPLER_BINDING, &lastSampler) + var lastArrayBuffer int32 + gl.GetIntegerv(gl.ARRAY_BUFFER_BINDING, &lastArrayBuffer) + var lastElementArrayBuffer int32 + gl.GetIntegerv(gl.ELEMENT_ARRAY_BUFFER_BINDING, &lastElementArrayBuffer) + var lastVertexArray int32 + gl.GetIntegerv(gl.VERTEX_ARRAY_BINDING, &lastVertexArray) + var lastPolygonMode [2]int32 + gl.GetIntegerv(gl.POLYGON_MODE, &lastPolygonMode[0]) + var lastViewport [4]int32 + gl.GetIntegerv(gl.VIEWPORT, &lastViewport[0]) + var lastScissorBox [4]int32 + gl.GetIntegerv(gl.SCISSOR_BOX, &lastScissorBox[0]) + var lastBlendSrcRgb int32 + gl.GetIntegerv(gl.BLEND_SRC_RGB, &lastBlendSrcRgb) + var lastBlendDstRgb int32 + gl.GetIntegerv(gl.BLEND_DST_RGB, &lastBlendDstRgb) + var lastBlendSrcAlpha int32 + gl.GetIntegerv(gl.BLEND_SRC_ALPHA, &lastBlendSrcAlpha) + var lastBlendDstAlpha int32 + gl.GetIntegerv(gl.BLEND_DST_ALPHA, &lastBlendDstAlpha) + var lastBlendEquationRgb int32 + gl.GetIntegerv(gl.BLEND_EQUATION_RGB, &lastBlendEquationRgb) + var lastBlendEquationAlpha int32 + gl.GetIntegerv(gl.BLEND_EQUATION_ALPHA, &lastBlendEquationAlpha) + lastEnableBlend := gl.IsEnabled(gl.BLEND) + lastEnableCullFace := gl.IsEnabled(gl.CULL_FACE) + lastEnableDepthTest := gl.IsEnabled(gl.DEPTH_TEST) + lastEnableScissorTest := gl.IsEnabled(gl.SCISSOR_TEST) + + // 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. + gl.Viewport(0, 0, int32(fbWidth), int32(fbHeight)) + orthoProjection := mgl32.Mat4{ + 2.0 / float32(displayWidth), 0.0, 0.0, 0.0, + 0.0, 2.0 / float32(-displayHeight), 0.0, 0.0, + 0.0, 0.0, -1.0, 0.0, + -1.0, 1.0, 0.0, 1.0, + } + shader.BindTextures() + shader.UseProgram() + shader.SetUniformMat4("projection", orthoProjection) + 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.) + var vao uint32 + gl.GenVertexArrays(1, &vao) + gl.BindVertexArray(vao) + gl.BindBuffer(gl.ARRAY_BUFFER, vbo) + gl.EnableVertexAttribArray(uint32(attribPosition)) + gl.EnableVertexAttribArray(uint32(attribUV)) + gl.EnableVertexAttribArray(uint32(attribColor)) + vertexSize, vertexOffsetPos, vertexOffsetUv, vertexOffsetCol := imgui.VertexBufferLayout() + gl.VertexAttribPointerWithOffset(uint32(attribPosition), 2, gl.FLOAT, false, int32(vertexSize), uintptr(vertexOffsetPos)) + gl.VertexAttribPointerWithOffset(uint32(attribUV), 2, gl.FLOAT, false, int32(vertexSize), uintptr(vertexOffsetUv)) + gl.VertexAttribPointerWithOffset(uint32(attribColor), 4, gl.UNSIGNED_BYTE, true, int32(vertexSize), uintptr(vertexOffsetCol)) + indexSize := imgui.IndexBufferLayout() + drawType := gl.UNSIGNED_SHORT + const bytesPerUint32 = 4 + if indexSize == bytesPerUint32 { + drawType = gl.UNSIGNED_INT + } + + // Draw + for _, list := range draw.CommandLists() { + var indexBufferOffset uintptr + + vertexBuffer, vertexBufferSize := list.VertexBuffer() + gl.BindBuffer(gl.ARRAY_BUFFER, vbo) + gl.BufferData(gl.ARRAY_BUFFER, vertexBufferSize, vertexBuffer, gl.STREAM_DRAW) + + indexBuffer, indexBufferSize := list.IndexBuffer() + gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, elem) + 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, 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.DrawElementsWithOffset(gl.TRIANGLES, int32(cmd.ElementCount()), uint32(drawType), indexBufferOffset) + } + indexBufferOffset += uintptr(cmd.ElementCount() * indexSize) + } + } + gl.DeleteVertexArrays(1, &vao) + + // Restore modified GL state + gl.UseProgram(uint32(lastProgram)) + gl.BindTexture(gl.TEXTURE_2D, uint32(lastTexture)) + gl.BindSampler(0, uint32(lastSampler)) + gl.ActiveTexture(uint32(lastActiveTexture)) + gl.BindVertexArray(uint32(lastVertexArray)) + gl.BindBuffer(gl.ARRAY_BUFFER, uint32(lastArrayBuffer)) + gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, uint32(lastElementArrayBuffer)) + gl.BlendEquationSeparate(uint32(lastBlendEquationRgb), uint32(lastBlendEquationAlpha)) + gl.BlendFuncSeparate(uint32(lastBlendSrcRgb), uint32(lastBlendDstRgb), uint32(lastBlendSrcAlpha), uint32(lastBlendDstAlpha)) + if lastEnableBlend { + gl.Enable(gl.BLEND) + } else { + gl.Disable(gl.BLEND) + } + if lastEnableCullFace { + gl.Enable(gl.CULL_FACE) + } else { + gl.Disable(gl.CULL_FACE) + } + if lastEnableDepthTest { + gl.Enable(gl.DEPTH_TEST) + } else { + gl.Disable(gl.DEPTH_TEST) + } + if lastEnableScissorTest { + gl.Enable(gl.SCISSOR_TEST) + } else { + gl.Disable(gl.SCISSOR_TEST) + } + gl.PolygonMode(gl.FRONT_AND_BACK, uint32(lastPolygonMode[0])) + gl.Viewport(lastViewport[0], lastViewport[1], lastViewport[2], lastViewport[3]) + gl.Scissor(lastScissorBox[0], lastScissorBox[1], lastScissorBox[2], lastScissorBox[3]) +} diff --git a/internal/igwrap/shader.frag b/internal/igwrap/shader.frag new file mode 100644 index 0000000..7500784 --- /dev/null +++ b/internal/igwrap/shader.frag @@ -0,0 +1,13 @@ +#version 330 + +uniform sampler2D tex; + +in vec2 fragUV; +in vec4 fragColor; + +out vec4 outputColor; + +void main() { + outputColor = vec4(fragColor.rgb, fragColor.a * texture(tex, fragUV.st).r); +} + diff --git a/internal/igwrap/shader.vert b/internal/igwrap/shader.vert new file mode 100644 index 0000000..a230d00 --- /dev/null +++ b/internal/igwrap/shader.vert @@ -0,0 +1,17 @@ +#version 330 + +uniform mat4 projection; + +in vec2 pos; +in vec2 uv; +in vec4 color; + +out vec2 fragUV; +out vec4 fragColor; + +void main() { + fragUV = uv; + fragColor = color; + gl_Position = projection * vec4(pos.xy, 0, 1); +} + diff --git a/internal/io/io.go b/internal/io/io.go new file mode 100644 index 0000000..d714800 --- /dev/null +++ b/internal/io/io.go @@ -0,0 +1,7 @@ +package io + +import "edgaru089.ml/go/gl01/internal/util/itype" + +var ( + DisplaySize itype.Vec2i // Size of the window viewport in pixels. +) diff --git a/internal/render/gpu_preference/gpu_preference.c b/internal/render/gpu_preference/gpu_preference.c new file mode 100644 index 0000000..7013eef --- /dev/null +++ b/internal/render/gpu_preference/gpu_preference.c @@ -0,0 +1,10 @@ + +// define the two symbols on Windows so that application +// could benefit from using the more powerful discrete GPU + +#if defined (_WIN32) + +__declspec(dllexport) unsigned long NvOptimusEnablement = 1; +__declspec(dllexport) unsigned long AmdPowerXpressRequestHighPerformance = 1; + +#endif diff --git a/internal/render/gpu_preference/gpu_preference.go b/internal/render/gpu_preference/gpu_preference.go new file mode 100644 index 0000000..29bd149 --- /dev/null +++ b/internal/render/gpu_preference/gpu_preference.go @@ -0,0 +1,3 @@ +package gpu_preference + +import "C" diff --git a/internal/render/gpu_preference/linux_nvidia.go b/internal/render/gpu_preference/linux_nvidia.go new file mode 100644 index 0000000..c04ca64 --- /dev/null +++ b/internal/render/gpu_preference/linux_nvidia.go @@ -0,0 +1,15 @@ +//+build nvidia + +package gpu_preference + +import ( + "os" + "runtime" +) + +func init() { + if runtime.GOOS == "linux" { + os.Setenv("__NV_PRIME_RENDER_OFFLOAD", "1") + os.Setenv("__GLX_VENDOR_LIBRARY_NAME", "nvidia") + } +} diff --git a/internal/render/render_framewire.go b/internal/render/render_framewire.go new file mode 100644 index 0000000..8aca7f5 --- /dev/null +++ b/internal/render/render_framewire.go @@ -0,0 +1,127 @@ +package render + +import ( + "image/color" + "unsafe" + + "edgaru089.ml/go/gl01/internal/asset" + "edgaru089.ml/go/gl01/internal/util/itype" + "github.com/go-gl/gl/all-core/gl" + "github.com/go-gl/mathgl/mgl32" +) + +// framewire vertex +type frvertex struct { + pos itype.Vec3f + color itype.Vec4f +} + +// FramewireRenderer is a renderer drawing framewires. +// +// It is mainly for debugging uses. +type FramewireRenderer struct { + shader *Shader + + vao, vbo uint32 + vertex []frvertex +} + +// Framewire is a global FramewireRenderer managed by game logic, for debugging. +var Framewire = &FramewireRenderer{} + +func (f *FramewireRenderer) Init() (err error) { + + f.shader, err = NewShader(asset.FramewireShaderVert, asset.FramewireShaderFrag) + if err != nil { + return err + } + + f.shader.SetUniformMat4("model", mgl32.Ident4()) + gl.BindFragDataLocation(f.shader.Handle(), 0, gl.Str("outputColor\x00")) + + gl.GenVertexArrays(1, &f.vao) + gl.BindVertexArray(f.vao) + gl.GenBuffers(1, &f.vbo) + gl.BindBuffer(gl.ARRAY_BUFFER, f.vbo) + + vertAttrib := uint32(gl.GetAttribLocation(f.shader.Handle(), gl.Str("vert\x00"))) + gl.VertexAttribPointer(vertAttrib, 3, gl.FLOAT, false, int32(unsafe.Sizeof(frvertex{})), gl.PtrOffset(int(unsafe.Offsetof(frvertex{}.pos)))) + + colorAttrib := uint32(gl.GetAttribLocation(f.shader.Handle(), gl.Str("vertColor\x00"))) + gl.VertexAttribPointer(colorAttrib, 4, gl.FLOAT, false, int32(unsafe.Sizeof(frvertex{})), gl.PtrOffset(int(unsafe.Offsetof(frvertex{}.color)))) + + gl.EnableVertexAttribArray(vertAttrib) + gl.EnableVertexAttribArray(colorAttrib) + + return nil +} + +func colorToVec4f(color color.Color) itype.Vec4f { + r, g, b, a := color.RGBA() + return itype.Vec4f{float32(r) / 0xffff, float32(g) / 0xffff, float32(b) / 0xffff, float32(a) / 0xffff} +} + +// PushLine pushes a line from p0 to p1 into the vertex array. +func (f *FramewireRenderer) PushLine(p0, p1 itype.Vec3f, color0, color1 color.Color) { + f.vertex = append(f.vertex, + frvertex{ + pos: p0, + color: colorToVec4f(color0), + }, + frvertex{ + pos: p1, + color: colorToVec4f(color1), + }, + ) +} + +const FramewireSizeShrink = 1e-2 + +// PushBox pushes a 3D box into the vertex array. +// +// The size of the box is shrunk by a little bit so that it is not obstructed by the world. +func (f *FramewireRenderer) PushBox(box itype.Boxf, color color.Color) { + box = itype.Boxf{ + OffX: box.OffX + FramewireSizeShrink, + OffY: box.OffY + 5*FramewireSizeShrink, + OffZ: box.OffZ + FramewireSizeShrink, + SizeX: box.SizeX - 2*FramewireSizeShrink, + SizeY: box.SizeY - 10*FramewireSizeShrink, + SizeZ: box.SizeZ - 2*FramewireSizeShrink, + } + base := box.MinPoint() + + f.PushLine(base, base.Addv(box.SizeX, 0, 0), color, color) + f.PushLine(base, base.Addv(0, 0, box.SizeZ), color, color) + f.PushLine(base.Addv(box.SizeX, 0, box.SizeZ), base.Addv(box.SizeX, 0, 0), color, color) + f.PushLine(base.Addv(box.SizeX, 0, box.SizeZ), base.Addv(0, 0, box.SizeZ), color, color) + + f.PushLine(base, base.Addv(0, box.SizeY, 0), color, color) + f.PushLine(base.Addv(box.SizeX, 0, 0), base.Addv(box.SizeX, box.SizeY, 0), color, color) + f.PushLine(base.Addv(0, 0, box.SizeZ), base.Addv(0, box.SizeY, box.SizeZ), color, color) + f.PushLine(base.Addv(box.SizeX, 0, box.SizeZ), base.Addv(box.SizeX, box.SizeY, box.SizeZ), color, color) + + f.PushLine(base.Addv(0, box.SizeY, 0), base.Addv(box.SizeX, box.SizeY, 0), color, color) + f.PushLine(base.Addv(0, box.SizeY, 0), base.Addv(0, box.SizeY, box.SizeZ), color, color) + f.PushLine(base.Addv(box.SizeX, box.SizeY, box.SizeZ), base.Addv(box.SizeX, box.SizeY, 0), color, color) + f.PushLine(base.Addv(box.SizeX, box.SizeY, box.SizeZ), base.Addv(0, box.SizeY, box.SizeZ), color, color) +} + +// Render renders the framewire into the current OpenGL context and clears it. +func (f *FramewireRenderer) Render(view *View) { + if len(f.vertex) == 0 { + return + } + + f.shader.UseProgram() + f.shader.SetUniformMat4("view", view.View()) + f.shader.SetUniformMat4("projection", view.Perspective()) + + gl.BindVertexArray(f.vao) + gl.BindBuffer(gl.ARRAY_BUFFER, f.vbo) + gl.BufferData(gl.ARRAY_BUFFER, len(f.vertex)*int(unsafe.Sizeof(frvertex{})), gl.Ptr(f.vertex), gl.STREAM_DRAW) + + gl.DrawArrays(gl.LINES, 0, int32(len(f.vertex))) + + f.vertex = f.vertex[0:0] +} diff --git a/internal/render/render_world.go b/internal/render/render_world.go new file mode 100644 index 0000000..5a5f382 --- /dev/null +++ b/internal/render/render_world.go @@ -0,0 +1,130 @@ +package render + +import ( + "unsafe" + + "edgaru089.ml/go/gl01/internal/asset" + "edgaru089.ml/go/gl01/internal/io" + "edgaru089.ml/go/gl01/internal/util/itype" + "edgaru089.ml/go/gl01/internal/world" + "github.com/go-gl/gl/all-core/gl" + "github.com/go-gl/mathgl/mgl32" + "github.com/inkyblackness/imgui-go/v4" +) + +var ( + ShadowmapSize = itype.Vec2i{3072, 3072} // Size of the shadow mapping +) + +// WorldRenderer holds texture/shader resource and viewport +// information for world rendering. +type WorldRenderer struct { + shader *Shader + texture *Texture + + depthmapFBO, depthmap uint32 + depthmapShader *Shader +} + +// The default WorldRenderer. +var DefaultWorldRenderer WorldRenderer + +// Init initializes the WorldRenderer. +func (r *WorldRenderer) Init(w *world.World) (err error) { + + r.shader, err = NewShader(asset.WorldShaderVert, asset.WorldShaderFrag) + if err != nil { + return err + } + + r.depthmapShader, err = NewShader(asset.WorldShaderShadowmapVert, asset.WorldShaderShadowmapFrag) + if err != nil { + return err + } + + asset.InitWorldTextureAtlas() + r.texture = NewTextureRGBA(asset.WorldTextureAtlas.Image) + r.shader.SetUniformTexture("tex", r.texture) + + r.shader.SetUniformMat4("model", mgl32.Ident4()) + r.depthmapShader.SetUniformMat4("model", mgl32.Ident4()) + // and view and projection uniforms not yet set + gl.BindFragDataLocation(r.shader.Handle(), 0, gl.Str("outputColor\x00")) + + // Set the pointers for attributions + vertAttrib := r.shader.GetAttribLocation("vert") + gl.VertexAttribPointer(vertAttrib, 3, gl.FLOAT, false, int32(unsafe.Sizeof(world.Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(world.Vertex{}.World)))) + normalAttrib := r.shader.GetAttribLocation("normal") + gl.VertexAttribPointer(normalAttrib, 3, gl.FLOAT, false, int32(unsafe.Sizeof(world.Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(world.Vertex{}.Normal)))) + texCoordAttrib := r.shader.GetAttribLocation("vertTexCoord") + gl.VertexAttribPointer(texCoordAttrib, 2, gl.FLOAT, false, int32(unsafe.Sizeof(world.Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(world.Vertex{}.Texture)))) + lightAttrib := r.shader.GetAttribLocation("light") + gl.VertexAttribPointer(lightAttrib, 1, gl.FLOAT, false, int32(unsafe.Sizeof(world.Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(world.Vertex{}.Light)))) + + // generate the depthmap and depthmap FBO + gl.GenFramebuffers(1, &r.depthmapFBO) + gl.GenTextures(1, &r.depthmap) + gl.BindTexture(gl.TEXTURE_2D, r.depthmap) + gl.TexImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, int32(ShadowmapSize[0]), int32(ShadowmapSize[1]), 0, gl.DEPTH_COMPONENT, gl.FLOAT, nil) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_BORDER) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_BORDER) + borderColor := []float32{1, 1, 1, 1} + gl.TexParameterfv(gl.TEXTURE_2D, gl.TEXTURE_BORDER_COLOR, &borderColor[0]) + // attach depth texture as FBO's depth buffer + gl.BindFramebuffer(gl.FRAMEBUFFER, r.depthmapFBO) + gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, r.depthmap, 0) + gl.DrawBuffer(gl.NONE) + gl.ReadBuffer(gl.NONE) + gl.BindFramebuffer(gl.FRAMEBUFFER, 0) + + r.shader.SetUniformTextureHandle("shadowmap", r.depthmap) + + gl.Enable(gl.CULL_FACE) + gl.Enable(gl.DEPTH_TEST) + gl.DepthFunc(gl.LESS) + + return nil +} + +var sun = [3]float32{0.2, 0.4, 0.3} + +func (r *WorldRenderer) Render(world *world.World, view *View) { + imgui.SliderFloat3("Sun", &sun, -1, 1) + normalSun := itype.Vec3f(sun).Normalize() + + // 1. Render to depth map + gl.Viewport(0, 0, int32(ShadowmapSize[0]), int32(ShadowmapSize[1])) + gl.BindFramebuffer(gl.FRAMEBUFFER, r.depthmapFBO) + gl.Clear(gl.DEPTH_BUFFER_BIT) + + lightPos := view.EyePos.Add(normalSun.Multiply(20)) + lightView := mgl32.LookAt(lightPos[0], lightPos[1], lightPos[2], view.EyePos[0], view.EyePos[1], view.EyePos[2], 0, 1, 0) + lightProjection := mgl32.Ortho(-50, 50, -50, 50, 1, 50) + lightspace := lightProjection.Mul4(lightView) + + r.depthmapShader.UseProgram() + r.depthmapShader.SetUniformMat4("lightspace", lightspace) + + world.Render() + + gl.BindFramebuffer(gl.FRAMEBUFFER, 0) + + // 2. Render the scene + gl.Viewport(0, 0, int32(io.DisplaySize[0]), int32(io.DisplaySize[1])) + gl.Clear(gl.DEPTH_BUFFER_BIT) + r.shader.UseProgram() + r.shader.BindTextures() + + r.shader.SetUniformMat4("lightspace", lightspace) + r.shader.SetUniformMat4("view", view.View()) + r.shader.SetUniformMat4("projection", view.Perspective()) + r.shader.SetUniformVec3f("viewPos", view.EyePos) + r.shader.SetUniformVec4f("fogColor", itype.Vec4f{0.6, 0.8, 1.0, 1.0}) + r.shader.SetUniformVec3f("sun", normalSun) + + gl.Enable(gl.FRAMEBUFFER_SRGB) + world.Render() + gl.Disable(gl.FRAMEBUFFER_SRGB) +} diff --git a/internal/render/render_world.go.old b/internal/render/render_world.go.old new file mode 100755 index 0000000..61fe631 --- /dev/null +++ b/internal/render/render_world.go.old @@ -0,0 +1,119 @@ +package render + +import ( + "time" + "unsafe" + + "github.com/Edgaru089/gl01/internal/asset" + "github.com/Edgaru089/gl01/internal/util/itype" + "github.com/Edgaru089/gl01/internal/world" + "github.com/go-gl/gl/all-core/gl" + "github.com/go-gl/mathgl/mgl32" +) + +// WorldRenderer holds texture/shader resource and viewport +// information for world rendering. +type WorldRenderer struct { + shader *Shader + texture *Texture + + vao, vbo uint32 + vbolen int + vertex []world.Vertex + + // for testing! + initTime time.Time +} + +// The default WorldRenderer. +var DefaultWorldRenderer WorldRenderer + +// Init initializes the WorldRenderer. +func (r *WorldRenderer) Init(w *world.World) (err error) { + + r.shader, err = NewShader(asset.WorldShaderVert, asset.WorldShaderFrag) + if err != nil { + return err + } + + asset.InitWorldTextureAtlas() + r.texture = NewTextureRGBA(asset.WorldTextureAtlas.Image) + r.shader.SetUniformTexture("tex", r.texture) + + r.shader.SetUniformMat4("model", mgl32.Ident4()) + // and view and projection uniforms not yet set + gl.BindFragDataLocation(r.shader.Handle(), 0, gl.Str("outputColor\x00")) + + gl.GenVertexArrays(1, &r.vao) + gl.BindVertexArray(r.vao) + gl.GenBuffers(1, &r.vbo) + gl.BindBuffer(gl.ARRAY_BUFFER, r.vbo) + + gensync := make(chan []world.Vertex, len(w.Chunks)) + for _, c := range w.Chunks { + chunk := c + go func() { + arr := chunk.AppendVertex([]world.Vertex{}) + gensync <- arr + }() + } + for range w.Chunks { + r.vertex = append(r.vertex, (<-gensync)...) + } + close(gensync) + + gl.BufferData(gl.ARRAY_BUFFER, int(unsafe.Sizeof(world.Vertex{}))*len(r.vertex), gl.Ptr(r.vertex), gl.DYNAMIC_DRAW) + + vertAttrib := uint32(gl.GetAttribLocation(r.shader.Handle(), gl.Str("vert\x00"))) + gl.VertexAttribPointer(vertAttrib, 3, gl.FLOAT, false, int32(unsafe.Sizeof(world.Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(world.Vertex{}.World)))) + + normalAttrib := uint32(gl.GetAttribLocation(r.shader.Handle(), gl.Str("normal\x00"))) + gl.VertexAttribPointer(normalAttrib, 3, gl.FLOAT, false, int32(unsafe.Sizeof(world.Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(world.Vertex{}.Normal)))) + + texCoordAttrib := uint32(gl.GetAttribLocation(r.shader.Handle(), gl.Str("vertTexCoord\x00"))) + gl.VertexAttribPointer(texCoordAttrib, 2, gl.FLOAT, false, int32(unsafe.Sizeof(world.Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(world.Vertex{}.Texture)))) + + lightAttrib := uint32(gl.GetAttribLocation(r.shader.Handle(), gl.Str("light\x00"))) + gl.VertexAttribPointer(lightAttrib, 1, gl.FLOAT, false, int32(unsafe.Sizeof(world.Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(world.Vertex{}.Light)))) + + gl.EnableVertexAttribArray(vertAttrib) + gl.EnableVertexAttribArray(normalAttrib) + gl.EnableVertexAttribArray(texCoordAttrib) + gl.EnableVertexAttribArray(lightAttrib) + + w.EnableVertexArrayAttrib(vertAttrib) + w.EnableVertexArrayAttrib(normalAttrib) + w.EnableVertexArrayAttrib(texCoordAttrib) + w.EnableVertexArrayAttrib(lightAttrib) + + gl.Enable(gl.CULL_FACE) + gl.Enable(gl.DEPTH_TEST) + gl.DepthFunc(gl.LESS) + + r.initTime = time.Now() + + return nil +} + +func (r *WorldRenderer) Render(world *world.World, view *View) { + + r.shader.UseProgram() + r.shader.BindTextures() + + r.shader.SetUniformMat4("view", view.view) + r.shader.SetUniformMat4("projection", mgl32.Perspective(view.fovy, view.aspect, 0.1, 200)) + r.shader.SetUniformVec3f("sun", itype.Vec3f{-0.2, 1, 0.8}.Normalize()) + + // for testing! + //model := mgl32.HomogRotate3D(float32(time.Since(r.initTime).Seconds()), mgl32.Vec3{0, 1, 0}) + //r.shader.SetUniformMat4("model", model) + + //r.shader.SetUniformMat4("view", r.view) + + gl.BindVertexArray(r.vao) + gl.BindBuffer(gl.ARRAY_BUFFER, r.vbo) + gl.DrawArrays(gl.TRIANGLES, 0, int32(len(r.vertex))) + + world.Render() + //world.Chunk(0, 0).Render() +} diff --git a/internal/render/shader.go b/internal/render/shader.go new file mode 100644 index 0000000..e57ca87 --- /dev/null +++ b/internal/render/shader.go @@ -0,0 +1,257 @@ +package render + +import ( + "fmt" + "log" + "strings" + + "edgaru089.ml/go/gl01/internal/util/itype" + "github.com/go-gl/gl/all-core/gl" + "github.com/go-gl/mathgl/mgl32" +) + +// returns max texture unit count +func getMaxTextureUnits() int32 { + var cnt int32 + gl.GetIntegerv(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS, &cnt) + return cnt +} + +// Shader contains a shader program, with a vertex and a fragment (pixel) shader. +type Shader struct { + prog uint32 + uniforms map[string]int32 + textures map[int32]*Texture // maps uniform location to *Texture +} + +// helper construct to get uniforms and restore previous glUseProgram +func (s *Shader) uniformBlender(name string) (location int32, restore func()) { + if s.prog != 0 { + var saved int32 + // Use program object + gl.GetIntegerv(gl.CURRENT_PROGRAM, &saved) + if uint32(saved) != s.prog { + gl.UseProgram(s.prog) + } + + return s.UniformLocation(name), func() { + if uint32(saved) != s.prog { + gl.UseProgram(uint32(saved)) + } + } + } + return 0, func() {} +} + +func compileShader(src string, stype uint32) (prog uint32, err error) { + prog = gl.CreateShader(stype) + + strs, free := gl.Strs(src, "\x00") + gl.ShaderSource(prog, 1, strs, nil) + free() + gl.CompileShader(prog) + + var status int32 + gl.GetShaderiv(prog, gl.COMPILE_STATUS, &status) + if status == gl.FALSE { + var len int32 + gl.GetShaderiv(prog, gl.INFO_LOG_LENGTH, &len) + + log := strings.Repeat("\x00", int(len+1)) + gl.GetShaderInfoLog(prog, len, nil, gl.Str(log)) + + gl.DeleteShader(prog) + + switch stype { + case gl.VERTEX_SHADER: + return 0, fmt.Errorf("failed to compile Vertex Shader: %s", log) + case gl.FRAGMENT_SHADER: + return 0, fmt.Errorf("failed to compile Fragment Shader: %s", log) + default: + return 0, fmt.Errorf("failed to compile Unknown(%d) Shader: %s", stype, log) + } + } + + return +} + +// NewShader compiles and links a Vertex and a Fragment (Pixel) shader into one program. +// The source code does not need to be terminated with \x00. +func NewShader(vert, frag string) (s *Shader, err error) { + + vertid, err := compileShader(vert, gl.VERTEX_SHADER) + if err != nil { + return nil, err + } + + fragid, err := compileShader(frag, gl.FRAGMENT_SHADER) + if err != nil { + gl.DeleteShader(vertid) + return nil, err + } + + s = &Shader{} + s.uniforms = make(map[string]int32) + s.textures = make(map[int32]*Texture) + s.prog = gl.CreateProgram() + + gl.AttachShader(s.prog, vertid) + gl.AttachShader(s.prog, fragid) + gl.LinkProgram(s.prog) + + var status int32 + gl.GetProgramiv(s.prog, gl.LINK_STATUS, &status) + if status == gl.FALSE { + var len int32 + gl.GetProgramiv(s.prog, gl.INFO_LOG_LENGTH, &len) + + log := strings.Repeat("\x00", int(len+1)) + gl.GetProgramInfoLog(s.prog, len, nil, gl.Str(log)) + + gl.DeleteProgram(s.prog) + gl.DeleteShader(vertid) + gl.DeleteShader(fragid) + + return nil, fmt.Errorf("failed to link Program: %s", log) + } + + gl.DeleteShader(vertid) + gl.DeleteShader(fragid) + + return +} + +// UniformLocation returns the location id of the given uniform. +// it returns -1 if the uniform is not found. +func (s *Shader) UniformLocation(name string) int32 { + if id, ok := s.uniforms[name]; ok { + return id + } else { + + location := gl.GetUniformLocation(s.prog, gl.Str(name+"\x00")) + s.uniforms[name] = location + + if location == -1 { + log.Printf("Shader: uniform \"%s\" not found", name) + } + + return location + } +} + +// UseProgram calls glUseProgram. +func (s *Shader) UseProgram() { + gl.UseProgram(s.prog) +} + +// BindTextures calls glActiveTexture and glBindTexture, updating the texture unit slots. +func (s *Shader) BindTextures() { + var i int + for loc, tex := range s.textures { + + index := int32(i + 1) + + gl.Uniform1i(loc, index) + gl.ActiveTexture(uint32(gl.TEXTURE0 + index)) + gl.BindTexture(gl.TEXTURE_2D, tex.tex) + i++ + } + + gl.ActiveTexture(gl.TEXTURE0) +} + +// Handle returns the OpenGL handle of the program. +func (s *Shader) Handle() uint32 { + return s.prog +} + +func (s *Shader) SetUniformTexture(name string, tex *Texture) { + if s.prog == 0 { + return + } + + loc := s.UniformLocation(name) + if loc == -1 { + return + } + + // Store the location to texture map + _, ok := s.textures[loc] + if !ok { + // new texture, make sure there are enough texture units + if len(s.textures)+1 >= int(getMaxTextureUnits()) { + log.Printf("Shader: Warning: Impossible to use texture \"%s\" for shader: all available texture units are used", name) + return + } + } + + s.textures[loc] = tex +} + +// SetUniformTextureHandle sets a uniform as a sampler2D from an external OpenGL texture. +// tex is the OpenGL texture handle (the one you get with glGenTextures()) +func (s *Shader) SetUniformTextureHandle(name string, tex uint32) { + s.SetUniformTexture(name, &Texture{tex: tex}) +} + +func (s *Shader) SetUniformMat4(name string, value mgl32.Mat4) { + loc, restore := s.uniformBlender(name) + defer restore() + + gl.UniformMatrix4fv(loc, 1, false, &value[0]) +} + +func (s *Shader) SetUniformFloat(name string, value float32) { + loc, restore := s.uniformBlender(name) + defer restore() + + gl.Uniform1f(loc, value) +} +func (s *Shader) SetUniformVec2f(name string, value itype.Vec2f) { + loc, restore := s.uniformBlender(name) + defer restore() + + gl.Uniform2f(loc, value[0], value[1]) +} +func (s *Shader) SetUniformVec3f(name string, value itype.Vec3f) { + loc, restore := s.uniformBlender(name) + defer restore() + + gl.Uniform3f(loc, value[0], value[1], value[2]) +} +func (s *Shader) SetUniformVec4f(name string, value itype.Vec4f) { + loc, restore := s.uniformBlender(name) + defer restore() + + gl.Uniform4f(loc, value[0], value[1], value[2], value[3]) +} + +func (s *Shader) SetUniformInt(name string, value int32) { + loc, restore := s.uniformBlender(name) + defer restore() + + gl.Uniform1i(loc, value) +} +func (s *Shader) SetUniformVec2i(name string, value itype.Vec2i) { + loc, restore := s.uniformBlender(name) + defer restore() + + gl.Uniform2i(loc, int32(value[0]), int32(value[1])) +} +func (s *Shader) SetUniformVec3i(name string, value itype.Vec3i) { + loc, restore := s.uniformBlender(name) + defer restore() + + gl.Uniform3i(loc, int32(value[0]), int32(value[1]), int32(value[2])) +} +func (s *Shader) SetUniformVec4i(name string, value itype.Vec4i) { + loc, restore := s.uniformBlender(name) + defer restore() + + gl.Uniform4i(loc, int32(value[0]), int32(value[1]), int32(value[2]), int32(value[3])) +} + +func (s *Shader) GetAttribLocation(name string) uint32 { + name = name + "\x00" + return uint32(gl.GetAttribLocation(s.prog, gl.Str(name))) +} diff --git a/internal/render/texture.go b/internal/render/texture.go new file mode 100644 index 0000000..54915cb --- /dev/null +++ b/internal/render/texture.go @@ -0,0 +1,143 @@ +package render + +import ( + "image" + + "github.com/go-gl/gl/all-core/gl" +) + +func curTextureBinding() uint32 { + var id int32 + gl.GetIntegerv(gl.TEXTURE_BINDING_2D, &id) + return uint32(id) +} + +// Texture holds handle to OpenGL Texture on the graphics card memory. +type Texture struct { + tex uint32 + + hasMipmap bool +} + +// NewTexture creates a new, empty Texture. +func NewTexture() *Texture { + // Restore current texture binding + defer gl.BindTexture(gl.TEXTURE_2D, curTextureBinding()) + + var tex uint32 + + gl.GenTextures(1, &tex) + gl.BindTexture(gl.TEXTURE_2D, tex) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) + + return &Texture{tex: tex} +} + +// NewTextureRGBA creates a new Texture with image. +func NewTextureRGBA(image *image.RGBA) *Texture { + // Restore current texture binding + defer gl.BindTexture(gl.TEXTURE_2D, curTextureBinding()) + + var tex uint32 + + gl.GenTextures(1, &tex) + gl.BindTexture(gl.TEXTURE_2D, tex) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) + + gl.TexImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + int32(image.Rect.Size().X), + int32(image.Rect.Size().Y), + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + gl.Ptr(image.Pix), + ) + + return &Texture{tex: tex} +} + +// SetSmooth sets the min/mag filters to LINEAR(smooth) or NEAREST(not smooth) +// TODO: Not working +func (t *Texture) SetSmooth(smooth bool) { + defer gl.BindTexture(gl.TEXTURE_2D, curTextureBinding()) + gl.BindTexture(gl.TEXTURE_2D, t.tex) + if smooth { + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) + } else { + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST) + } +} + +// UpdateRGBA updates the content of the texture with image. +// It deletes existing mipmap, you need to generate it again. +func (t *Texture) UpdateRGBA(image *image.RGBA) { + + // Restore current texture binding + defer gl.BindTexture(gl.TEXTURE_2D, curTextureBinding()) + + gl.BindTexture(gl.TEXTURE_2D, t.tex) + gl.TexImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + int32(image.Rect.Size().X), + int32(image.Rect.Size().Y), + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + gl.Ptr(image.Pix), + ) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) + + t.hasMipmap = false + +} + +// GenerateMipMap generates mipmap for the texture. +func (t *Texture) GenerateMipMap() { + + // Restore current texture binding + defer gl.BindTexture(gl.TEXTURE_2D, curTextureBinding()) + + gl.BindTexture(gl.TEXTURE_2D, t.tex) + gl.GenerateMipmap(gl.TEXTURE_2D) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST_MIPMAP_LINEAR) + + t.hasMipmap = true + +} + +// InvalidateMipMap invalidates mipmap for the texture. +func (t *Texture) InvalidateMipMap() { + + // Restore current texture binding + defer gl.BindTexture(gl.TEXTURE_2D, curTextureBinding()) + + gl.BindTexture(gl.TEXTURE_2D, t.tex) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST) + + t.hasMipmap = false +} + +// Handle returns the OpenGL handle of the texture. +func (t *Texture) Handle() uint32 { + return t.tex +} + +// Free deletes the texture. +func (t *Texture) Free() { + if t.tex != 0 { + gl.DeleteTextures(1, &t.tex) + } +} diff --git a/internal/render/view.go b/internal/render/view.go new file mode 100644 index 0000000..00fde07 --- /dev/null +++ b/internal/render/view.go @@ -0,0 +1,80 @@ +package render + +import ( + "edgaru089.ml/go/gl01/internal/util/itype" + "github.com/go-gl/mathgl/mgl32" +) + +// View represents a viewport in the 3-d world. +// It is essentialy a projection and a view matrix. +type View struct { + EyePos itype.Vec3f // position of the eye + rotY, rotZ, fovY itype.Angle + aspect float32 + near, far float32 + clipSet bool +} + +// View returns the View matrix from parameters set with LookAt(). +func (r *View) View() mgl32.Mat4 { + delta := mgl32.Rotate3DY(r.rotY.Radians()).Mul3(mgl32.Rotate3DZ(r.rotZ.Radians())).Mul3x1(mgl32.Vec3{1, 0, 0}) + + return mgl32.LookAt( + r.EyePos[0], r.EyePos[1], r.EyePos[2], + r.EyePos[0]+delta[0], + r.EyePos[1]+delta[1], + r.EyePos[2]+delta[2], + 0, 1, 0, + ) +} + +// Perspective returns the Perspective matrix with parameters set with Aspect(), FovY() and Clip(). +func (r *View) Perspective() mgl32.Mat4 { + if r.clipSet { + return mgl32.Perspective(r.fovY.Radians(), r.aspect, r.near, r.far) + } else { + return mgl32.Perspective(r.fovY.Radians(), r.aspect, 0.1, 200) + } +} + +// LookAt resets the View matrix with a camera looking from Eye. +// The camera points to +RotY from X+ axis rotating around Y axis (right-handed); +// +/-RotZ form X+ axis rotating up/down around Z axis. +// ( RotZ should be in the (-90,90) degrees range ) +// +// It also returns itself so that operations can be chained. +func (r *View) LookAt(eye itype.Vec3f, rotY, rotZ itype.Angle) *View { + r.EyePos = eye + r.rotY = rotY + r.rotZ = rotZ + return r +} + +// Aspect sets the aspect (width / height) of the viewport. +// +// It also returns itself so that operations can be chained. +func (r *View) Aspect(aspectRatio float32) *View { + r.aspect = aspectRatio + return r +} + +// FovY sets the Field of View (an angle) on the Y axis. +// FovX = FovY * aspect. +// +// With FovX, call SetFovY(FovX / aspect). +// +// It also returns itself so that operations can be chained. +func (r *View) FovY(fovy itype.Angle) *View { + r.fovY = fovy + return r +} + +// Clip sets the near/far clipping distance of the Perspective. +// It defaults to 0.1 / 200. +// +// It also returns itself so that operations can be chained. +func (r *View) Clip(near, far float32) *View { + r.near, r.far = near, far + r.clipSet = true + return r +} diff --git a/internal/util/absminus.go b/internal/util/absminus.go new file mode 100644 index 0000000..1971b1c --- /dev/null +++ b/internal/util/absminus.go @@ -0,0 +1,18 @@ +package util + +// AbsMinusd returns the value, with the absolute value subtracted +// by minus, or 0 if minus is more than abs. +func AbsMinusd(val, minus float64) float64 { + if val > 0 { + val -= minus + if val < 0 { + val = 0 + } + } else { + val += minus + if val > 0 { + val = 0 + } + } + return val +} diff --git a/internal/util/atlas.go b/internal/util/atlas.go new file mode 100644 index 0000000..e70f3d4 --- /dev/null +++ b/internal/util/atlas.go @@ -0,0 +1,159 @@ +package util + +import ( + "errors" + "image" + "image/color" + "io" + "log" + "sort" + + "edgaru089.ml/go/gl01/internal/util/itype" +) + +// The largest width a texture atlas can grow to. +const AtlasMaxWidth = 256 + +// Atlas stores a sequence of textures, binding them into one big texture. +type Atlas struct { + Image *image.RGBA + ImageSize itype.Vec2i + + imgs []struct { + name string + img image.Image + } + + rects map[string]itype.Recti +} + +// Add adds a new image.Image with name. +func (a *Atlas) Add(name string, img image.Image) { + a.imgs = append(a.imgs, struct { + name string + img image.Image + }{name, img}) +} + +// AddFile adds a new image file with name. +// +// Returns error if image.Decode returns error. +func (a *Atlas) AddFile(name string, imgfile io.Reader) error { + img, _, err := image.Decode(imgfile) + if err != nil { + return errors.New("TexAtlas: decoding file: " + err.Error()) + } + + a.imgs = append(a.imgs, struct { + name string + img image.Image + }{name, img}) + + return nil +} + +// Rect returns texture rect in pixel coordinates, with origin in the +// top-left corner. +func (a *Atlas) Rect(name string) itype.Recti { + recti, ok := a.rects[name] + if !ok { + recti = a.rects["_dummy_nontransparent"] + } + return recti +} + +// RectNormalized returns texture rect in normalized coordinates, +// i.e., are between [0,1) divided by texture size on each dimension. +func (a *Atlas) RectNormalized(name string) itype.Rectf { + recti := a.Rect(name) + return itype.Rectf{ + Left: float32(recti.Left) / float32(a.ImageSize[0]), + Top: float32(recti.Top) / float32(a.ImageSize[1]), + Width: float32(recti.Width) / float32(a.ImageSize[0]), + Height: float32(recti.Height) / float32(a.ImageSize[1]), + } +} + +// HasBuilt returns if the Atlas has been built. +func (a *Atlas) HasBuilt() bool { + return a.rects != nil +} + +// BuildTexture builds the texture atlas. +// /*** It can also swaps the texture upside-down to avoid OpenGL coordinate issues. ***/ +func (a *Atlas) BuildTexture( /*upsideDown bool*/ ) { + + var offX, offY, sizeX, sizeY int + var maxHeight int + + // HACK Add a dummy solid image to prevent skipping mask rendering + // because of a transparent pixel + dummy := image.NewRGBA(image.Rect(0, 0, 2, 2)) + dummy.Set(0, 0, color.Black) + dummy.Set(0, 1, color.RGBA{255, 0, 255, 255}) + dummy.Set(1, 0, color.RGBA{255, 0, 255, 255}) + dummy.Set(1, 1, color.Black) + a.imgs = append(a.imgs, struct { + name string + img image.Image + }{"_dummy_nontransparent", dummy}) + + sort.Sort(SortFunc{ + Lenf: func() int { return len(a.imgs) }, + Swapf: func(i, j int) { a.imgs[i], a.imgs[j] = a.imgs[j], a.imgs[i] }, + Lessf: func(i, j int) bool { + if a.imgs[i].name == "_dummy_nontransparent" { + return true + } else if a.imgs[j].name == "_dummy_nontransparent" { + return false + } else if a.imgs[i].img.Bounds().Dy() < a.imgs[j].img.Bounds().Dy() { + return true + } else if a.imgs[i].img.Bounds().Dy() == a.imgs[j].img.Bounds().Dy() && + a.imgs[i].img.Bounds().Dx() < a.imgs[j].img.Bounds().Dx() { + return true + } + return false + }, + }) + + a.rects = make(map[string]itype.Recti) + + for _, i := range a.imgs { + + if offX+i.img.Bounds().Dx() > AtlasMaxWidth { + // New line + offX = 0 + offY += maxHeight + maxHeight = 0 + } + + a.rects[i.name] = itype.Recti{offX, offY, i.img.Bounds().Dx(), i.img.Bounds().Dy()} + + log.Printf("Atlas: texture %s at pos %v\n", i.name, a.rects[i.name]) + + sizeX = Maxi(sizeX, offX+i.img.Bounds().Dx()) + sizeY = Maxi(sizeY, offY+i.img.Bounds().Dy()) + maxHeight = Maxi(maxHeight, i.img.Bounds().Dy()) + + offX += i.img.Bounds().Dx() + } + + a.Image = image.NewRGBA(image.Rect(0, 0, sizeX, sizeY)) + a.ImageSize = itype.Vec2i{sizeX, sizeY} + + for _, i := range a.imgs { + rect := a.rects[i.name] + for x := 0; x < rect.Width; x++ { + for y := 0; y < rect.Height; y++ { + a.Image.Set( + x+rect.Left, + y+rect.Top, /*i.img.Bounds().Dy()-1-(y+rect.Top),*/ + i.img.At( + i.img.Bounds().Min.X+x, + i.img.Bounds().Min.Y+y, + ), + ) + } + } + } +} diff --git a/internal/util/bunnyhop.go b/internal/util/bunnyhop.go new file mode 100644 index 0000000..8e6ab67 --- /dev/null +++ b/internal/util/bunnyhop.go @@ -0,0 +1,15 @@ +package util + +import "edgaru089.ml/go/gl01/internal/util/itype" + +func BunnyhopAccelerate(accelDir, prevVel itype.Vec3d, accelVel, maxVel float64) (resultVel itype.Vec3d) { + if accelVel < 1e-4 { + return prevVel + } + projVel := prevVel.Dot(accelDir) + if projVel+accelVel > maxVel { + accelVel = maxVel - projVel + } + + return prevVel.Add(accelDir.Normalize().Multiply(accelVel)) +} diff --git a/internal/util/itype/angle.go b/internal/util/itype/angle.go new file mode 100644 index 0000000..ba48e03 --- /dev/null +++ b/internal/util/itype/angle.go @@ -0,0 +1,35 @@ +package itype + +import "math" + +// Angle wraps a angle in the form of radian float32. +// +// Its value lies strictly in [0, 2Pi). +type Angle float32 + +func normalizeRadians(rad float64) float64 { + if rad < 0 { + return 2*math.Pi - math.Mod(-rad, 2*math.Pi) + } + return math.Mod(rad, 2*math.Pi) +} + +// Radians construct an Angle from a radian value. +func Radians(rad float32) Angle { + return Angle(normalizeRadians(float64(rad))) +} + +// Degrees construct an Angle from a degree value. +func Degrees(deg float32) Angle { + return Radians(deg * math.Pi / 180) +} + +// Radians return the value of the Angle in radians. +func (a Angle) Radians() float32 { + return float32(normalizeRadians(float64(a))) +} + +// Degrees return the value of the Angle in degrees. +func (a Angle) Degrees() float32 { + return float32(normalizeRadians(float64(a)) * 180 / math.Pi) +} diff --git a/internal/util/itype/dataset.go b/internal/util/itype/dataset.go new file mode 100644 index 0000000..29b3de8 --- /dev/null +++ b/internal/util/itype/dataset.go @@ -0,0 +1,11 @@ +package itype + +// Dataset describes arbitrary data stored in String-Interface{} pairs. +// This is the major way how complicated data is stored in blocks and entities. +// +// The empty interface should only hold these 4 types: +// - int +// - float64 +// - bool +// - string +type Dataset map[string]interface{} diff --git a/internal/util/itype/direction.go b/internal/util/itype/direction.go new file mode 100644 index 0000000..6cdd436 --- /dev/null +++ b/internal/util/itype/direction.go @@ -0,0 +1,37 @@ +package itype + +type Direction int + +const ( + XPlus Direction = iota // X+ + XMinus // X- + YPlus // Y+ + YMinus // Y- + ZPlus // Z+ + ZMinus // Z- +) + +var DirectionVeci = [6]Vec3i{ + XPlus: {1, 0, 0}, + XMinus: {-1, 0, 0}, + YPlus: {0, 1, 0}, + YMinus: {0, -1, 0}, + ZPlus: {0, 0, 1}, + ZMinus: {0, 0, -1}, +} +var DirectionVecf = [6]Vec3f{ + XPlus: {1, 0, 0}, + XMinus: {-1, 0, 0}, + YPlus: {0, 1, 0}, + YMinus: {0, -1, 0}, + ZPlus: {0, 0, 1}, + ZMinus: {0, 0, -1}, +} +var DirectionVecd = [6]Vec3d{ + XPlus: {1, 0, 0}, + XMinus: {-1, 0, 0}, + YPlus: {0, 1, 0}, + YMinus: {0, -1, 0}, + ZPlus: {0, 0, 1}, + ZMinus: {0, 0, -1}, +} diff --git a/internal/util/itype/rect.go b/internal/util/itype/rect.go new file mode 100644 index 0000000..ec28189 --- /dev/null +++ b/internal/util/itype/rect.go @@ -0,0 +1,133 @@ +package itype + +import "sort" + +// Recti is a 2D rectangle with int coordinates. +type Recti struct { + Left, Top int + Width, Height int +} + +// Rectf is a 2D rectangle with float32 coordinates. +type Rectf struct { + Left, Top float32 + Width, Height float32 +} + +// Rectd is a 2D rectangle with float64 coordinates. +type Rectd struct { + Left, Top float64 + Width, Height float64 +} + +// Boxi is a 3D box with int coordinates. +type Boxi struct { + OffX, OffY, OffZ int + SizeX, SizeY, SizeZ int +} + +// Boxf is a 3D box with float32 coordinates. +type Boxf struct { + OffX, OffY, OffZ float32 + SizeX, SizeY, SizeZ float32 +} + +func (b Boxf) Offset(offset Vec3f) Boxf { + return Boxf{ + OffX: b.OffX + offset[0], + OffY: b.OffY + offset[1], + OffZ: b.OffZ + offset[2], + SizeX: b.SizeX, + SizeY: b.SizeY, + SizeZ: b.SizeZ, + } +} + +func (b Boxf) MinPoint() Vec3f { + return Vec3f{b.OffX, b.OffY, b.OffZ} +} +func (b Boxf) MaxPoint() Vec3f { + return Vec3f{b.OffX + b.SizeX, b.OffY + b.SizeY, b.OffZ + b.SizeZ} +} + +// Boxd is a 3D box with float64 coordinates. +type Boxd struct { + OffX, OffY, OffZ float64 + SizeX, SizeY, SizeZ float64 +} + +func (b Boxd) ToFloat32() Boxf { + return Boxf{ + OffX: float32(b.OffX), + OffY: float32(b.OffY), + OffZ: float32(b.OffZ), + SizeX: float32(b.SizeX), + SizeY: float32(b.SizeY), + SizeZ: float32(b.SizeZ), + } +} + +func (b Boxd) Offset(offset Vec3d) Boxd { + return Boxd{ + OffX: b.OffX + offset[0], + OffY: b.OffY + offset[1], + OffZ: b.OffZ + offset[2], + SizeX: b.SizeX, + SizeY: b.SizeY, + SizeZ: b.SizeZ, + } +} + +func (b Boxd) Offsetv(x, y, z float64) Boxd { + return Boxd{ + OffX: b.OffX + x, + OffY: b.OffY + y, + OffZ: b.OffZ + z, + SizeX: b.SizeX, + SizeY: b.SizeY, + SizeZ: b.SizeZ, + } +} + +func (b Boxd) MinPoint() Vec3d { + return Vec3d{b.OffX, b.OffY, b.OffZ} +} +func (b Boxd) MaxPoint() Vec3d { + return Vec3d{b.OffX + b.SizeX, b.OffY + b.SizeY, b.OffZ + b.SizeZ} +} + +func (b Boxd) Contains(point Vec3d) bool { + return point[0] >= b.OffX && point[0] <= b.OffX+b.SizeX && + point[1] >= b.OffY && point[1] <= b.OffY+b.SizeY && + point[2] >= b.OffZ && point[2] <= b.OffZ+b.SizeZ +} + +func pointIntersect(n, m, p, q float64) (min, len float64) { + if m < p || q < n { // no intersection + return 0, 0 + } + + arr := []float64{n, m, p, q} + sort.Float64s(arr) + + return arr[1], arr[2] - arr[1] +} + +func (box1 Boxd) Intersects(box2 Boxd) (result Boxd) { + a, b := pointIntersect(box1.OffX, box1.OffX+box1.SizeX, box2.OffX, box2.OffX+box2.SizeX) + c, d := pointIntersect(box1.OffY, box1.OffY+box1.SizeY, box2.OffY, box2.OffY+box2.SizeY) + e, f := pointIntersect(box1.OffZ, box1.OffZ+box1.SizeZ, box2.OffZ, box2.OffZ+box2.SizeZ) + + if b == 0 || d == 0 || f == 0 { + return Boxd{} + } else { + return Boxd{ + OffX: a, + SizeX: b, + OffY: c, + SizeY: d, + OffZ: e, + SizeZ: f, + } + } +} diff --git a/internal/util/itype/vec.go b/internal/util/itype/vec.go new file mode 100644 index 0000000..fd2c6a6 --- /dev/null +++ b/internal/util/itype/vec.go @@ -0,0 +1,147 @@ +package itype + +import ( + "math" +) + +// Vec2i is a two-element int vector +type Vec2i [2]int + +func Vec2iToFloat32(v Vec2i) Vec2f { return Vec2f{float32(v[0]), float32(v[1])} } + +func (v Vec2i) Add(add Vec2i) Vec2i { return Vec2i{v[0] + add[0], v[1] + add[1]} } +func (v Vec2i) MultiplyInt(mult int) Vec2i { return Vec2i{v[0] * mult, v[1] * mult} } + +// Vec3i is a three-element int vector +type Vec3i [3]int + +func (v Vec3i) ToFloat32() Vec3f { return Vec3f{float32(v[0]), float32(v[1]), float32(v[2])} } +func (v Vec3i) ToFloat64() Vec3d { return Vec3d{float64(v[0]), float64(v[1]), float64(v[2])} } + +func (v Vec3i) Add(add Vec3i) Vec3i { return Vec3i{v[0] + add[0], v[1] + add[1], v[2] + add[2]} } +func (v Vec3i) Addv(x, y, z int) Vec3i { return Vec3i{v[0] + x, v[1] + y, v[2] + z} } +func (v Vec3i) MultiplyInt(mult int) Vec3i { return Vec3i{v[0] * mult, v[1] * mult, v[2] * mult} } + +// Vec4i is a four-element int vector +type Vec4i [4]int + +func Vec4iToFloat32(v Vec4i) Vec4f { + return Vec4f{float32(v[0]), float32(v[1]), float32(v[2]), float32(v[3])} +} + +func (v Vec4i) Add(add Vec4i) Vec4i { + return Vec4i{ + v[0] + add[0], + v[1] + add[1], + v[2] + add[2], + v[3] + add[3], + } +} +func (v Vec4i) MultiplyInt(mult int) Vec4i { + return Vec4i{ + v[0] * mult, + v[1] * mult, + v[2] * mult, + v[3] * mult, + } +} + +// Vec2f is a two-element float vector +type Vec2f [2]float32 + +// Vec3f is a three-element float vector +type Vec3f [3]float32 + +func (v Vec3f) Add(add Vec3f) Vec3f { + return Vec3f{v[0] + add[0], v[1] + add[1], v[2] + add[2]} +} + +func (v Vec3f) Addv(x, y, z float32) Vec3f { + return Vec3f{v[0] + x, v[1] + y, v[2] + z} +} + +func (v Vec3f) Multiply(mult float32) Vec3f { + return Vec3f{v[0] * mult, v[1] * mult, v[2] * mult} +} + +func (v Vec3f) Floor() Vec3i { + return Vec3i{ + int(math.Floor(float64(v[0]))), + int(math.Floor(float64(v[1]))), + int(math.Floor(float64(v[2]))), + } +} +func (v Vec3f) Length() float32 { + v0, v1, v2 := float64(v[0]), float64(v[1]), float64(v[2]) + return float32(math.Sqrt(v0*v0 + v1*v1 + v2*v2)) +} + +func (v Vec3f) Normalize() Vec3f { + l := v.Length() + return Vec3f{v[0] / l, v[1] / l, v[2] / l} +} + +func (v Vec3f) ToFloat64() Vec3d { + return Vec3d{float64(v[0]), float64(v[1]), float64(v[2])} +} + +// Vec4f is a four-element float vector +type Vec4f [4]float32 + +// Vec2d is a two-element float64 vector +type Vec2d [2]float64 + +// Vec3d is a three-element float64 vector +type Vec3d [3]float64 + +func (v Vec3d) Negative() Vec3d { + return Vec3d{-v[0], -v[1], -v[2]} +} + +func (v Vec3d) Add(add Vec3d) Vec3d { + return Vec3d{v[0] + add[0], v[1] + add[1], v[2] + add[2]} +} + +func (v Vec3d) Addf(add Vec3f) Vec3d { + return Vec3d{v[0] + float64(add[0]), v[1] + float64(add[1]), v[2] + float64(add[2])} +} + +func (v Vec3d) Addv(x, y, z float64) Vec3d { + return Vec3d{v[0] + x, v[1] + y, v[2] + z} +} + +func (v Vec3d) Multiply(mult float64) Vec3d { + return Vec3d{v[0] * mult, v[1] * mult, v[2] * mult} +} + +func (v1 Vec3d) Cross(v2 Vec3d) Vec3d { + return Vec3d{v1[1]*v2[2] - v1[2]*v2[1], v1[2]*v2[0] - v1[0]*v2[2], v1[0]*v2[1] - v1[1]*v2[0]} +} + +func (v1 Vec3d) Dot(v2 Vec3d) float64 { + return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2] +} + +func (v Vec3d) Length() float64 { + return math.Sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]) +} + +func (v Vec3d) Normalize() Vec3d { + l := v.Length() + return Vec3d{v[0] / l, v[1] / l, v[2] / l} +} + +func (v Vec3d) Floor() Vec3i { + return Vec3i{ + int(math.Floor(v[0])), + int(math.Floor(v[1])), + int(math.Floor(v[2])), + } +} + +func (v Vec3d) ToFloat32() Vec3f { + return Vec3f{float32(v[0]), float32(v[1]), float32(v[2])} +} + +// Vec4d is a four-element float64 vector +type Vec4d [4]float64 diff --git a/internal/util/minmax.go b/internal/util/minmax.go new file mode 100644 index 0000000..05edbbc --- /dev/null +++ b/internal/util/minmax.go @@ -0,0 +1,55 @@ +package util + +import "math" + +func Maxi(a, b int) int { + if a > b { + return a + } + return b +} +func Mini(a, b int) int { + if a < b { + return a + } + return b +} + +func Maxf(a, b float32) float32 { + if a > b { + return a + } + return b +} +func Minf(a, b float32) float32 { + if a < b { + return a + } + return b +} + +func Maxd(a, b float64) float64 { + if a > b { + return a + } + return b +} +func Mind(a, b float64) float64 { + if a < b { + return a + } + return b +} + +func AbsMaxd(a, b float64) float64 { + if math.Abs(a) > math.Abs(b) { + return a + } + return b +} +func AbsMind(a, b float64) float64 { + if math.Abs(a) < math.Abs(b) { + return a + } + return b +} diff --git a/internal/util/mod.go b/internal/util/mod.go new file mode 100644 index 0000000..09fbaa9 --- /dev/null +++ b/internal/util/mod.go @@ -0,0 +1,20 @@ +package util + +func FloatModf(val, mod float32) float32 { + for val > mod { + val -= mod + } + for val < 0 { + val += mod + } + return val +} +func FloatModd(val, mod float64) float64 { + for val > mod { + val -= mod + } + for val < 0 { + val += mod + } + return val +} diff --git a/internal/util/sort.go b/internal/util/sort.go new file mode 100644 index 0000000..a1599a0 --- /dev/null +++ b/internal/util/sort.go @@ -0,0 +1,23 @@ +package util + +import "sort" + +type SortFunc struct { + Lenf func() int + Lessf func(i, j int) bool + Swapf func(i, j int) +} + +var _ sort.Interface = SortFunc{} + +func (s SortFunc) Len() int { + return s.Lenf() +} + +func (s SortFunc) Less(i, j int) bool { + return s.Lessf(i, j) +} + +func (s SortFunc) Swap(i, j int) { + s.Swapf(i, j) +} diff --git a/internal/world/block.go b/internal/world/block.go new file mode 100644 index 0000000..1d52c69 --- /dev/null +++ b/internal/world/block.go @@ -0,0 +1,178 @@ +package world + +import ( + "fmt" + + "edgaru089.ml/go/gl01/internal/util/itype" +) + +// BlockRenderType is an enum describing the rendering process of a block +type BlockRenderType int + +const ( + OneTexture BlockRenderType = iota // Render with one texture of the same on all faces, "Name.png" + ThreeTexture // Render with one texture on the top, one around the sides, and one on the bottom, "Name_top/side/bot.png," + SixTexture // Render with six different textures on six faces, "Name_x+/x-/y+/y-/z+/z-.png" + CustomRendering // Rendering calls BlockAppearance.CustomRenderAppend() +) + +// BlockAppearance describes basic appearance of a kind of block. +type BlockAppearance struct { + Name string // A short name, like "stone" or "dirt", used for texture lookups + Transparent bool // Is block transparent? + NotSolid bool // Is block not solid, i.e., has no solid hitbox? (this makes the zero value reasonable) + Light int // The light level it emits, 0 is none + + Hitbox itype.Boxd // Hitbox, in block-local coordinates; empty slice means a default hitbox of 1x1x1 + + RenderType BlockRenderType // Rendering type, defaults to OneTexture (zero value) + + // Called on render if RenderType == CustomRendering. + // + // Be sure to return vertexArray at the end of the function (in case it got reallocated)!!!! + CustomRenderAppend func( + position itype.Vec3i, + aux int, + data itype.Dataset, + vertexArray []Vertex, + ) []Vertex +} + +// BlockBehaviour describes a kind of block of the same Major ID. +type BlockBehaviour interface { + + // Static returns if the Behaviour is "static", i.e., the Appearance does not + // change with position, Minor ID or Dataset. Static Behaviours are cached + // by the renderer and only generated once. + // + // Static implies RequireDataset = false and RequireBlockUpdate = false. + Static() bool + + // RequireDataset returns if the type of block requires a Dataset attached. + RequireDataset() bool + + // RequireBlockUpdate return if BlockUpdate should be called if a neighboring + // block has changed. Blocks not requiring BlockUpdate does not change at all. + RequireBlockUpdate() bool + + // Appearance returns the Appearance of the block at global position Position, + // with Minor ID aux, and Dataset data. + // + // If RequireDataset if false, data is nil. + Appearance(position itype.Vec3i, aux int, data itype.Dataset) BlockAppearance + + // BlockUpdate is called when RequireBlockUpdate is true and the block at + // global position Position, with Minor ID aux, and Dataset data has a neighbor + // that changed state. A block will only be updated once in a tick. + // + // If RequireDataset if false, data is nil. + // + // Return true if this block also changed state, false otherwise. + BlockUpdate(position itype.Vec3i, aux int, data itype.Dataset) bool +} + +type blockBehaviourStatic struct { + app BlockAppearance +} + +func (blockBehaviourStatic) Static() bool { return true } +func (blockBehaviourStatic) RequireDataset() bool { return false } +func (blockBehaviourStatic) RequireBlockUpdate() bool { return false } +func (b blockBehaviourStatic) Appearance(position itype.Vec3i, aux int, data itype.Dataset) BlockAppearance { + return b.app +} +func (blockBehaviourStatic) BlockUpdate(position itype.Vec3i, aux int, data itype.Dataset) bool { + return false +} + +// BlockBehaviourStatic returns a Static BlockBehaviour that has the given BlockAppearance. +func BlockBehaviourStatic(app BlockAppearance) BlockBehaviour { + return blockBehaviourStatic{app: app} +} + +var behaviour map[int]BlockBehaviour = make(map[int]BlockBehaviour) +var appearance map[int]BlockAppearance = make(map[int]BlockAppearance) +var behaviourDoneRegister bool + +// RegisterBlockBehaviour registers behaviour with the given id. +// +// If the id is already taken, or id == 0, false is returned and nothing is done. +// Otherwise, true is returned and the block is registered. +func RegisterBlockBehaviour(id int, b BlockBehaviour) bool { + if _, ok := behaviour[id]; behaviourDoneRegister || id == 0 || ok { + return false + } + + behaviour[id] = b + return true +} + +// DoneRegisteringBlockBehaviour is to be called after Registering BlockBehaviour, +// i.e., in Post-Init() initializations. +func DoneRegisteringBlockBehaviour() { + for id, b := range behaviour { + if b.Static() { + appearance[id] = b.Appearance(itype.Vec3i{}, 0, nil) + } + } + + behaviourDoneRegister = true +} + +// GetBlockAppearance gets the block appearance of the given block in the fastest way possible. +func GetBlockAppearance(position itype.Vec3i, id, aux int, data itype.Dataset) BlockAppearance { + if app, ok := appearance[id]; ok { // Cache + if app.Hitbox == (itype.Boxd{}) { + app.Hitbox = itype.Boxd{ + OffX: 0, OffY: 0, OffZ: 0, + SizeX: 1, SizeY: 1, SizeZ: 1, + } + } + return app + } + + // Slow way + b, ok := behaviour[id] + if !ok { + panic(fmt.Sprint("invalid block type ", id)) + } + + app := b.Appearance(position, aux, data) + if app.Hitbox == (itype.Boxd{}) { + app.Hitbox = itype.Boxd{ + OffX: 0, OffY: 0, OffZ: 0, + SizeX: 1, SizeY: 1, SizeZ: 1, + } + } + return app +} + +// GetBlockBehaviour gets the block behaviour of the given id, or nil if not present. +func GetBlockBehaviour(id int) BlockBehaviour { + return behaviour[id] +} + +// Block is a structure to store and pass Blocks around. +type Block struct { + Id, Aux int + Dataset itype.Dataset + Behaviour BlockBehaviour +} + +// Appearance is a shortcut for Behaviour.Appearance(). +// It returns the Appearance of the block with the given parameters. +func (b *Block) Appearance(position itype.Vec3i) BlockAppearance { + return b.Behaviour.Appearance(position, b.Aux, b.Dataset) +} + +// BlockUpdate is a shortcut for Behaviour.BlockUpdate(). +// It is called when RequireBlockUpdate is true and the block at +// global position Position, with Minor ID aux, and Dataset data has a neighbor +// that changed state. A block will only be updated once in a tick. +// +// If RequireDataset if false, data is nil. +// +// Return true if this block also changed state, false otherwise. +func (b *Block) BlockUpdate(position itype.Vec3i) bool { + return b.Behaviour.BlockUpdate(position, b.Aux, b.Dataset) +} diff --git a/internal/world/block.go.new b/internal/world/block.go.new new file mode 100644 index 0000000..1144c96 --- /dev/null +++ b/internal/world/block.go.new @@ -0,0 +1,160 @@ +package world + +import ( + "fmt" + + "github.com/Edgaru089/gl01/internal/util/itype" +) + +// BlockRenderType is an enum describing the rendering process of a block +type BlockRenderType int + +const ( + OneTexture BlockRenderType = iota // Render with one texture of the same on all faces, "Name.png" + ThreeTexture // Render with one texture on the top, one around the sides, and one on the bottom, "Name_top/side/bot.png," + SixTexture // Render with six different textures on six faces, "Name_x+/x-/y+/y-/z+/z-.png" + CustomRendering // Rendering calls BlockAppearance.CustomRenderAppend() +) + +// BlockAppearance describes basic appearance of a kind of block. +type BlockAppearance struct { + Name string // A short name, like "stone" or "dirt", used for texture lookups + Transparent bool // Is block transparent? + NotSolid bool // Is block not solid, i.e., has no solid hitbox? (this makes the zero value reasonable) + Light int // The light level it emits, 0 is none + + Hitbox []itype.Boxd // Hitbox, in block-local coordinates; empty slice means a default hitbox of 1x1x1 + + RenderType BlockRenderType // Rendering type, defaults to OneTexture (zero value) + + // Called on render if RenderType == CustomRendering. + // + // Be sure to return vertexArray at the end of the function (in case it got reallocated)!!!! + CustomRenderAppend func( + position itype.Vec3i, + aux int, + data itype.Dataset, + vertexArray []Vertex, + ) []Vertex +} + +// BlockBehaviour describes a kind of block of the same Major ID. +type BlockBehaviour interface { + + // Static returns if the Behaviour is "static", i.e., the Appearance does not + // change with position, Minor ID or Dataset. Static Behaviours are cached + // by the renderer and only generated once. + // + // Static implies RequireDataset = false and RequireBlockUpdate = false. + Static() bool + + // RequireDataset returns if the type of block requires a Dataset attached. + RequireDataset() bool + + // RequireBlockUpdate return if BlockUpdate should be called if a neighboring + // block has changed. Blocks not requiring BlockUpdate does not change at all. + RequireBlockUpdate() bool + + // Appearance returns the Appearance of the block at global position Position, + // with Minor ID aux, and Dataset data. + // + // If RequireDataset if false, data is nil. + Appearance(position itype.Vec3i, aux int, data itype.Dataset) BlockAppearance + + // BlockUpdate is called when RequireBlockUpdate is true and the block at + // global position Position, with Minor ID aux, and Dataset data has a neighbor + // that changed state. A block will only be updated once in a tick. + // + // If RequireDataset if false, data is nil. + // + // Return true if this block also changed state, false otherwise. + BlockUpdate(position itype.Vec3i, aux int, data itype.Dataset) bool +} + +type blockBehaviourStatic struct { + app BlockAppearance +} + +func (blockBehaviourStatic) Static() bool { return true } +func (blockBehaviourStatic) RequireDataset() bool { return false } +func (blockBehaviourStatic) RequireBlockUpdate() bool { return false } +func (b blockBehaviourStatic) Appearance(position itype.Vec3i, aux int, data itype.Dataset) BlockAppearance { + return b.app +} +func (blockBehaviourStatic) BlockUpdate(position itype.Vec3i, aux int, data itype.Dataset) bool { + return false +} + +// BlockBehaviourStatic returns a Static BlockBehaviour that has the given BlockAppearance. +func BlockBehaviourStatic(app BlockAppearance) BlockBehaviour { + return blockBehaviourStatic{app: app} +} + +var behaviour map[int]BlockBehaviour = make(map[int]BlockBehaviour) +var appearance map[int]BlockAppearance = make(map[int]BlockAppearance) +var behaviourDoneRegister bool + +// RegisterBlockBehaviour registers behaviour with the given id. +// +// If the id is already taken, or id == 0, false is returned and nothing is done. +// Otherwise, true is returned and the block is registered. +func RegisterBlockBehaviour(id int, b BlockBehaviour) bool { + if _, ok := behaviour[id]; behaviourDoneRegister || id == 0 || ok { + return false + } + + behaviour[id] = b + return true +} + +// DoneRegisteringBlockBehaviour is to be called after Registering BlockBehaviour, +// i.e., in Post-Init() initializations. +func DoneRegisteringBlockBehaviour() { + for id, b := range behaviour { + if b.Static() { + appearance[id] = b.Appearance(itype.Vec3i{}, 0, nil) + } + } + + behaviourDoneRegister = true +} + +// GetBlockAppearance gets the block appearance of the given block in the fastest way possible. +func GetBlockAppearance(position itype.Vec3i, id, aux int, data itype.Dataset) BlockAppearance { + if app, ok := appearance[id]; ok { // Cache + if len(app.Hitbox) == 0 { + app.Hitbox = []itype.Boxd{{ + OffX: 0, OffY: 0, OffZ: 0, + SizeX: 1, SizeY: 1, SizeZ: 1, + }} + } + return app + } + + // Slow way + b, ok := behaviour[id] + if !ok { + panic(fmt.Sprint("invalid block type ", id)) + } + + app := b.Appearance(position, aux, data) + if len(app.Hitbox) == 0 { + app.Hitbox = []itype.Boxd{{ + OffX: 0, OffY: 0, OffZ: 0, + SizeX: 1, SizeY: 1, SizeZ: 1, + }} + } + return app +} + +// GetBlockBehaviour gets the block behaviour of the given id, or nil if not present. +func GetBlockBehaviour(id int) BlockBehaviour { + return behaviour[id] +} + +// Block is a structure to store and pass Blocks around. +type Block struct { + Id, Aux int + Dataset itype.Dataset + Behaviour BlockBehaviour +} diff --git a/internal/world/chunk.go b/internal/world/chunk.go new file mode 100644 index 0000000..97c2522 --- /dev/null +++ b/internal/world/chunk.go @@ -0,0 +1,72 @@ +package world + +import ( + "encoding/gob" + "io" + "log" +) + +const ( + ChunkSizeX = 16 + ChunkSizeY = 128 + ChunkSizeZ = 16 +) + +type Chunk struct { + X, Z int // Chunk coordinate in global coordinate (y is always zero) + + Id, Aux [ChunkSizeX][ChunkSizeY][ChunkSizeZ]uint16 + + // render data kept unexported (therefore excluded from gob encoding) + renderChanged bool + vao, vbo uint32 + vbolen int + vertUpdate chan []Vertex +} + +func (c *Chunk) SetChunkID(x, z int) { + c.X = x + c.Z = z + c.renderChanged = true +} + +func (c *Chunk) SetBlock(x, y, z int, id int) { + c.Id[x][y][z] = uint16(id) + c.renderChanged = true +} + +// InvalidateRender should be called if a block inside the chunk has changed +// and the chunk needs to re-rendered. +func (c *Chunk) InvalidateRender() { + c.renderChanged = true +} + +// LoadFromGob loads the chunk from a gob-encoded file. +func (c *Chunk) LoadFromGob(file io.Reader) { + d := gob.NewDecoder(file) + err := d.Decode(c) + if err != nil { + log.Print("LoadFromGob Error: ", err) + } +} + +// LoadFromGobIndexed loads the chunk from a gob-encoded file, with the +// chunk Index overwritten. +func (c *Chunk) LoadFromGobIndexed(file io.Reader, x, z int) { + d := gob.NewDecoder(file) + err := d.Decode(c) + if err != nil { + log.Printf("LoadFromGobIndexed(x=%d, z=%d) Error: %s", x, z, err) + } + c.X = x + c.Z = z +} + +// WriteToGob writes the chunk to a gob-encoded file. +func (c *Chunk) WriteToGob(file io.Writer) { + e := gob.NewEncoder(file) + err := e.Encode(c) + if err != nil { + log.Printf("WriteToGob(x=%d, z=%d) Error: %s", c.X, c.Z, err) + } +} diff --git a/internal/world/default_blocks.go b/internal/world/default_blocks.go new file mode 100644 index 0000000..6d2a10c --- /dev/null +++ b/internal/world/default_blocks.go @@ -0,0 +1,40 @@ +package world + +const ( + BlockNil = iota + + BlockDebug + BlockDebugDir + BlockDebugNonexist + + BlockStone + BlockDirt + BlockGrass + BlockBedrock + BlockSand + BlockLogOak + BlockLeavesOak + + BlockCount +) + +func init() { + + RegisterBlockBehaviour(1, BlockBehaviourStatic(BlockAppearance{Name: "debug"})) + RegisterBlockBehaviour(2, BlockBehaviourStatic(BlockAppearance{Name: "debug_dir", RenderType: SixTexture})) + RegisterBlockBehaviour(3, BlockBehaviourStatic(BlockAppearance{Name: "debug_nonexist"})) + + RegisterBlockBehaviour(4, BlockBehaviourStatic(BlockAppearance{Name: "stone"})) + RegisterBlockBehaviour(5, BlockBehaviourStatic(BlockAppearance{Name: "dirt"})) + RegisterBlockBehaviour(6, BlockBehaviourStatic(BlockAppearance{Name: "grass", RenderType: ThreeTexture})) + RegisterBlockBehaviour(7, BlockBehaviourStatic(BlockAppearance{Name: "bedrock"})) + RegisterBlockBehaviour(8, BlockBehaviourStatic(BlockAppearance{Name: "sand"})) + RegisterBlockBehaviour(9, BlockBehaviourStatic(BlockAppearance{Name: "log_oak", RenderType: ThreeTexture})) + RegisterBlockBehaviour(10, BlockBehaviourStatic(BlockAppearance{Name: "leaves_oak"})) + + if BlockCount != 11 { + panic("world.DefaultBlocks: block count not correct (check for block numbering in default_blocks.go)") + } + + DoneRegisteringBlockBehaviour() +} diff --git a/internal/world/render.go b/internal/world/render.go new file mode 100644 index 0000000..c1f6c9d --- /dev/null +++ b/internal/world/render.go @@ -0,0 +1,215 @@ +package world + +import ( + "log" + "time" + "unsafe" + + "edgaru089.ml/go/gl01/internal/asset" + "edgaru089.ml/go/gl01/internal/util/itype" + "github.com/go-gl/gl/all-core/gl" +) + +// Vertex represents a rendering vertex in the global coordinate system. +type Vertex struct { + World itype.Vec3f // World vertex coordinate + Normal itype.Vec3f // Surface normal vector (normalized) + Texture itype.Vec2f // World Texture Atlas vertex coordinate + Light float32 // Light level of the block (0 ~ 15=blocklight, 16=sunlight) +} + +func (c *Chunk) InitRender() { + gl.GenVertexArrays(1, &c.vao) + gl.BindVertexArray(c.vao) + gl.GenBuffers(1, &c.vbo) + gl.BindBuffer(gl.ARRAY_BUFFER, c.vbo) + + gl.VertexAttribPointer(0, 3, gl.FLOAT, false, int32(unsafe.Sizeof(Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(Vertex{}.World)))) + gl.VertexAttribPointer(1, 3, gl.FLOAT, false, int32(unsafe.Sizeof(Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(Vertex{}.Normal)))) + gl.VertexAttribPointer(2, 2, gl.FLOAT, false, int32(unsafe.Sizeof(Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(Vertex{}.Texture)))) + gl.VertexAttribPointer(3, 1, gl.FLOAT, false, int32(unsafe.Sizeof(Vertex{})), gl.PtrOffset(int(unsafe.Offsetof(Vertex{}.Light)))) + gl.EnableVertexAttribArray(0) + gl.EnableVertexAttribArray(1) + gl.EnableVertexAttribArray(2) + gl.EnableVertexAttribArray(3) + + c.vertUpdate = make(chan []Vertex) + + c.renderChanged = true +} + +func (c *Chunk) FreeRender() { + gl.DeleteVertexArrays(1, &c.vao) + gl.DeleteBuffers(1, &c.vbo) + close(c.vertUpdate) +} + +// Bind calls glBindVertexArray(vao) and glBindBuffer(vbo). +func (c *Chunk) Bind() { + gl.BindVertexArray(c.vao) + gl.BindBuffer(gl.ARRAY_BUFFER, c.vbo) +} + +// a global vertex array for VBO updates +//var vertex []Vertex + +// updateRender should be called with gl.BindBuffer(c.vbo). +func (c *Chunk) updateRender() { + if c.renderChanged { + go func() { + t := time.Now() + vert := c.AppendVertex([]Vertex{}) + log.Printf("Chunk [%d,%d]: UpdateRender: vertex len of %d*%d = %d in %dms", c.X, c.Z, unsafe.Sizeof(Vertex{}), len(vert), int(unsafe.Sizeof(Vertex{}))*len(vert), time.Since(t).Milliseconds()) + c.vertUpdate <- vert + }() + c.renderChanged = false + } + + select { + case vert := <-c.vertUpdate: + log.Printf("Chunk [%d,%d]: received len=%d", c.X, c.Z, len(vert)) + gl.BufferData(gl.ARRAY_BUFFER, int(unsafe.Sizeof(Vertex{}))*len(vert), gl.Ptr(vert), gl.DYNAMIC_DRAW) + c.vbolen = len(vert) + default: // do nothing + } +} + +func (c *Chunk) Render() { + c.Bind() + c.updateRender() + gl.DrawArrays(gl.TRIANGLES, 0, int32(c.vbolen)) +} + +// AppendVertex appends the chunk's global vertex into the array. +func (c *Chunk) AppendVertex(arr []Vertex) []Vertex { + off := itype.Vec3i{ + c.X * ChunkSizeX, + 0, + c.Z * ChunkSizeZ, + } + + for i := 0; i < ChunkSizeX; i++ { + for j := 0; j < ChunkSizeY; j++ { + for k := 0; k < ChunkSizeZ; k++ { + if c.Id[i][j][k] == 0 { + continue + } + + app := GetBlockAppearance(off.Addv(i, j, k), int(c.Id[i][j][k]), int(c.Aux[i][j][k]), nil) + switch app.RenderType { + case OneTexture: + arr = c.appendFace(itype.XPlus, itype.Vec3i{i, j, k}, app.Name+".png", arr) + arr = c.appendFace(itype.XMinus, itype.Vec3i{i, j, k}, app.Name+".png", arr) + arr = c.appendFace(itype.YPlus, itype.Vec3i{i, j, k}, app.Name+".png", arr) + arr = c.appendFace(itype.YMinus, itype.Vec3i{i, j, k}, app.Name+".png", arr) + arr = c.appendFace(itype.ZPlus, itype.Vec3i{i, j, k}, app.Name+".png", arr) + arr = c.appendFace(itype.ZMinus, itype.Vec3i{i, j, k}, app.Name+".png", arr) + case ThreeTexture: + arr = c.appendFace(itype.XPlus, itype.Vec3i{i, j, k}, app.Name+"_side.png", arr) + arr = c.appendFace(itype.XMinus, itype.Vec3i{i, j, k}, app.Name+"_side.png", arr) + arr = c.appendFace(itype.YPlus, itype.Vec3i{i, j, k}, app.Name+"_top.png", arr) + arr = c.appendFace(itype.YMinus, itype.Vec3i{i, j, k}, app.Name+"_bot.png", arr) + arr = c.appendFace(itype.ZPlus, itype.Vec3i{i, j, k}, app.Name+"_side.png", arr) + arr = c.appendFace(itype.ZMinus, itype.Vec3i{i, j, k}, app.Name+"_side.png", arr) + case SixTexture: + arr = c.appendFace(itype.XPlus, itype.Vec3i{i, j, k}, app.Name+"_x+.png", arr) + arr = c.appendFace(itype.XMinus, itype.Vec3i{i, j, k}, app.Name+"_x-.png", arr) + arr = c.appendFace(itype.YPlus, itype.Vec3i{i, j, k}, app.Name+"_y+.png", arr) + arr = c.appendFace(itype.YMinus, itype.Vec3i{i, j, k}, app.Name+"_y-.png", arr) + arr = c.appendFace(itype.ZPlus, itype.Vec3i{i, j, k}, app.Name+"_z+.png", arr) + arr = c.appendFace(itype.ZMinus, itype.Vec3i{i, j, k}, app.Name+"_z-.png", arr) + case CustomRendering: + arr = app.CustomRenderAppend(off.Addv(i, j, k), int(c.Aux[i][j][k]), nil, arr) + } + + } + } + } + + return arr +} + +func (c *Chunk) appendFace(face itype.Direction, pos itype.Vec3i, texname string, arr []Vertex) []Vertex { + + off := pos.Addv(c.X*ChunkSizeX, 0, c.Z*ChunkSizeZ).ToFloat32() + + // check if we can skip this face + next := pos.Add(itype.DirectionVeci[face]) + + if next[0] >= 0 && next[1] >= 0 && next[2] >= 0 && next[0] < ChunkSizeX && next[1] < ChunkSizeY && next[2] < ChunkSizeZ && + c.Id[next[0]][next[1]][next[2]] != 0 && + !GetBlockAppearance( + next.Addv(c.X*ChunkSizeX, 0, c.Z*ChunkSizeZ), + int(c.Id[next[0]][next[1]][next[2]]), + int(c.Aux[next[0]][next[1]][next[2]]), + nil, + ).Transparent { // face next to a solid block + return arr // skip! + } + + switch face { + case itype.XPlus: // X+ + arr = append(arr, + // Vertex Position Normal Vector(normalized) Texture Coord Light + Vertex{off.Addv(1, 0, 0), itype.Vec3f{1, 0, 0}, itype.Vec2f{1, 1}, 16}, + Vertex{off.Addv(1, 1, 0), itype.Vec3f{1, 0, 0}, itype.Vec2f{1, 0}, 16}, + Vertex{off.Addv(1, 1, 1), itype.Vec3f{1, 0, 0}, itype.Vec2f{0, 0}, 16}, + Vertex{off.Addv(1, 0, 0), itype.Vec3f{1, 0, 0}, itype.Vec2f{1, 1}, 16}, + Vertex{off.Addv(1, 1, 1), itype.Vec3f{1, 0, 0}, itype.Vec2f{0, 0}, 16}, + Vertex{off.Addv(1, 0, 1), itype.Vec3f{1, 0, 0}, itype.Vec2f{0, 1}, 16}, + ) + case itype.XMinus: // X- + arr = append(arr, + Vertex{off.Addv(0, 0, 0), itype.Vec3f{-1, 0, 0}, itype.Vec2f{0, 1}, 16}, + Vertex{off.Addv(0, 1, 1), itype.Vec3f{-1, 0, 0}, itype.Vec2f{1, 0}, 16}, + Vertex{off.Addv(0, 1, 0), itype.Vec3f{-1, 0, 0}, itype.Vec2f{0, 0}, 16}, + Vertex{off.Addv(0, 0, 0), itype.Vec3f{-1, 0, 0}, itype.Vec2f{0, 1}, 16}, + Vertex{off.Addv(0, 0, 1), itype.Vec3f{-1, 0, 0}, itype.Vec2f{1, 1}, 16}, + Vertex{off.Addv(0, 1, 1), itype.Vec3f{-1, 0, 0}, itype.Vec2f{1, 0}, 16}, + ) + case itype.YPlus: // Y+ + arr = append(arr, + Vertex{off.Addv(0, 1, 0), itype.Vec3f{0, 1, 0}, itype.Vec2f{0, 0}, 16}, + Vertex{off.Addv(0, 1, 1), itype.Vec3f{0, 1, 0}, itype.Vec2f{0, 1}, 16}, + Vertex{off.Addv(1, 1, 0), itype.Vec3f{0, 1, 0}, itype.Vec2f{1, 0}, 16}, + Vertex{off.Addv(0, 1, 1), itype.Vec3f{0, 1, 0}, itype.Vec2f{0, 1}, 16}, + Vertex{off.Addv(1, 1, 1), itype.Vec3f{0, 1, 0}, itype.Vec2f{1, 1}, 16}, + Vertex{off.Addv(1, 1, 0), itype.Vec3f{0, 1, 0}, itype.Vec2f{1, 0}, 16}, + ) + case itype.YMinus: // Y- + arr = append(arr, + Vertex{off.Addv(0, 0, 0), itype.Vec3f{0, -1, 0}, itype.Vec2f{1, 0}, 16}, + Vertex{off.Addv(1, 0, 0), itype.Vec3f{0, -1, 0}, itype.Vec2f{0, 0}, 16}, + Vertex{off.Addv(1, 0, 1), itype.Vec3f{0, -1, 0}, itype.Vec2f{0, 1}, 16}, + Vertex{off.Addv(0, 0, 0), itype.Vec3f{0, -1, 0}, itype.Vec2f{1, 0}, 16}, + Vertex{off.Addv(1, 0, 1), itype.Vec3f{0, -1, 0}, itype.Vec2f{0, 1}, 16}, + Vertex{off.Addv(0, 0, 1), itype.Vec3f{0, -1, 0}, itype.Vec2f{1, 1}, 16}, + ) + case itype.ZPlus: // Z+ + arr = append(arr, + Vertex{off.Addv(0, 0, 1), itype.Vec3f{0, 0, 1}, itype.Vec2f{0, 1}, 16}, + Vertex{off.Addv(1, 0, 1), itype.Vec3f{0, 0, 1}, itype.Vec2f{1, 1}, 16}, + Vertex{off.Addv(0, 1, 1), itype.Vec3f{0, 0, 1}, itype.Vec2f{0, 0}, 16}, + Vertex{off.Addv(1, 1, 1), itype.Vec3f{0, 0, 1}, itype.Vec2f{1, 0}, 16}, + Vertex{off.Addv(0, 1, 1), itype.Vec3f{0, 0, 1}, itype.Vec2f{0, 0}, 16}, + Vertex{off.Addv(1, 0, 1), itype.Vec3f{0, 0, 1}, itype.Vec2f{1, 1}, 16}, + ) + case itype.ZMinus: // Z- + arr = append(arr, + Vertex{off.Addv(0, 0, 0), itype.Vec3f{0, 0, -1}, itype.Vec2f{1, 1}, 16}, + Vertex{off.Addv(0, 1, 0), itype.Vec3f{0, 0, -1}, itype.Vec2f{1, 0}, 16}, + Vertex{off.Addv(1, 1, 0), itype.Vec3f{0, 0, -1}, itype.Vec2f{0, 0}, 16}, + Vertex{off.Addv(0, 0, 0), itype.Vec3f{0, 0, -1}, itype.Vec2f{1, 1}, 16}, + Vertex{off.Addv(1, 1, 0), itype.Vec3f{0, 0, -1}, itype.Vec2f{0, 0}, 16}, + Vertex{off.Addv(1, 0, 0), itype.Vec3f{0, 0, -1}, itype.Vec2f{0, 1}, 16}, + ) + } + + texrect := asset.WorldTextureAtlas.RectNormalized(texname) + for i := len(arr) - 6; i < len(arr); i++ { + arr[i].Texture[0] = texrect.Left + arr[i].Texture[0]*texrect.Width + arr[i].Texture[1] = texrect.Top + arr[i].Texture[1]*texrect.Height + } + + return arr +} diff --git a/internal/world/render.go.old b/internal/world/render.go.old new file mode 100644 index 0000000..71167d4 --- /dev/null +++ b/internal/world/render.go.old @@ -0,0 +1,204 @@ +package world + +import ( + "fmt" + "log" + "os" + "unsafe" + + "github.com/Edgaru089/gl01/internal/asset" + "github.com/Edgaru089/gl01/internal/util/itype" + "github.com/go-gl/gl/all-core/gl" +) + +// Vertex represents a rendering vertex in the global coordinate system. +type Vertex struct { + World itype.Vec3f // World vertex coordinate + Normal itype.Vec3f // Surface normal vector (normalized) + Texture itype.Vec2f // World Texture Atlas vertex coordinate + Light float32 // Light level of the block (0 ~ 15=blocklight, 16=sunlight) +} + +func (c *Chunk) InitRender() { + gl.GenVertexArrays(1, &c.vao) + gl.GenBuffers(1, &c.vbo) + + c.renderChanged = true +} + +func (c *Chunk) FreeRender() { + gl.DeleteVertexArrays(1, &c.vao) + gl.DeleteBuffers(1, &c.vbo) +} + +// Bind calls glBindVertexArray(vao) and glBindBuffer(vbo). +func (c *Chunk) Bind() { + gl.BindVertexArray(c.vao) + gl.BindBuffer(gl.ARRAY_BUFFER, c.vbo) +} + +// EnableVertexArrayAttrib calls glEnableVertexArrayAttrib(vao, attribId). +func (c *Chunk) EnableVertexArrayAttrib(attribId uint32) { + c.Bind() + gl.EnableVertexAttribArray(attribId) +} + +// a global vertex array for VBO updates +//var vertex []Vertex + +// updateRender should be called with gl.BindBuffer(c.vbo). +func (c *Chunk) updateRender() { + if c.renderChanged { + c.vertex = c.AppendVertex(c.vertex[0:0]) + log.Printf("Chunk [%d,%d]: UpdateRender: vertex len of %d*%d = %d", c.X, c.Z, unsafe.Sizeof(Vertex{}), len(c.vertex), int(unsafe.Sizeof(Vertex{}))*len(c.vertex)) + + f, _ := os.Create("vertex.txt") + fmt.Fprint(f, c.vertex) + f.Close() + + gl.BufferData(gl.ARRAY_BUFFER, int(unsafe.Sizeof(Vertex{}))*len(c.vertex), gl.Ptr(c.vertex), gl.DYNAMIC_DRAW) + c.vbolen = len(c.vertex) + + c.renderChanged = false + } +} + +func (c *Chunk) Render() { + gl.BindVertexArray(c.vao) + gl.BindBuffer(gl.ARRAY_BUFFER, c.vbo) + c.updateRender() + gl.DrawArrays(gl.TRIANGLES, 0, int32(c.vbolen)) +} + +// ChunkRenderAppend appends the chunk's global vertex into the array. +func (c *Chunk) AppendVertex(arr []Vertex) []Vertex { + off := itype.Vec3i{ + c.X * ChunkSizeX, + 0, + c.Z * ChunkSizeZ, + } + + for i := 0; i < ChunkSizeX; i++ { + for j := 0; j < ChunkSizeY; j++ { + for k := 0; k < ChunkSizeZ; k++ { + if c.Id[i][j][k] == 0 { + continue + } + + app := GetBlockAppearance(off.Addv(i, j, k), int(c.Id[i][j][k]), int(c.Aux[i][j][k]), nil) + switch app.RenderType { + case OneTexture: + arr = c.appendFace(itype.XPlus, itype.Vec3i{i, j, k}, app.Name+".png", arr) + arr = c.appendFace(itype.XMinus, itype.Vec3i{i, j, k}, app.Name+".png", arr) + arr = c.appendFace(itype.YPlus, itype.Vec3i{i, j, k}, app.Name+".png", arr) + arr = c.appendFace(itype.YMinus, itype.Vec3i{i, j, k}, app.Name+".png", arr) + arr = c.appendFace(itype.ZPlus, itype.Vec3i{i, j, k}, app.Name+".png", arr) + arr = c.appendFace(itype.ZMinus, itype.Vec3i{i, j, k}, app.Name+".png", arr) + case ThreeTexture: + arr = c.appendFace(itype.XPlus, itype.Vec3i{i, j, k}, app.Name+"_side.png", arr) + arr = c.appendFace(itype.XMinus, itype.Vec3i{i, j, k}, app.Name+"_side.png", arr) + arr = c.appendFace(itype.YPlus, itype.Vec3i{i, j, k}, app.Name+"_top.png", arr) + arr = c.appendFace(itype.YMinus, itype.Vec3i{i, j, k}, app.Name+"_bot.png", arr) + arr = c.appendFace(itype.ZPlus, itype.Vec3i{i, j, k}, app.Name+"_side.png", arr) + arr = c.appendFace(itype.ZMinus, itype.Vec3i{i, j, k}, app.Name+"_side.png", arr) + case SixTexture: + arr = c.appendFace(itype.XPlus, itype.Vec3i{i, j, k}, app.Name+"_x+.png", arr) + arr = c.appendFace(itype.XMinus, itype.Vec3i{i, j, k}, app.Name+"_x-.png", arr) + arr = c.appendFace(itype.YPlus, itype.Vec3i{i, j, k}, app.Name+"_y+.png", arr) + arr = c.appendFace(itype.YMinus, itype.Vec3i{i, j, k}, app.Name+"_y-.png", arr) + arr = c.appendFace(itype.ZPlus, itype.Vec3i{i, j, k}, app.Name+"_z+.png", arr) + arr = c.appendFace(itype.ZMinus, itype.Vec3i{i, j, k}, app.Name+"_z-.png", arr) + case CustomRendering: + arr = app.CustomRenderAppend(off.Addv(i, j, k), int(c.Aux[i][j][k]), nil, arr) + } + + } + } + } + + return arr +} + +func (c *Chunk) appendFace(face itype.Direction, pos itype.Vec3i, texname string, arr []Vertex) []Vertex { + + off := pos.Addv(c.X*ChunkSizeX, 0, c.Z*ChunkSizeZ).ToFloat32() + + // check if we can skip this face + next := pos.Add(itype.DirectionVeci[face]) + if next[0] >= 0 && next[1] >= 0 && next[2] >= 0 && next[0] < ChunkSizeX && next[1] < ChunkSizeY && next[2] < ChunkSizeZ && + c.Id[next[0]][next[1]][next[2]] != 0 && + !GetBlockAppearance( + next.Addv(c.X*ChunkSizeX, 0, c.Z*ChunkSizeZ), + int(c.Id[next[0]][next[1]][next[2]]), + int(c.Aux[next[0]][next[1]][next[2]]), + nil, + ).Transparent { // face next to a solid block + return arr // skip! + } + + switch face { + case itype.XPlus: // X+ + arr = append(arr, + // Vertex Position Normal Vector(normalized) Texture Coord Light + Vertex{off.Addv(1, 0, 0), itype.Vec3f{1, 0, 0}, itype.Vec2f{1, 1}, 0.7}, + Vertex{off.Addv(1, 1, 0), itype.Vec3f{1, 0, 0}, itype.Vec2f{1, 0}, 0.7}, + Vertex{off.Addv(1, 1, 1), itype.Vec3f{1, 0, 0}, itype.Vec2f{0, 0}, 0.7}, + Vertex{off.Addv(1, 0, 0), itype.Vec3f{1, 0, 0}, itype.Vec2f{1, 1}, 0.7}, + Vertex{off.Addv(1, 1, 1), itype.Vec3f{1, 0, 0}, itype.Vec2f{0, 0}, 0.7}, + Vertex{off.Addv(1, 0, 1), itype.Vec3f{1, 0, 0}, itype.Vec2f{0, 1}, 0.7}, + ) + case itype.XMinus: // X- + arr = append(arr, + Vertex{off.Addv(0, 0, 0), itype.Vec3f{-1, 0, 0}, itype.Vec2f{0, 1}, 0.95}, + Vertex{off.Addv(0, 1, 1), itype.Vec3f{-1, 0, 0}, itype.Vec2f{1, 0}, 0.95}, + Vertex{off.Addv(0, 1, 0), itype.Vec3f{-1, 0, 0}, itype.Vec2f{0, 0}, 0.95}, + Vertex{off.Addv(0, 0, 0), itype.Vec3f{-1, 0, 0}, itype.Vec2f{0, 1}, 0.95}, + Vertex{off.Addv(0, 0, 1), itype.Vec3f{-1, 0, 0}, itype.Vec2f{1, 1}, 0.95}, + Vertex{off.Addv(0, 1, 1), itype.Vec3f{-1, 0, 0}, itype.Vec2f{1, 0}, 0.95}, + ) + case itype.YPlus: // Y+ + arr = append(arr, + Vertex{off.Addv(0, 1, 0), itype.Vec3f{0, 1, 0}, itype.Vec2f{0, 0}, 1}, + Vertex{off.Addv(0, 1, 1), itype.Vec3f{0, 1, 0}, itype.Vec2f{0, 1}, 1}, + Vertex{off.Addv(1, 1, 0), itype.Vec3f{0, 1, 0}, itype.Vec2f{1, 0}, 1}, + Vertex{off.Addv(0, 1, 1), itype.Vec3f{0, 1, 0}, itype.Vec2f{0, 1}, 1}, + Vertex{off.Addv(1, 1, 1), itype.Vec3f{0, 1, 0}, itype.Vec2f{1, 1}, 1}, + Vertex{off.Addv(1, 1, 0), itype.Vec3f{0, 1, 0}, itype.Vec2f{1, 0}, 1}, + ) + case itype.YMinus: // Y- + arr = append(arr, + Vertex{off.Addv(0, 0, 0), itype.Vec3f{0, -1, 0}, itype.Vec2f{1, 0}, 0.6}, + Vertex{off.Addv(1, 0, 0), itype.Vec3f{0, -1, 0}, itype.Vec2f{0, 0}, 0.6}, + Vertex{off.Addv(1, 0, 1), itype.Vec3f{0, -1, 0}, itype.Vec2f{0, 1}, 0.6}, + Vertex{off.Addv(0, 0, 0), itype.Vec3f{0, -1, 0}, itype.Vec2f{1, 0}, 0.6}, + Vertex{off.Addv(1, 0, 1), itype.Vec3f{0, -1, 0}, itype.Vec2f{0, 1}, 0.6}, + Vertex{off.Addv(0, 0, 1), itype.Vec3f{0, -1, 0}, itype.Vec2f{1, 1}, 0.6}, + ) + case itype.ZPlus: // Z+ + arr = append(arr, + Vertex{off.Addv(0, 0, 1), itype.Vec2f{0, 1}, 0.85}, + Vertex{off.Addv(1, 0, 1), itype.Vec2f{1, 1}, 0.85}, + Vertex{off.Addv(0, 1, 1), itype.Vec2f{0, 0}, 0.85}, + Vertex{off.Addv(1, 1, 1), itype.Vec2f{1, 0}, 0.85}, + Vertex{off.Addv(0, 1, 1), itype.Vec2f{0, 0}, 0.85}, + Vertex{off.Addv(1, 0, 1), itype.Vec2f{1, 1}, 0.85}, + ) + case itype.ZMinus: // Z- + arr = append(arr, + Vertex{off.Addv(0, 0, 0), itype.Vec2f{1, 1}, 0.85}, + Vertex{off.Addv(0, 1, 0), itype.Vec2f{1, 0}, 0.85}, + Vertex{off.Addv(1, 1, 0), itype.Vec2f{0, 0}, 0.85}, + Vertex{off.Addv(0, 0, 0), itype.Vec2f{1, 1}, 0.85}, + Vertex{off.Addv(1, 1, 0), itype.Vec2f{0, 0}, 0.85}, + Vertex{off.Addv(1, 0, 0), itype.Vec2f{0, 1}, 0.85}, + ) + } + + texrect := asset.WorldTextureAtlas.RectNormalized(texname) + for i := len(arr) - 6; i < len(arr); i++ { + arr[i].Texture[0] = texrect.Left + arr[i].Texture[0]*texrect.Width + arr[i].Texture[1] = texrect.Top + arr[i].Texture[1]*texrect.Height + } + + return arr +} diff --git a/internal/world/world.go b/internal/world/world.go new file mode 100644 index 0000000..f902d02 --- /dev/null +++ b/internal/world/world.go @@ -0,0 +1,126 @@ +package world + +import ( + "fmt" + "io" + + "edgaru089.ml/go/gl01/internal/util/itype" +) + +// World holds a number of Chunks and Entities. +type World struct { + Chunks map[itype.Vec2i]*Chunk + Seed int32 +} + +// NewWorld creates a new, empty, world with no chunks. +func NewWorld() *World { + var w World + w.Chunks = make(map[itype.Vec2i]*Chunk) + + return &w +} + +// The default World. +var DefaultWorld World + +func init() { + DefaultWorld.Chunks = make(map[itype.Vec2i]*Chunk) +} + +// Chunk returns the chunk at [x, z], or nil if nonexistent. +func (w *World) Chunk(x, z int) *Chunk { + return w.Chunks[itype.Vec2i{x, z}] +} + +// SetChunk sets the chunk [x, z] to c. +func (w *World) SetChunk(x, z int, c *Chunk) { + if old, ok := w.Chunks[itype.Vec2i{x, z}]; ok { + old.FreeRender() + } + + c.X = x + c.Z = z + c.renderChanged = true + w.Chunks[itype.Vec2i{x, z}] = c + c.InitRender() +} + +func BlockPosToChunk(blockpos itype.Vec3i) (x, z int) { + if blockpos[0] >= 0 { + x = blockpos[0] / ChunkSizeX + } else { + x = (blockpos[0]+1)/ChunkSizeX - 1 + } + + if blockpos[2] >= 0 { + z = blockpos[2] / ChunkSizeZ + } else { + z = (blockpos[2]+1)/ChunkSizeZ - 1 + } + + return +} + +func BlockPosToInChunk(blockpos itype.Vec3i) (x, y, z int) { + if blockpos[0] >= 0 { + x = blockpos[0] % ChunkSizeX + } else { + x = ChunkSizeX + (blockpos[0] % ChunkSizeX) + if x == ChunkSizeX { + x = 0 + } + } + y = blockpos[1] + if blockpos[2] >= 0 { + z = blockpos[2] % ChunkSizeZ + } else { + z = ChunkSizeZ + (blockpos[2] % ChunkSizeZ) + if z == ChunkSizeZ { + z = 0 + } + } + return +} + +// Block returns the block at the position [pos], or an empty Block{} if it does not exist. +func (w *World) Block(pos itype.Vec3i) Block { + cx, cz := BlockPosToChunk(pos) + cix, ciy, ciz := BlockPosToInChunk(pos) + c, ok := w.Chunks[itype.Vec2i{cx, cz}] + if !ok || ciy < 0 || ciy >= ChunkSizeY { + return Block{} + } + return Block{ + Id: int(c.Id[cix][ciy][ciz]), + Aux: int(c.Aux[cix][ciy][ciz]), + Dataset: nil, + Behaviour: GetBlockBehaviour(int(c.Id[cix][ciy][ciz])), + } +} + +// LoadChunkFromGob loads (or overwrites) chunk [id.x, id.z] from the Gob-encoding file. +func (w *World) LoadChunkFromGob(id itype.Vec2i, file io.Reader) (err error) { + c := &Chunk{} + if err != nil { + return fmt.Errorf("World: load chunk (%d,%d) from gob file: %s", id[0], id[1], err) + } + + if old, ok := w.Chunks[id]; ok { + old.FreeRender() + } + + c.LoadFromGobIndexed(file, id[0], id[1]) + w.Chunks[id] = c + c.InitRender() + + return nil +} + +// Render should be called from WorldRenderer, setting shader and texture context +// for the Render function, which merely calls BindVertexArray and DrawArrays. +func (w *World) Render() { + for _, c := range w.Chunks { + c.Render() + } +} diff --git a/internal/world/worldgen/worldgen.go b/internal/world/worldgen/worldgen.go new file mode 100644 index 0000000..6c62767 --- /dev/null +++ b/internal/world/worldgen/worldgen.go @@ -0,0 +1,82 @@ +package worldgen + +import ( + "sync" + + packworld "edgaru089.ml/go/gl01/internal/world" + "github.com/aquilax/go-perlin" +) + +const ( + Alpha = 5 // Weight forming the noise sum + Beta = 2 // harmonic scaling/spacing + N = 3 // iterations + + AltitudeDensity = 10 // Density of the noise in altitude + + Sealevel = 63 // Space with Y=63 and lower should be the sea + Highest = 72 // Highest part of the terrain + Lowest = 56 // Lowest part of the terrain +) + +var perlins map[int64]*perlin.Perlin +var perlinsLock sync.RWMutex + +func init() { + perlins = make(map[int64]*perlin.Perlin) +} + +// Chunk generates the chunk (must with chunkID set!!) with seed. +func Chunk(chunk *packworld.Chunk, world *packworld.World, seed int64) { + + perlinsLock.RLock() + p, ok := perlins[seed] + if !ok { + perlinsLock.RUnlock() + perlinsLock.Lock() + p = perlin.NewPerlin(Alpha, Beta, N, seed) + perlins[seed] = p + perlinsLock.Unlock() + } else { + perlinsLock.RUnlock() + } + + //log.Printf("noise=%.5f", p.Noise2D(0.1, 0.1)) + + offX := packworld.ChunkSizeX * chunk.X + offZ := packworld.ChunkSizeZ * chunk.Z + + for x := 0; x < packworld.ChunkSizeX; x++ { + for z := 0; z < packworld.ChunkSizeZ; z++ { + + height := Lowest + int(float64(Highest-Lowest)*p.Noise2D( + float64(offX+x)/AltitudeDensity, + float64(offZ+z)/AltitudeDensity)) + //log.Printf("height = %d (noise=%.5f)", height, p.Noise2D(float64(offX+x), float64(offZ+z))) + + // covering dirt + chunk.Id[x][height][z] = packworld.BlockGrass + //chunk.Id[x][height][z] = packworld.BlockDebugDir + chunk.Id[x][height-1][z] = packworld.BlockDirt + chunk.Id[x][height-2][z] = packworld.BlockDirt + chunk.Id[x][height-3][z] = packworld.BlockDirt + height -= 4 + + // stone in the middle + for y := height; y >= 1; y-- { + chunk.Id[x][y][z] = packworld.BlockStone + } + + // bedrock at the bottom + chunk.Id[x][0][z] = packworld.BlockBedrock + + // plant trees + if height >= 50 && height <= 70 { + if p.Noise2D(float64(offX+x), float64(offZ+z)) > 0.9 { + } + } + } + } + + chunk.InvalidateRender() +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..463bb21 --- /dev/null +++ b/main.go @@ -0,0 +1,130 @@ +package main + +import ( + "bytes" + "fmt" + "runtime" + "time" + + "github.com/go-gl/gl/v4.1-core/gl" + "github.com/go-gl/glfw/v3.3/glfw" + "github.com/go-gl/mathgl/mgl32" +) + +const ( + windowWidth = 800 + windowHeight = 600 +) + +func init() { + runtime.LockOSThread() +} + +func main() { + + if err := glfw.Init(); err != nil { + panic(err) + } + defer glfw.Terminate() + + glfw.WindowHint(glfw.Resizable, 0) + glfw.WindowHint(glfw.ContextVersionMajor, 4) + glfw.WindowHint(glfw.ContextVersionMinor, 1) + glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile) + glfw.WindowHint(glfw.OpenGLForwardCompatible, 1) + win, err := glfw.CreateWindow(windowWidth, windowHeight, "Cube", nil, nil) + if err != nil { + panic(err) + } + win.MakeContextCurrent() + glfw.SwapInterval(1) + + err = gl.Init() + if err != nil { + panic(err) + } + + fmt.Println("OpenGL Version: ", gl.GoStr(gl.GetString(gl.VERSION))) + + program, err := newProgram(shaderVert, shaderFrag) + if err != nil { + panic(err) + } + gl.UseProgram(program) + + model := mgl32.Ident4() + modelUniform := gl.GetUniformLocation(program, gl.Str("model\x00")) + gl.UniformMatrix4fv(modelUniform, 1, false, &model[0]) + + view := mgl32.LookAtV(mgl32.Vec3{3, 3, 3}, mgl32.Vec3{0, 0, 0}, mgl32.Vec3{0, 1, 0}) + viewUniform := gl.GetUniformLocation(program, gl.Str("view\x00")) + gl.UniformMatrix4fv(viewUniform, 1, false, &view[0]) + + projection := mgl32.Perspective(mgl32.DegToRad(45), float32(windowWidth)/float32(windowHeight), 0.1, 10) + projectionUniform := gl.GetUniformLocation(program, gl.Str("projection\x00")) + gl.UniformMatrix4fv(projectionUniform, 1, false, &projection[0]) + + // Texture + textureUniform := gl.GetUniformLocation(program, gl.Str("tex\x00")) + gl.Uniform1i(textureUniform, 0) + + texture, err := newTexture(bytes.NewReader(textureData)) + if err != nil { + panic(err) + } + + gl.BindFragDataLocation(program, 0, gl.Str("outputColor\x00")) + + // Vertex Array + var vao uint32 + gl.GenVertexArrays(1, &vao) + gl.BindVertexArray(vao) + + // Vertex Buffer + var vbo uint32 + gl.GenBuffers(1, &vbo) + gl.BindBuffer(gl.ARRAY_BUFFER, vbo) + gl.BufferData(gl.ARRAY_BUFFER, len(cubeVertices)*4, gl.Ptr(cubeVertices), gl.STATIC_DRAW) + + vertAttrib := uint32(gl.GetAttribLocation(program, gl.Str("vert\x00"))) + gl.EnableVertexAttribArray(vertAttrib) + gl.VertexAttribPointer(vertAttrib, 3, gl.FLOAT, false, 5*4, gl.PtrOffset(0)) + + texCoordAttrib := uint32(gl.GetAttribLocation(program, gl.Str("vertTexCoord\x00"))) + gl.EnableVertexAttribArray(texCoordAttrib) + gl.VertexAttribPointer(texCoordAttrib, 2, gl.FLOAT, false, 5*4, gl.PtrOffset(3*4)) + + gl.Enable(gl.CULL_FACE) + gl.Enable(gl.DEPTH_TEST) + gl.DepthFunc(gl.LESS) + gl.ClearColor(1, 1, 1, 1) + + angle := 0.0 + prevTime := time.Now() + + for !win.ShouldClose() { + + gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT) + + // Angle update + angle += time.Since(prevTime).Seconds() + prevTime = time.Now() + + model = mgl32.HomogRotate3D(float32(angle), mgl32.Vec3{0, 1, 0}) + + // Render + // Shader variable + gl.UseProgram(program) + gl.UniformMatrix4fv(modelUniform, 1, false, &model[0]) + + gl.BindVertexArray(vao) + + gl.ActiveTexture(gl.TEXTURE0) + gl.BindTexture(gl.TEXTURE_2D, texture) + + gl.DrawArrays(gl.TRIANGLES, 0, 6*2*3) + + win.SwapBuffers() + glfw.PollEvents() + } +} diff --git a/shader.frag b/shader.frag new file mode 100644 index 0000000..14ed0e6 --- /dev/null +++ b/shader.frag @@ -0,0 +1,11 @@ +#version 330 + +uniform sampler2D tex; + +in vec2 fragTexCoord; + +out vec4 outputColor; + +void main() { + outputColor = texture(tex, fragTexCoord); +} diff --git a/shader.go b/shader.go new file mode 100644 index 0000000..d14eaca --- /dev/null +++ b/shader.go @@ -0,0 +1,81 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/go-gl/gl/v4.1-core/gl" +) + +func compileShader(src string, stype uint32) (prog uint32, err error) { + prog = gl.CreateShader(stype) + + strs, free := gl.Strs(src, "\x00") + gl.ShaderSource(prog, 1, strs, nil) + free() + gl.CompileShader(prog) + + var status int32 + gl.GetShaderiv(prog, gl.COMPILE_STATUS, &status) + if status == gl.FALSE { + var len int32 + gl.GetShaderiv(prog, gl.INFO_LOG_LENGTH, &len) + + log := strings.Repeat("\x00", int(len+1)) + gl.GetShaderInfoLog(prog, len, nil, gl.Str(log)) + + gl.DeleteShader(prog) + + switch stype { + case gl.VERTEX_SHADER: + return 0, fmt.Errorf("failed to compile Vertex Shader: %s", log) + case gl.FRAGMENT_SHADER: + return 0, fmt.Errorf("failed to compile Fragment Shader: %s", log) + default: + return 0, fmt.Errorf("failed to compile Unknown(%d) Shader: %s", stype, log) + } + } + + return +} + +func newProgram(vert, frag string) (prog uint32, err error) { + + vertid, err := compileShader(vert, gl.VERTEX_SHADER) + if err != nil { + return 0, err + } + + fragid, err := compileShader(frag, gl.FRAGMENT_SHADER) + if err != nil { + gl.DeleteShader(vertid) + return 0, err + } + + prog = gl.CreateProgram() + + gl.AttachShader(prog, vertid) + gl.AttachShader(prog, fragid) + gl.LinkProgram(prog) + + var status int32 + gl.GetProgramiv(prog, gl.LINK_STATUS, &status) + if status == gl.FALSE { + var len int32 + gl.GetProgramiv(prog, gl.INFO_LOG_LENGTH, &len) + + log := strings.Repeat("\x00", int(len+1)) + gl.GetProgramInfoLog(prog, len, nil, gl.Str(log)) + + gl.DeleteProgram(prog) + gl.DeleteShader(vertid) + gl.DeleteShader(fragid) + + return 0, fmt.Errorf("failed to link Program: %s", log) + } + + gl.DeleteShader(vertid) + gl.DeleteShader(fragid) + + return +} diff --git a/shader.vert b/shader.vert new file mode 100644 index 0000000..835ca41 --- /dev/null +++ b/shader.vert @@ -0,0 +1,16 @@ +#version 330 + +uniform mat4 projection; +uniform mat4 view; +uniform mat4 model; + +in vec3 vert; +in vec2 vertTexCoord; + +out vec2 fragTexCoord; + +void main() { + fragTexCoord = vertTexCoord; + gl_Position = projection * view * model * vec4(vert, 1); +} + diff --git a/square.png b/square.png new file mode 100644 index 0000000..134f144 Binary files /dev/null and b/square.png differ diff --git a/texture.go b/texture.go new file mode 100644 index 0000000..5c0ff72 --- /dev/null +++ b/texture.go @@ -0,0 +1,46 @@ +package main + +import ( + "errors" + "image" + "image/draw" + _ "image/png" + "io" + + "github.com/go-gl/gl/v4.1-core/gl" +) + +func newTexture(file io.Reader) (tex uint32, err error) { + img, _, err := image.Decode(file) + if err != nil { + return 0, err + } + + rgba := image.NewRGBA(img.Bounds()) + if rgba.Stride != rgba.Rect.Size().X*4 { + return 0, errors.New("unsupported stride") + } + draw.Draw(rgba, rgba.Bounds(), img, image.Point{0, 0}, draw.Src) + + gl.GenTextures(1, &tex) + gl.ActiveTexture(gl.TEXTURE0) + gl.BindTexture(gl.TEXTURE_2D, tex) + 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_WRAP_S, gl.CLAMP_TO_EDGE) + gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) + + gl.TexImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + int32(rgba.Rect.Size().X), + int32(rgba.Rect.Size().Y), + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + gl.Ptr(rgba.Pix), + ) + + return +}