Browse Source

first commit

master
asiekierka 5 years ago
commit
fee705ba89
8 changed files with 1168 additions and 0 deletions
  1. 4
    0
      README.md
  2. 92
    0
      block.go
  3. 287
    0
      game.go
  4. 162
    0
      model.go
  5. 450
    0
      render.go
  6. 23
    0
      types.go
  7. 39
    0
      vector.go
  8. 111
    0
      world.go

+ 4
- 0
README.md View File

@@ -0,0 +1,4 @@
# License

* The project has no defined license yet, which means a default of All Rights Reserved for the moment.
* The testing textures come from the Isabella II texture pack for Minecraft 1.5.2 by bonemouse (with slight adaptation edits) and are licensed under CC BY 3.0 Unported.

+ 92
- 0
block.go View File

@@ -0,0 +1,92 @@
package main

type Block interface {
New() Block
Name() string
GetModel(r *Render) Model
GetBoundingBox() BoundingBox
IsSideSolid(d Direction) bool
}

type BlockRegistry struct {
idBlock []Block
nameBlock map[string]Block
nameId map[string]int
maxId int
}

type BlockSimple struct {
name string
textures [6]string
models map[*Render]Model
}

func NewBlockRegistry() BlockRegistry {
return BlockRegistry{
idBlock: make([]Block, 256, 256),
nameId: make(map[string]int, 256),
nameBlock: make(map[string]Block, 256),
maxId: 1,
}
}

func (b *BlockRegistry) ByName(s string) Block {
return b.nameBlock[s]
}

func (b *BlockRegistry) ByID(i int) Block {
return b.idBlock[i]
}

func (b *BlockRegistry) GetID(bb Block) int {
return b.nameId[bb.Name()]
}

func (b *BlockRegistry) allocID() int {
b.maxId++
for b.idBlock[b.maxId] != nil {
b.maxId++
}
return b.maxId - 1
}

func (b *BlockRegistry) Register(bb Block) {
id := b.allocID()
b.nameBlock[bb.Name()] = bb
b.nameId[bb.Name()] = id
b.idBlock[id] = bb
}

func (b *BlockSimple) Name() string {
return b.name
}

func (b *BlockSimple) GetModel(r *Render) Model {
if b.models == nil {
b.models = make(map[*Render]Model, 1)
}
if v, ok := b.models[r]; ok {
return v
}
b.models[r] = NewCubeModel([6]Texture{
r.textures[b.textures[0]],
r.textures[b.textures[1]],
r.textures[b.textures[2]],
r.textures[b.textures[3]],
r.textures[b.textures[4]],
r.textures[b.textures[5]],
})
return b.models[r]
}

func (b *BlockSimple) GetBoundingBox() BoundingBox {
return BoundingBox{Vec3{0,0,0}, Vec3{1,1,1}}
}

func (b *BlockSimple) IsSideSolid(d Direction) bool {
return true
}

func (b *BlockSimple) New() Block {
return b
}

+ 287
- 0
game.go View File

@@ -0,0 +1,287 @@
package main

import (
"flag"
"fmt"
_ "image/png"
"log"
"math"
"os"
"runtime"
"runtime/pprof"
"time"

"github.com/barnex/fmath"
"github.com/go-gl/glfw/v3.1/glfw"
)

var cpuprofile = flag.Bool("cpuprofile", false, "write cpu profile to file")
var heapprofile = flag.Bool("heapprofile", false, "write heap profile to file")
var debugtextures = flag.Bool("debugtextures", false, "write texture sheet to file")

type Player struct {
pos Vec3
gravity float32
yaw float32
pitch float32
}

type Average struct {
data []float64
pos int
}

func NewAverage(len int) Average {
return Average{make([]float64, len), 0}
}

func (a *Average) Push(v float64) {
a.data[a.pos] = v
a.pos++
if a.pos == len(a.data) {
a.pos = 0
}
}

func (a Average) Get() float64 {
var t float64
for i := 0; i < len(a.data); i++ {
t += a.data[i]
}
return t / float64(len(a.data))
}

var (
fps Average
player Player
movementX float32
movementZ float32
render Render
lastMx float64
lastMy float64
)

const DEG_RAD = math.Pi / 180

func onMouse(w *glfw.Window, button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey) {
if action == glfw.Press {
if button == glfw.MouseButtonLeft {
breakBlock()
} else if button == glfw.MouseButtonRight {
placeBlock()
}
}
}

func onResize(w *glfw.Window, width int, height int) {
render.Resize(int32(width), int32(height))
}

func onMove(w *glfw.Window, x float64, y float64) {
player.yaw += float32((x - lastMx) / 1000)
player.pitch += float32((y - lastMy) / 1000)
if player.pitch < -(math.Pi / 2) {
player.pitch = -(math.Pi / 2)
} else if player.pitch > (math.Pi / 2) {
player.pitch = (math.Pi / 2)
}
lastMx = x
lastMy = y
}

