gl01/internal/world/block.go
2022-01-28 15:24:03 +08:00

181 lines
6.2 KiB
Go

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,
world *World,
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, world *World) 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, world *World) 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, world *World) BlockAppearance {
return b.app
}
func (blockBehaviourStatic) BlockUpdate(position itype.Vec3i, aux int, data itype.Dataset, world *World) 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, 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, world *World) 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, world)
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
World *World
}
// 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, b.World)
}
// 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, b.World)
}