Initial commit

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

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