func (player Player) GetHoverCoords(w World) (Position, bool) {
stepX := -fmath.Sin(-player.yaw) * fmath.Cos(player.pitch) * 0.2
stepY := -fmath.Sin(player.pitch) * 0.2
stepZ := -fmath.Cos(-player.yaw) * fmath.Cos(player.pitch) * 0.2
bX := player.pos[0]
bY := player.pos[1] + EYE_HEIGHT
bZ := player.pos[2]
for stepCount := 100; stepCount > 0; stepCount-- {
if w.GetBlock(int(bX), int(bY), int(bZ)) != nil {
return Position{int(bX), int(bY), int(bZ)}, true
} else {
bX += stepX
bY += stepY
bZ += stepZ
}
}
return Position{}, false
}

func breakBlock() {
if pos, exists := player.GetHoverCoords(&w); exists {
w.SetBlock(pos.x, pos.y, pos.z, nil)
}
}

func placeBlock() {
stepX := -fmath.Sin(-player.yaw) * fmath.Cos(player.pitch) * 0.2
stepY := -fmath.Sin(player.pitch) * 0.2
stepZ := -fmath.Cos(-player.yaw) * fmath.Cos(player.pitch) * 0.2
bX := player.pos[0]
bY := player.pos[1] + EYE_HEIGHT
bZ := player.pos[2]
for stepCount := 100; stepCount > 0; stepCount-- {
if w.GetBlock(int(bX), int(bY), int(bZ)) != nil {
bX -= stepX
bY -= stepY
bZ -= stepZ
w.SetBlock(int(bX), int(bY), int(bZ), br.ByName("gold_block"))
return
} else {
bX += stepX
bY += stepY
bZ += stepZ
}
}
}

func onKey(w *glfw.Window, key glfw.Key, scancode int, action glfw.Action, mods glfw.ModifierKey) {
if key == glfw.KeyW {
if action == glfw.Release {
movementX = 0.0
} else {
movementX = 0.12
}
}
if key == glfw.KeyS {
if action == glfw.Release {
movementX = 0.0
} else {
movementX = -0.12
}
}
if key == glfw.KeyA {
if action == glfw.Release {
movementZ = 0.0
} else {
movementZ = -0.12
}
}
if key == glfw.KeyD {
if action == glfw.Release {
movementZ = 0.0
} else {
movementZ = 0.12
}
}
if key == glfw.KeySpace {
if action == glfw.Press {
player.gravity = 0.3
}
}
}

func init() {
// GLFW event handling must run on the main OS thread
runtime.LockOSThread()
}

var (
w WorldFlat
br BlockRegistry
)

