gl01/internal/world/block.go
2022-02-24 20:20:33 +08:00

213 lines
7.1 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, i.e., does not block nearby blocks in rendering?
NotSolid bool // Is block not solid, i.e., has no hitbox at all? (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
Lookbox []itype.Boxd // Selection hitbox, hit only by the view ray; empty means Hitbox[]
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,
vertexArrayWater []Vertex,
) (verts []Vertex, waters []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
// Break is called when the block is broken by natural means.
Break(position itype.Vec3i, aux int, data itype.Dataset, world *World)
}
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
}
func (blockBehaviourStatic) Break(position itype.Vec3i, aux int, data itype.Dataset, world *World) {}
// 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 len(app.Hitbox) == 0 {
app.Hitbox = []itype.Boxd{{
OffX: 0, OffY: 0, OffZ: 0,
SizeX: 1, SizeY: 1, SizeZ: 1,
}}
}
if len(app.Lookbox) == 0 {
app.Lookbox = app.Hitbox
}
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 len(app.Hitbox) == 0 {
app.Hitbox = []itype.Boxd{{
OffX: 0, OffY: 0, OffZ: 0,
SizeX: 1, SizeY: 1, SizeZ: 1,
}}
}
if len(app.Lookbox) == 0 {
app.Lookbox = app.Hitbox
}
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 {
if b.Behaviour == nil {
return BlockAppearance{}
}
app := b.Behaviour.Appearance(position, b.Aux, b.Dataset, b.World)
if !app.NotSolid && len(app.Hitbox) == 0 {
app.Hitbox = []itype.Boxd{{
OffX: 0, OffY: 0, OffZ: 0,
SizeX: 1, SizeY: 1, SizeZ: 1,
}}
}
if len(app.Lookbox) == 0 {
if len(app.Hitbox) == 0 {
app.Lookbox = []itype.Boxd{{
OffX: 0, OffY: 0, OffZ: 0,
SizeX: 1, SizeY: 1, SizeZ: 1,
}}
} else {
app.Lookbox = app.Hitbox
}
}
return app
}
// 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)
}