Initial commit

This commit is contained in:
Edgaru089 2022-01-20 21:58:50 +08:00
commit b44d41ec66
86 changed files with 5415 additions and 0 deletions

13
.gitignore vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

21
internal/asset/shader.go Normal file
View 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

View File

@ -0,0 +1,9 @@
#version 330
in vec4 fragColor;
out vec4 outputColor;
void main() {
outputColor = fragColor;
}

View 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);
}

View 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;
}

View File

@ -0,0 +1,6 @@
#version 330
void main() {
// gl_FragDepth = gl.FragCoord.z;
}

View 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);
}

View 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
View 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*/ )
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 225 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 568 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 632 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 645 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 560 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 478 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

View 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
View 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)
}

View File

@ -0,0 +1 @@
package entity

298
internal/entity/physics.go Normal file
View 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)
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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])
}

View 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);
}

View 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
View 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.
)

View 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

View File

@ -0,0 +1,3 @@
package gpu_preference
import "C"

View 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")
}
}

View 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]
}

View 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)
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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))
}

View 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)
}

View 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{}

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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)
}
}

View 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
View 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
}

View 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
View 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()
}
}

View 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
View 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 KiB

46
texture.go Normal file
View 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
}