func main() {
flag.Parse()

fps = NewAverage(256)
player.pos = Vec3{8, MAP_H + 16, 8}

br = NewBlockRegistry()
br.Register(&BlockSimple{name: "grass", textures: [6]string{"dirt.png", "grass.png", "grass_side.png", "grass_side.png", "grass_side.png", "grass_side.png"}})
br.Register(&BlockSimple{name: "dirt", textures: [6]string{"dirt.png", "dirt.png", "dirt.png", "dirt.png", "dirt.png", "dirt.png"}})
br.Register(&BlockSimple{name: "stone", textures: [6]string{"stone.png", "stone.png", "stone.png", "stone.png", "stone.png", "stone.png"}})
br.Register(&BlockSimple{name: "gold_block", textures: [6]string{"gold_block.png", "gold_block.png", "gold_block.png", "gold_block.png", "gold_block.png", "gold_block.png"}})

w = NewWorldFlat(br)

fmt.Printf("Loading...\n")
if err := glfw.Init(); err != nil {
log.Fatalln("failed to initialize glfw:", err)
}
defer glfw.Terminate()

glfw.WindowHint(glfw.ContextVersionMajor, 2)
glfw.WindowHint(glfw.ContextVersionMinor, 1)
window, err := glfw.CreateWindow(800, 600, "GM-M1-142, strona 12", nil, nil)
if err != nil {
panic(err)
}
window.MakeContextCurrent()
window.SetInputMode(glfw.CursorMode, glfw.CursorDisabled)
window.SetKeyCallback(onKey)
window.SetCursorPosCallback(onMove)
window.SetFramebufferSizeCallback(onResize)
window.SetMouseButtonCallback(onMouse)

//glfw.SwapInterval(0)

render.Init(800, 600, *debugtextures)
defer render.Deinit()

w.RegisterRenderListener(&render)

if *cpuprofile {
fmt.Printf("CPU profiling ON!")
f, err := os.Create("cpu.prof")
if err != nil {
log.Fatal(err)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}

for !window.ShouldClose() {
t := time.Now()
render.Render(&player, &w)
window.SwapBuffers()
glfw.PollEvents()
nanoTime := time.Since(t)
movementLX := movementX * float32(nanoTime) / (16 * 1000000)
movementLZ := movementZ * float32(nanoTime) / (16 * 1000000)
gravityLD := 0.5 * float32(nanoTime) / (1000 * 1000000)
fpsNow := float64(1000000000) / float64(nanoTime)
fps.Push(fpsNow)
//fmt.Printf("%.2f (%.2f) [%.2f %.2f %.2f]\n", fps.Get(), nanoTime, player.pos[0], player.pos[1], player.pos[2])

// player.pos[1] - player.pitch * movementLD,
// fmath.Cos(player.pitch)

// fall
if w.GetBlock(int(player.pos[0]), int(fmath.Ceil(player.pos[1])) - 1, int(player.pos[2])) == nil {
player.gravity -= gravityLD
} else if player.gravity < 0 {
player.gravity = 0
}

if player.gravity < 0 {
bY := player.pos[1]
for w.GetBlock(int(player.pos[0]), int(fmath.Ceil(player.pos[1])), int(player.pos[2])) == nil && player.pos[1] > (bY + player.gravity) {
player.pos[1] -= 0.0025
}
} else if player.gravity > 0 {
bY := player.pos[1]
for w.GetBlock(int(player.pos[0]), int(fmath.Ceil(player.pos[1])), int(player.pos[2])) == nil && player.pos[1] < (bY + player.gravity) {
player.pos[1] += 0.0025
}
}

nx := player.pos[0] - fmath.Sin(-player.yaw) * movementLX + fmath.Cos(-player.yaw) * movementLZ
nz := player.pos[2] - fmath.Cos(-player.yaw) * movementLX - fmath.Sin(-player.yaw) * movementLZ

if w.GetBlock(int(nx), int(fmath.Ceil(player.pos[1])), int(nz)) == nil {
player.pos[0] = nx
player.pos[2] = nz
}
}

if *heapprofile {
f, err := os.Create("heap.prof")
if err != nil {
log.Fatal(err)
}
pprof.WriteHeapProfile(f)
f.Close()
}
}

+ 162
- 0
model.go View File

@@ -0,0 +1,162 @@
package main

import (
"github.com/go-gl/gl/v2.1/gl"
)

type Texture struct {
binding uint32
minU float32
maxU float32
minV float32
maxV float32
}

type Vertex struct {
coord Vec3
texcoord Vec2
color Vec3
}

type Quad struct {
v [4]Vertex
normal Vec3
}

type Model struct {
faceQuads [6][]Quad
quads []Quad
dlist uint32
hasDlist bool
}

func getQuadDirection(q *Quad) Direction {
m := []int{2, 3, 0, 1, 4, 5}
var d Direction
var occ int
for i := 0; i < 3; i++ {
j := q.v[0].coord[i]
if (j == 0 || j == 1) && j == q.v[1].coord[i] && q.v[1].coord[i] == q.v[2].coord[i] && q.v[2].coord[i] == q.v[3].coord[i] {
occ++
d = Direction(m[i * 2 + int(j)])
}
}
if occ == 1 {
return d
} else {
return UNKNOWN
}
}

func (m *Model) freeDlist() {
if m.hasDlist {
gl.DeleteBuffers(1, &m.dlist)
m.hasDlist = false
}
}

func (m *Model) AddQuad(q Quad) {
m.freeDlist()
d := getQuadDirection(&q)
if d == UNKNOWN {
m.quads = append(m.quads, q)
} else {
m.faceQuads[int(d)] = append(m.faceQuads[int(d)], q)
}
}

func (q *Quad) render() {
gl.Normal3f(q.normal[0], q.normal[1], q.normal[2])
for i := 0; i < 4; i++ {
gl.Vertex3f(q.v[i].coord[0], q.v[i].coord[1], q.v[i].coord[2])
gl.TexCoord2f(q.v[i].texcoord[0], q.v[i].texcoord[1])
gl.Color3f(q.v[i].color[0], q.v[i].color[1], q.v[i].color[2])
}
}

func (m *Model) Render() {
if m.hasDlist {
gl.CallList(m.dlist)
} else {
gl.NewList(m.dlist, gl.COMPILE_AND_EXECUTE)
gl.Begin(gl.QUADS)
for i := 0; i < 6; i++ {
for _, quad := range m.faceQuads[i] {
quad.render()
}
}
for _, quad := range m.quads {
quad.render()
}
gl.End()
gl.EndList()
}
}

func NewCubeModel(ta [6]Texture) Model {
m := Model{}
q := Quad{}
c := Vec3{1, 1, 1}

t := ta[5]
q.normal = Vec3{0, 0, 1}
q.v = [4]Vertex{
Vertex{coord: Vec3{0, 1, 1}, texcoord: Vec2{t.minU, t.minV}, color: c},
Vertex{coord: Vec3{1, 1, 1}, texcoord: Vec2{t.maxU, t.minV}, color: c},
Vertex{coord: Vec3{1, 0, 1}, texcoord: Vec2{t.maxU, t.maxV}, color: c},
Vertex{coord: Vec3{0, 0, 1}, texcoord: Vec2{t.minU, t.maxV}, color: c},
}
m.AddQuad(q)

t = ta[4]
q.normal = Vec3{0, 0, -1}
q.v = [4]Vertex{
Vertex{coord: Vec3{0, 1, 0}, texcoord: Vec2{t.minU, t.minV}, color: c},
Vertex{coord: Vec3{1, 1, 0}, texcoord: Vec2{t.maxU, t.minV}, color: c},
Vertex{coord: Vec3{1, 0, 0}, texcoord: Vec2{t.maxU, t.maxV}, color: c},
Vertex{coord: Vec3{0, 0, 0}, texcoord: Vec2{t.minU, t.maxV}, color: c},
}
m.AddQuad(q)

t = ta[1]
q.normal = Vec3{0, 0, 1}
q.v = [4]Vertex{
Vertex{coord: Vec3{0, 1, 0}, texcoord: Vec2{t.minU, t.minV}, color: c},
Vertex{coord: Vec3{1, 1, 0}, texcoord: Vec2{t.maxU, t.minV}, color: c},
Vertex{coord: Vec3{1, 1, 1}, texcoord: Vec2{t.maxU, t.maxV}, color: c},
Vertex{coord: Vec3{0, 1, 1}, texcoord: Vec2{t.minU, t.maxV}, color: c},
}
m.AddQuad(q)

t = ta[0]
q.normal = Vec3{0, 0, -1}
q.v = [4]Vertex{
Vertex{coord: Vec3{0, 0, 0}, texcoord: Vec2{t.minU, t.minV}, color: c},
Vertex{coord: Vec3{1, 0, 0}, texcoord: Vec2{t.maxU, t.minV}, color: c},
Vertex{coord: Vec3{1, 0, 1}, texcoord: Vec2{t.maxU, t.maxV}, color: c},
Vertex{coord: Vec3{0, 0, 1}, texcoord: Vec2{t.minU, t.maxV}, color: c},
}
m.AddQuad(q)

t = ta[3]
q.normal = Vec3{1, 0, 0}
q.v = [4]Vertex{
Vertex{coord: Vec3{1, 1, 0}, texcoord: Vec2{t.minU, t.minV}, color: c},
Vertex{coord: Vec3{1, 1, 1}, texcoord: Vec2{t.maxU, t.minV}, color: c},
Vertex{coord: Vec3{1, 0, 1}, texcoord: Vec2{t.maxU, t.maxV}, color: c},
Vertex{coord: Vec3{1, 0, 0}, texcoord: Vec2{t.minU, t.maxV}, color: c},
}
m.AddQuad(q)

t = ta[2]
q.normal = Vec3{-1, 0, 0}
q.v = [4]Vertex{
Vertex{coord: Vec3{0, 1, 0}, texcoord: Vec2{t.minU, t.minV}, color: c},
Vertex{coord: Vec3{0, 1, 1}, texcoord: Vec2{t.maxU, t.minV}, color: c},
Vertex{coord: Vec3{0, 0, 1}, texcoord: Vec2{t.maxU, t.maxV}, color: c},
Vertex{coord: Vec3{0, 0, 0}, texcoord: Vec2{t.minU, t.maxV}, color: c},
}
m.AddQuad(q)

return m
}

+ 450
- 0
render.go View File

@@ -0,0 +1,450 @@
package main

import (
"fmt"
"image"
"image/draw"
"image/png"
_ "image/png"
"io/ioutil"
// "math"
"os"
// "runtime"

"github.com/go-gl/gl/v2.1/gl"
)

const Z_OFFSET = 1 / 64
const VIEW_DISTANCE = 5
const EYE_HEIGHT = 1.7

type Render struct {
textures map[string]Texture
blockSheet uint32
buffers map[Position]*VertexBuffer
toRefresh chan VertexRefreshRequest
}

type VertexRefreshRequest struct {
pos Position
vbo *VertexBuffer
}

type VertexBuffer struct {
world World
data []float32
count int32
vbo uint32
vboCount int32
vboInit bool
refreshReady bool
}

var playerLocal *Player

func (v *VertexBuffer) Append(q Quad) {
for i := 0; i < 4; i++ {
v.data = append(v.data, q.v[i].coord[0], q.v[i].coord[1], q.v[i].coord[2],
q.normal[0], q.normal[1], q.normal[2],
q.v[i].color[0], q.v[i].color[1], q.v[i].color[2],
q.v[i].texcoord[0], q.v[i].texcoord[1])
}
v.count += 4
}

func (v *VertexBuffer) AppendFancy(q *Quad, coordOffset Vec3, texCoordOffset Vec2, lightLevel float32) {
for i := 0; i < 4; i++ {
v.data = append(v.data, q.v[i].coord[0] + coordOffset[0], q.v[i].coord[1] + coordOffset[1], q.v[i].coord[2] + coordOffset[2],
q.normal[0], q.normal[1], q.normal[2],
q.v[i].color[0] * lightLevel, q.v[i].color[1] * lightLevel, q.v[i].color[2] * lightLevel,
q.v[i].texcoord[0] + texCoordOffset[0], q.v[i].texcoord[1] + texCoordOffset[1])
}
v.count += 4
}

func (v *VertexBuffer) Deinit() {
gl.DeleteBuffers(1, &v.vbo)
v.data = nil
}

func (v *VertexBuffer) Reset() {
v.data = nil
v.count = 0
}

func (v *VertexBuffer) Draw() {
if v.count > 0 {
if !v.vboInit {
gl.GenBuffers(1, &v.vbo)
v.vboInit = true
}
gl.BindBuffer(gl.ARRAY_BUFFER, v.vbo)

if (v.refreshReady) {
gl.BufferData(gl.ARRAY_BUFFER, 4*len(v.data), gl.Ptr(v.data), gl.STATIC_DRAW)
v.vboCount = v.count
v.data = nil
v.refreshReady = false
}

gl.VertexPointer(3, gl.FLOAT, 44, gl.PtrOffset(0))
gl.TexCoordPointer(2, gl.FLOAT, 44, gl.PtrOffset(9*4))
gl.NormalPointer(gl.FLOAT, 44, gl.PtrOffset(3*4))
gl.ColorPointer(3, gl.FLOAT, 44, gl.PtrOffset(6*4))
gl.DrawArrays(gl.QUADS, 0, v.vboCount)
}
}

func nearestPow2(n int) int {
t := uint32(n - 1)
t |= t >> 1
t |= t >> 2
t |= t >> 4
t |= t >> 8
t |= t >> 16
return int(t + 1)
}

func chunkRefreshLoop(r *Render) {
a := 0
for a == 0 {
vbo := <- r.toRefresh
vbo.vbo.Reset()
renderChunk(r, vbo.pos, vbo.vbo)
vbo.vbo.refreshReady = true
}
}

func (r *Render) Init(width int32, height int32, debugtextures bool) {
if err := gl.Init(); err != nil {
panic(err)
}

r.toRefresh = make(chan VertexRefreshRequest, 64)
go chunkRefreshLoop(r)
go chunkRefreshLoop(r)

r.buffers = make(map[Position]*VertexBuffer, 1000)
r.textures = make(map[string]Texture)
r.initTextures(debugtextures)
setupScene()
r.Resize(width, height)
}

func (r *Render) initTextures(debugtextures bool) {
// TODO: not assume that all textures are going to be 16x16
files, _ := ioutil.ReadDir("./textures/")
textures := make(map[string]image.Image, 256)
for _, texfn := range files {
if texfn.IsDir() {
continue
}
imgFile, err := os.Open("./textures/" + texfn.Name())
if err == nil {
img, _, err := image.Decode(imgFile)
if err == nil {
textures[texfn.Name()] = img
}
imgFile.Close()
}
}

countSide := 1
for countSide*countSide < len(textures) {
countSide *= 2
}
rgba := image.NewRGBA(image.Rectangle{image.ZP, image.Pt(countSide << 4, countSide << 4)})
pos := 0

fmt.Printf("Initialized texture of size %d x %d\n", countSide << 4, countSide << 4)

gl.Enable(gl.TEXTURE_2D)
gl.GenTextures(1, &r.blockSheet)

for name, img := range textures {
pX := (pos % countSide)
pY := (pos / countSide)
dp := image.Pt(pX << 4, pY << 4)
pos++

draw.Draw(rgba, image.Rectangle{dp, dp.Add(image.Pt(16, 16))}, img, image.ZP, draw.Src)

fmt.Printf("Loaded texture %s @ %d, %d\n", name, pX, pY)
r.textures[name] = Texture{
binding: r.blockSheet,
minU: float32(pX) / float32(countSide),
maxU: float32(pX + 1) / float32(countSide),
minV: float32(pY) / float32(countSide),
maxV: float32(pY + 1) / float32(countSide),
}
}

if debugtextures {
tmpFile, _ := os.Create("./blockSheet.png")
png.Encode(tmpFile, rgba)
tmpFile.Close()
}

gl.BindTexture(gl.TEXTURE_2D, r.blockSheet)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
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(rgba.Rect.Size().X),
int32(rgba.Rect.Size().Y),
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
gl.Ptr(rgba.Pix))
}

