Initial commit
13
.gitignore
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
.DS_Store
|
||||
._*
|
||||
|
||||
*.gob
|
||||
|
||||
gl01
|
||||
gl01.exe
|
||||
*.exe
|
||||
*.dll
|
||||
|
||||
cmd/cmd
|
||||
cmd/cmd.exe
|
||||
cmd/imgui.ini
|
82
cmd/main.go
Normal file
@ -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++
|
||||
}
|
||||
}
|
||||
}
|
138
cmd/main.go.old
Normal file
@ -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()
|
||||
}
|
||||
}
|
94
cmd/main.go.old2
Normal file
@ -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()
|
||||
}
|
||||
}
|
65
embed.go
Normal file
@ -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,
|
||||
}
|
13
go.mod
Normal file
@ -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
|
20
go.sum
Normal file
@ -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=
|
9
internal/asset/embed.go
Normal file
@ -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
|
BIN
internal/asset/font/unifont-11.0.02.2.ttf
Executable file
21
internal/asset/shader.go
Normal file
@ -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
|
9
internal/asset/shader/framewire.frag
Normal file
@ -0,0 +1,9 @@
|
||||
#version 330
|
||||
|
||||
in vec4 fragColor;
|
||||
|
||||
out vec4 outputColor;
|
||||
|
||||
void main() {
|
||||
outputColor = fragColor;
|
||||
}
|
16
internal/asset/shader/framewire.vert
Normal file
@ -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);
|
||||
}
|
||||
|
87
internal/asset/shader/world.frag
Normal file
@ -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;
|
||||
}
|
6
internal/asset/shader/world.shadowmap.frag
Normal file
@ -0,0 +1,6 @@
|
||||
#version 330
|
||||
|
||||
void main() {
|
||||
// gl_FragDepth = gl.FragCoord.z;
|
||||
}
|
||||
|
14
internal/asset/shader/world.shadowmap.vert
Normal file
@ -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);
|
||||
}
|
||||
|
28
internal/asset/shader/world.vert
Normal file
@ -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;
|
||||
}
|
||||
|
43
internal/asset/texture.go
Normal file
@ -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*/ )
|
||||
}
|
BIN
internal/asset/texture/world/bedrock.png
Normal file
After Width: | Height: | Size: 225 B |
BIN
internal/asset/texture/world/cobblestone.png
Normal file
After Width: | Height: | Size: 568 B |
BIN
internal/asset/texture/world/cobblestone_mossy.png
Normal file
After Width: | Height: | Size: 632 B |
BIN
internal/asset/texture/world/command_block.png
Normal file
After Width: | Height: | Size: 349 B |
BIN
internal/asset/texture/world/debug.png
Normal file
After Width: | Height: | Size: 645 B |
BIN
internal/asset/texture/world/debug_dir_x+.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
internal/asset/texture/world/debug_dir_x-.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
internal/asset/texture/world/debug_dir_y+.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
internal/asset/texture/world/debug_dir_y-.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
internal/asset/texture/world/debug_dir_z+.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
internal/asset/texture/world/debug_dir_z-.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
BIN
internal/asset/texture/world/dirt.png
Normal file
After Width: | Height: | Size: 266 B |
BIN
internal/asset/texture/world/grass_bot.png
Normal file
After Width: | Height: | Size: 266 B |
BIN
internal/asset/texture/world/grass_side.png
Normal file
After Width: | Height: | Size: 408 B |
BIN
internal/asset/texture/world/grass_top.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
internal/asset/texture/world/grass_top_greyscale.png
Normal file
After Width: | Height: | Size: 560 B |
BIN
internal/asset/texture/world/leaves_oak.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
internal/asset/texture/world/log_oak.png
Normal file
After Width: | Height: | Size: 528 B |
BIN
internal/asset/texture/world/log_oak_bot.png
Normal file
After Width: | Height: | Size: 478 B |
BIN
internal/asset/texture/world/log_oak_top.png
Normal file
After Width: | Height: | Size: 478 B |
BIN
internal/asset/texture/world/stone.png
Normal file
After Width: | Height: | Size: 223 B |
29
internal/entity/entities/player.go
Normal file
@ -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) {
|
||||
|
||||
}
|
109
internal/entity/entity.go
Normal file
@ -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)
|
||||
}
|
1
internal/entity/manager.go
Normal file
@ -0,0 +1 @@
|
||||
package entity
|
298
internal/entity/physics.go
Normal file
@ -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)
|
||||
}
|
233
internal/entity/physics.go.new
Normal file
@ -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)
|
||||
}
|
49
internal/game/game.go
Normal file
@ -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
|
||||
}
|
333
internal/game/logic.go
Normal file
@ -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)
|
||||
}
|
115
internal/game/logic.go.old
Executable file
@ -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)
|
||||
}
|
285
internal/game/logic.go.old2
Executable file
@ -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)
|
||||
}
|
122
internal/igwrap/glfw.go
Normal file
@ -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))
|
||||
}
|
248
internal/igwrap/render.go
Normal file
@ -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])
|
||||
}
|
13
internal/igwrap/shader.frag
Normal file
@ -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);
|
||||
}
|
||||
|
17
internal/igwrap/shader.vert
Normal file
@ -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);
|
||||
}
|
||||
|
7
internal/io/io.go
Normal file
@ -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.
|
||||
)
|
10
internal/render/gpu_preference/gpu_preference.c
Normal file
@ -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
|
3
internal/render/gpu_preference/gpu_preference.go
Normal file
@ -0,0 +1,3 @@
|
||||
package gpu_preference
|
||||
|
||||
import "C"
|
15
internal/render/gpu_preference/linux_nvidia.go
Normal file
@ -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")
|
||||
}
|
||||
}
|
127
internal/render/render_framewire.go
Normal file
@ -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]
|
||||
}
|
130
internal/render/render_world.go
Normal file
@ -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)
|
||||
}
|
119
internal/render/render_world.go.old
Executable file
@ -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()
|
||||
}
|
257
internal/render/shader.go
Normal file
@ -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)))
|
||||
}
|
143
internal/render/texture.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
80
internal/render/view.go
Normal file
@ -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
|
||||
}
|
18
internal/util/absminus.go
Normal file
@ -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
|
||||
}
|
159
internal/util/atlas.go
Normal file
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
15
internal/util/bunnyhop.go
Normal file
@ -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))
|
||||
}
|
35
internal/util/itype/angle.go
Normal file
@ -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)
|
||||
}
|
11
internal/util/itype/dataset.go
Normal file
@ -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{}
|
37
internal/util/itype/direction.go
Normal file
@ -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},
|
||||
}
|
133
internal/util/itype/rect.go
Normal file
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
147
internal/util/itype/vec.go
Normal file
@ -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
|
55
internal/util/minmax.go
Normal file
@ -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
|
||||
}
|
20
internal/util/mod.go
Normal file
@ -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
|
||||
}
|
23
internal/util/sort.go
Normal file
@ -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)
|
||||
}
|
178
internal/world/block.go
Normal file
@ -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)
|
||||
}
|
160
internal/world/block.go.new
Normal file
@ -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
|
||||
}
|
72
internal/world/chunk.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
40
internal/world/default_blocks.go
Normal file
@ -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()
|
||||
}
|
215
internal/world/render.go
Normal file
@ -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
|
||||
}
|
204
internal/world/render.go.old
Normal file
@ -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
|
||||
}
|
126
internal/world/world.go
Normal file
@ -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()
|
||||
}
|
||||
}
|
82
internal/world/worldgen/worldgen.go
Normal file
@ -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()
|
||||
}
|
130
main.go
Normal file
@ -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()
|
||||
}
|
||||
}
|
11
shader.frag
Normal file
@ -0,0 +1,11 @@
|
||||
#version 330
|
||||
|
||||
uniform sampler2D tex;
|
||||
|
||||
in vec2 fragTexCoord;
|
||||
|
||||
out vec4 outputColor;
|
||||
|
||||
void main() {
|
||||
outputColor = texture(tex, fragTexCoord);
|
||||
}
|
81
shader.go
Normal file
@ -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
|
||||
}
|
16
shader.vert
Normal file
@ -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);
|
||||
}
|
||||
|
BIN
square.png
Normal file
After Width: | Height: | Size: 352 KiB |
46
texture.go
Normal file
@ -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
|
||||
}
|