func (r *Render) markForUpdate(p Position) {
buf, exists := r.buffers[p]
if exists {
r.toRefresh <- VertexRefreshRequest{pos: p, vbo: buf}
}
}

func (r *Render) OnRenderUpdate(x int, y int, z int) {
cx := x >> 4
cy := y >> 4
cz := z >> 4
r.markForUpdate(Position{cx, cy, cz})
if x & 15 == 0 {
r.markForUpdate(Position{cx - 1, cy, cz})
} else if x & 15 == 15 {
r.markForUpdate(Position{cx + 1, cy, cz})
}
if y & 15 == 0 {
r.markForUpdate(Position{cx, cy - 1, cz})
} else if y & 15 == 15 {
r.markForUpdate(Position{cx, cy + 1, cz})
}
if z & 15 == 0 {
r.markForUpdate(Position{cx, cy, cz - 1})
} else if z & 15 == 15 {
r.markForUpdate(Position{cx, cy, cz + 1})
}
}

func (r *Render) Deinit() {
gl.DeleteTextures(1, &r.blockSheet)
}

func intMax(a int, b int) int {
if a > b { return a } else { return b }
}

func isUsefulChunk(player *Player, p Position) bool {
dist := intMax(intMax(p.x * 16 - int(player.pos[0]), p.y * 16 - int(player.pos[1])), p.z * 16 - int(player.pos[2]))
return dist <= VIEW_DISTANCE*16
}

func dynamicChunkRender(r *Render, w World, p Position, vbo *VertexBuffer) {
}

func (r *Render) drawBlockVBOs(player *Player, w World) {
gl.Enable(gl.TEXTURE_2D)
gl.BindTexture(gl.TEXTURE_2D, r.blockSheet)

pcx := int(player.pos[0]) >> 4
pcy := int(player.pos[1]) >> 4
pcz := int(player.pos[2]) >> 4

// remove unused VBOs
for pos, buf := range r.buffers {
if !isUsefulChunk(player, pos) {
buf.Deinit()
delete(r.buffers, pos)
}
}

gl.EnableClientState(gl.VERTEX_ARRAY)
gl.EnableClientState(gl.TEXTURE_COORD_ARRAY)
gl.EnableClientState(gl.NORMAL_ARRAY)
gl.EnableClientState(gl.COLOR_ARRAY)

// add necessary VBOs and render
for y := -VIEW_DISTANCE; y <= VIEW_DISTANCE; y++ {
for z := -VIEW_DISTANCE; z <= VIEW_DISTANCE; z++ {
for x := -VIEW_DISTANCE; x <= VIEW_DISTANCE; x++ {
pos := Position{x: pcx + x, y: pcy + y, z: pcz + z}
if !w.IsLoaded(pos.x << 4, pos.y << 4, pos.z << 4) {
continue
}

if _, exists := r.buffers[pos]; !exists {
v := VertexBuffer{world: w}
r.buffers[pos] = &v
r.toRefresh <- VertexRefreshRequest{pos: pos, vbo: r.buffers[pos]}
} else {
r.buffers[pos].Draw()
}
}
}
}

gl.DisableClientState(gl.VERTEX_ARRAY)
gl.DisableClientState(gl.TEXTURE_COORD_ARRAY)
gl.DisableClientState(gl.NORMAL_ARRAY)
gl.DisableClientState(gl.COLOR_ARRAY)
}

func (r *Render) drawBlockHighlight(pos Position) {
xMin := float32(pos.x) - Z_OFFSET
xMax := float32(pos.x + 1) + Z_OFFSET
yMin := float32(pos.y) - Z_OFFSET
yMax := float32(pos.y + 1) + Z_OFFSET
zMin := float32(pos.z) - Z_OFFSET
zMax := float32(pos.z + 1) + Z_OFFSET

gl.Disable(gl.TEXTURE_2D)
gl.Enable(gl.LINE_SMOOTH)
gl.PolygonMode(gl.FRONT_AND_BACK, gl.LINE)
gl.LineWidth(2)
gl.Begin(gl.QUADS)
gl.Vertex3f(xMin, yMin, zMin)
gl.Vertex3f(xMax, yMin, zMin)
gl.Vertex3f(xMax, yMin, zMax)
gl.Vertex3f(xMin, yMin, zMax)
gl.Vertex3f(xMin, yMax, zMin)
gl.Vertex3f(xMax, yMax, zMin)
gl.Vertex3f(xMax, yMax, zMax)
gl.Vertex3f(xMin, yMax, zMax)

gl.Vertex3f(xMin, yMin, zMin)
gl.Vertex3f(xMin, yMax, zMin)
gl.Vertex3f(xMin, yMax, zMax)
gl.Vertex3f(xMin, yMin, zMax)
gl.Vertex3f(xMax, yMin, zMin)
gl.Vertex3f(xMax, yMax, zMin)
gl.Vertex3f(xMax, yMax, zMax)
gl.Vertex3f(xMax, yMin, zMax)

gl.Vertex3f(xMin, yMin, zMin)
gl.Vertex3f(xMin, yMax, zMin)
gl.Vertex3f(xMax, yMax, zMin)
gl.Vertex3f(xMax, yMin, zMin)
gl.Vertex3f(xMin, yMin, zMax)
gl.Vertex3f(xMin, yMax, zMax)
gl.Vertex3f(xMax, yMax, zMax)
gl.Vertex3f(xMax, yMin, zMax)
gl.End()
gl.PolygonMode(gl.FRONT_AND_BACK, gl.FILL)
}

func (r *Render) Render(player *Player, w World) {
// --- INIT ---
playerLocal = player

gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
gl.MatrixMode(gl.MODELVIEW)
gl.LoadIdentity()
gl.Color4f(1, 1, 1, 1)

// --- BLOCK AREA ---
gl.PushMatrix()
gl.Rotatef(player.pitch/DEG_RAD, 1, 0, 0)
gl.Rotatef(player.yaw/DEG_RAD, 0, 1, 0)
gl.Translatef(-player.pos[0], -player.pos[1]-EYE_HEIGHT, -player.pos[2])
r.drawBlockVBOs(player, w)

// draw block wireframe
if pos, exists := player.GetHoverCoords(w); exists {
r.drawBlockHighlight(pos)
}
gl.PopMatrix()

// --- CLEANUP ---
}

func (r *Render) Resize(width int32, height int32) {
ratio := float64(height) / float64(width) * 0.01

gl.Viewport(0, 0, width, height)
gl.MatrixMode(gl.PROJECTION)
gl.LoadIdentity()
if ratio > 1.0 {
gl.Frustum(-(0.01 / ratio), (0.01 / ratio), -1, 1, 0.01, 512)
} else {
gl.Frustum(-0.01, 0.01, -ratio, ratio, 0.01, 512)
}
gl.MatrixMode(gl.MODELVIEW)
gl.LoadIdentity()
}

func setupScene() {
gl.Enable(gl.ALPHA_TEST)
gl.Enable(gl.DEPTH_TEST)
gl.Disable(gl.LIGHTING)

gl.ClearColor(0.4, 0.6, 0.8, 0)
gl.ClearDepth(1)
gl.DepthFunc(gl.LEQUAL)

gl.Fogi(gl.FOG_MODE, gl.LINEAR)
gl.Fogf(gl.FOG_START, (VIEW_DISTANCE * 16) - 32)
gl.Fogf(gl.FOG_END, (VIEW_DISTANCE * 16) - 8)
fogcol := []float32{0.4, 0.6, 0.8, 1}
gl.Fogfv(gl.FOG_COLOR, &fogcol[0])
gl.Enable(gl.FOG)

ambient := []float32{1, 1, 1, 1}
gl.LightModelfv(gl.LIGHT_MODEL_AMBIENT, &ambient[0])
}

func isSolidSide(w World, x int, y int, z int, side Direction) bool {
b := w.GetBlock(x, y, z)
if b != nil {
return b.IsSideSolid(side)
} else {
return false
}
}

func renderQuad(vbo *VertexBuffer, x int, y int, z int, q *Quad, d Direction) {
lightLevelScaler := []float32{0.55, 1.0, 0.85, 0.85, 0.7, 0.7}
vbo.AppendFancy(q, Vec3{float32(x), float32(y), float32(z)}, Vec2{}, lightLevelScaler[int(d)])
}

func renderModel(vbo *VertexBuffer, x int, y int, z int, m Model) {
for i := 0; i < 6; i++ {
xOff := 0
yOff := 0
zOff := 0
if i == 0 { yOff = -1 }
if i == 1 { yOff = 1 }
if i == 2 { xOff = -1 }
if i == 3 { xOff = 1 }
if i == 4 { zOff = -1 }
if i == 5 { zOff = 1 }
if !isSolidSide(vbo.world, x+xOff, y+yOff, z+zOff, Direction(i ^ 1)) {
for _, quad := range m.faceQuads[i] {
renderQuad(vbo, x, y, z, &quad, Direction(i))
}
}
}
for _, quad := range m.quads {
renderQuad(vbo, x, y, z, &quad, UNKNOWN)
}
}

func renderChunk(r *Render, p Position, vbo *VertexBuffer) {
for y := 0; y < 16; y++ {
py := p.y << 4 + y
for z := 0; z < 16; z++ {
pz := p.z << 4 + z
for x := 0; x < 16; x++ {
px := p.x << 4 + x
block := vbo.world.GetBlock(px, py, pz)
if block != nil {
renderModel(vbo, px, py, pz, block.GetModel(r))
}
}
}
}
}

+ 23
- 0
types.go View File

@@ -0,0 +1,23 @@
package main

type Direction int

const (
DOWN Direction = iota
UP
LEFT
RIGHT
BACK
FORWARD
UNKNOWN
)

type Named interface {
Name() string
}

type Position struct {
x int
y int
z int
}

+ 39
- 0
vector.go View File

@@ -0,0 +1,39 @@
package main

import (
"github.com/barnex/fmath"
)

type Vec2 [2]float32
type Vec3 [3]float32

func (v Vec3) Translate(v2 Vec3) Vec3 {
return Vec3{v[0] + v2[0], v[1] + v2[1], v[2] + v2[2]}
}

func (v Vec3) Scale(t float32) Vec3 {
return Vec3{v[0] * t, v[1] * t, v[2] * t}
}

func (v Vec2) Translate(v2 Vec2) Vec2 {
return Vec2{v[0] + v2[0], v[1] + v2[1]}
}

func (v Vec2) Scale(t float32) Vec2 {
return Vec2{v[0] * t, v[1] * t}
}

type BoundingBox struct {
min Vec3
max Vec3
}

func (b BoundingBox) Intersects(b2 BoundingBox) bool {
return (fmath.Abs(b.min[0] - b2.min[0]) * 2 < (b.max[0] - b.min[0] + b2.max[0] - b2.min[0]) &&
fmath.Abs(b.min[1] - b2.min[1]) * 2 < (b.max[1] - b.min[1] + b2.max[1] - b2.min[1]) &&
fmath.Abs(b.min[2] - b2.min[2]) * 2 < (b.max[2] - b.min[2] + b2.max[2] - b2.min[2]))
}

func (b BoundingBox) Translate(v Vec3) BoundingBox {
return BoundingBox{b.min.Translate(v), b.max.Translate(v)}
}

+ 111
- 0
world.go View File

@@ -0,0 +1,111 @@
package main

import (
"math/rand"
"github.com/larspensjo/Go-simplex-noise/simplexnoise"
)

const (
MAP_W = 256
MAP_H = 128
MAP_D = 256
)

type WorldFlat struct {
blocks []int16
blockReg BlockRegistry
renderListeners []RenderListener
}

type World interface {
IsValid(int, int, int) bool
IsLoaded(int, int, int) bool
GetBlock(int, int, int) Block
SetBlock(int, int, int, Block)
}

type BlockAccess interface {
GetBlock(int, int, int) Block
SetBlock(int, int, int, Block)
}

type RenderListener interface {
OnRenderUpdate(int, int, int)
}

func NewWorldFlat(blockReg BlockRegistry) WorldFlat {
w := WorldFlat{
blocks: make([]int16, MAP_W*MAP_H*MAP_D),
blockReg: blockReg,
}

seed := float64(rand.Intn(20000000))
arr := [6]float64{0.004, 0.008, 0.016, 0.032, 0.064, 0.0128}
arr2 := [6]float64{32, 16, 8, 4, 2, 1}
for z := 0; z < MAP_D; z++ {
for x := 0; x < MAP_W; x++ {
heightF := float64(MAP_H / 2)
for i := 0; i < len(arr); i++ {
heightF += simplexnoise.Noise3(float64(x) * arr[i], float64(z) * arr[i], seed + float64(i * 1000000)) * arr2[i]
}
height := int(heightF)
t := blockReg.ByName("stone")
for h := 0; h < MAP_H; h++ {
if h == (height - 3) {
t = blockReg.ByName("dirt")
} else if h == height {
t = blockReg.ByName("grass")
} else if h > height {
t = nil
}
if t != nil {
w.SetBlock(x, h, z, t.New())
}
}
}
}

return w
}

func pos(x int, y int, z int) int {
return (y*MAP_D + z)*MAP_W + x
}

func (w *WorldFlat) IsLoaded(x int, y int, z int) bool {
return w.IsValid(x, y, z)
}

func (w *WorldFlat) IsValid(x int, y int, z int) bool {
return x >= 0 && y >= 0 && z >= 0 && x < MAP_W && y < MAP_H && z < MAP_D
}

func (w *WorldFlat) GetBlock(x int, y int, z int) Block {
if w.IsValid(x,y,z) {
return w.blockReg.ByID(int(w.blocks[pos(x,y,z)]))
} else {
return nil
}
}

func (w *WorldFlat) RegisterRenderListener(r RenderListener) {
if w.renderListeners == nil {
w.renderListeners = make([]RenderListener, 1, 1)
}
w.renderListeners = append(w.renderListeners, r)
}

func (w *WorldFlat) SetBlock(x int, y int, z int, block Block) {
if w.IsValid(x,y,z) {
if block == nil {
w.blocks[pos(x,y,z)] = 0
} else {
w.blocks[pos(x,y,z)] = int16(w.blockReg.GetID(block))
}
for _, listener := range w.renderListeners {
if listener != nil {
listener.OnRenderUpdate(x, y, z)
}
}
}
}

Loading…
Cancel
Save