Arbitrary dimension chunks, null/not null cell distinction
This commit is contained in:
parent
fb44d77353
commit
94fe3662ad
@ -13,6 +13,7 @@ import ru.dbotthepony.kstarbound.player.QuestDescriptor
|
|||||||
import ru.dbotthepony.kstarbound.player.QuestInstance
|
import ru.dbotthepony.kstarbound.player.QuestInstance
|
||||||
import ru.dbotthepony.kstarbound.util.JVMTimeSource
|
import ru.dbotthepony.kstarbound.util.JVMTimeSource
|
||||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||||
|
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||||
import ru.dbotthepony.kstarbound.world.entities.ItemEntity
|
import ru.dbotthepony.kstarbound.world.entities.ItemEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
|
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
|
||||||
import ru.dbotthepony.kvector.vector.Vector2d
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
@ -74,7 +75,6 @@ fun main() {
|
|||||||
find += System.currentTimeMillis() - t
|
find += System.currentTimeMillis() - t
|
||||||
|
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
val chunk = client.world!!.chunkMap.computeIfAbsent(ChunkPos(chunkX, chunkY))
|
|
||||||
val inflater = Inflater()
|
val inflater = Inflater()
|
||||||
inflater.setInput(data)
|
inflater.setInput(data)
|
||||||
|
|
||||||
@ -90,7 +90,13 @@ fun main() {
|
|||||||
|
|
||||||
for (y in 0 .. 31) {
|
for (y in 0 .. 31) {
|
||||||
for (x in 0 .. 31) {
|
for (x in 0 .. 31) {
|
||||||
chunk.getCell(x, y).read(starbound.tilesByID::get, starbound.tileModifiersByID::get, starbound.liquidByID::get, reader)
|
val cell = client.world!!.chunkMap.getCellDirect(chunkX * 32 + x, chunkY * 32 + y)
|
||||||
|
|
||||||
|
if (cell == null) {
|
||||||
|
IChunkCell.skip(reader)
|
||||||
|
} else {
|
||||||
|
cell.read(starbound.tilesByID::get, starbound.tileModifiersByID::get, starbound.liquidByID::get, reader)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,14 +154,13 @@ fun main() {
|
|||||||
|
|
||||||
//ent.position += Vector2d(y = 14.0, x = -10.0)
|
//ent.position += Vector2d(y = 14.0, x = -10.0)
|
||||||
ent.position = Vector2d(600.0 + 16.0, 721.0 + 48.0)
|
ent.position = Vector2d(600.0 + 16.0, 721.0 + 48.0)
|
||||||
client.camera.pos = Vector2f(-2967f, 685f)
|
client.camera.pos = Vector2f(238f, 685f)
|
||||||
|
|
||||||
client.onDrawGUI {
|
client.onDrawGUI {
|
||||||
client.gl.font.render("${ent.position}", y = 100f, scale = 0.25f)
|
client.gl.font.render("${ent.position}", y = 100f, scale = 0.25f)
|
||||||
client.gl.font.render("${ent.movement.velocity}", y = 120f, scale = 0.25f)
|
client.gl.font.render("${ent.movement.velocity}", y = 120f, scale = 0.25f)
|
||||||
client.gl.font.render("${client.camera.pos} ${client.settings.zoom}", y = 140f, scale = 0.25f)
|
client.gl.font.render("Camera: ${client.camera.pos} ${client.settings.zoom}", y = 140f, scale = 0.25f)
|
||||||
client.gl.font.render("Camera: ${ChunkPos.fromPosition(client.camera.pos.toDoubleVector())}", y = 160f, scale = 0.25f)
|
client.gl.font.render("World chunk: ${client.world!!.chunkMap.cellToChunk(client.camera.pos.toDoubleVector())}", y = 160f, scale = 0.25f)
|
||||||
client.gl.font.render("World: ${client.world!!.chunkMap.cellToGrid(client.camera.pos.toDoubleVector())}", y = 180f, scale = 0.25f)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
client.onPreDrawWorld {
|
client.onPreDrawWorld {
|
||||||
|
@ -8,14 +8,12 @@ import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
|||||||
import ru.dbotthepony.kstarbound.client.render.TileLayerList
|
import ru.dbotthepony.kstarbound.client.render.TileLayerList
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||||
import ru.dbotthepony.kstarbound.world.*
|
import ru.dbotthepony.kstarbound.world.*
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE
|
import ru.dbotthepony.kstarbound.world.CHUNK_SIZEd
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEd
|
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEf
|
|
||||||
import ru.dbotthepony.kstarbound.world.api.ITileAccess
|
import ru.dbotthepony.kstarbound.world.api.ITileAccess
|
||||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||||
import ru.dbotthepony.kvector.arrays.Matrix4fStack
|
|
||||||
import ru.dbotthepony.kvector.vector.Vector2d
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
import ru.dbotthepony.kvector.vector.Vector2f
|
import ru.dbotthepony.kvector.vector.Vector2f
|
||||||
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
|
|
||||||
@ -28,13 +26,13 @@ import java.util.LinkedList
|
|||||||
const val Z_LEVEL_BACKGROUND = 60000
|
const val Z_LEVEL_BACKGROUND = 60000
|
||||||
const val Z_LEVEL_LIQUID = 10000
|
const val Z_LEVEL_LIQUID = 10000
|
||||||
|
|
||||||
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos), Closeable {
|
class ClientChunk(world: ClientWorld, pos: ChunkPos, width: Int, height: Int) : Chunk<ClientWorld, ClientChunk>(world, pos, width, height), Closeable {
|
||||||
val state: GLStateTracker get() = world.client.gl
|
val state: GLStateTracker get() = world.client.gl
|
||||||
|
|
||||||
private inner class TileLayerRenderer(private val view: ITileAccess, private val isBackground: Boolean) : AutoCloseable {
|
private inner class TileLayerRenderer(private val view: ITileAccess, private val isBackground: Boolean) : AutoCloseable {
|
||||||
private val layers = TileLayerList()
|
private val layers = TileLayerList()
|
||||||
val bakedMeshes = LinkedList<Pair<ConfiguredStaticMesh, Int>>()
|
val bakedMeshes = LinkedList<Pair<ConfiguredStaticMesh, Int>>()
|
||||||
var isDirty = true
|
var isDirty = false
|
||||||
|
|
||||||
fun bake() {
|
fun bake() {
|
||||||
if (!isDirty) return
|
if (!isDirty) return
|
||||||
@ -50,19 +48,20 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
|||||||
|
|
||||||
layers.clear()
|
layers.clear()
|
||||||
|
|
||||||
for ((pos, tile) in view.iterateTiles()) {
|
for (x in 0 until width) {
|
||||||
if (!world.chunkMap.inBounds(this@ClientChunk.pos.tile + pos)) continue
|
for (y in 0 until height) {
|
||||||
|
val tile = view.getTile(x, y) ?: continue
|
||||||
|
val material = tile.material
|
||||||
|
|
||||||
val material = tile.material
|
if (material != null) {
|
||||||
|
world.client.tileRenderers.getTileRenderer(material.materialName).tesselate(tile, view, layers, Vector2i(x, y), background = isBackground)
|
||||||
|
}
|
||||||
|
|
||||||
if (material != null) {
|
val modifier = tile.modifier
|
||||||
world.client.tileRenderers.getTileRenderer(material.materialName).tesselate(tile, view, layers, pos, background = isBackground)
|
|
||||||
}
|
|
||||||
|
|
||||||
val modifier = tile.modifier
|
if (modifier != null) {
|
||||||
|
world.client.tileRenderers.getModifierRenderer(modifier.modName).tesselate(tile, view, layers, Vector2i(x, y), background = isBackground, isModifier = true)
|
||||||
if (modifier != null) {
|
}
|
||||||
world.client.tileRenderers.getModifierRenderer(modifier.modName).tesselate(tile, view, layers, pos, background = isBackground, isModifier = true)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,8 +146,8 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
|||||||
liquidTypes.clear()
|
liquidTypes.clear()
|
||||||
liquidTypesVer = liquidChangeset
|
liquidTypesVer = liquidChangeset
|
||||||
|
|
||||||
for (x in 0 until CHUNK_SIZE) {
|
for (x in 0 until width) {
|
||||||
for (y in 0 until CHUNK_SIZE) {
|
for (y in 0 until height) {
|
||||||
getCell(x, y).liquid.def?.let { liquidTypes.add(it) }
|
getCell(x, y).liquid.def?.let { liquidTypes.add(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -157,7 +156,7 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
|||||||
return liquidTypes
|
return liquidTypes
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class Renderer(val renderOrigin: Vector2f = pos.tile.toFloatVector()) {
|
inner class Renderer(val renderOrigin: Vector2f) {
|
||||||
fun addLayers(layers: LayeredRenderer) {
|
fun addLayers(layers: LayeredRenderer) {
|
||||||
for ((baked, zLevel) in backgroundRenderer.bakedMeshes) {
|
for ((baked, zLevel) in backgroundRenderer.bakedMeshes) {
|
||||||
layers.add(zLevel + Z_LEVEL_BACKGROUND) {
|
layers.add(zLevel + Z_LEVEL_BACKGROUND) {
|
||||||
@ -200,8 +199,8 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
|||||||
for (type in types) {
|
for (type in types) {
|
||||||
builder.builder.begin()
|
builder.builder.begin()
|
||||||
|
|
||||||
for (x in 0 until CHUNK_SIZE) {
|
for (x in 0 until width) {
|
||||||
for (y in 0 until CHUNK_SIZE) {
|
for (y in 0 until height) {
|
||||||
val state = getCell(x, y)
|
val state = getCell(x, y)
|
||||||
|
|
||||||
if (state.liquid.def === type) {
|
if (state.liquid.def === type) {
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
package ru.dbotthepony.kstarbound.client
|
package ru.dbotthepony.kstarbound.client
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||||
import ru.dbotthepony.kstarbound.math.encasingChunkPosAABB
|
|
||||||
import ru.dbotthepony.kstarbound.math.encasingIntAABB
|
import ru.dbotthepony.kstarbound.math.encasingIntAABB
|
||||||
|
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
||||||
import ru.dbotthepony.kstarbound.world.*
|
import ru.dbotthepony.kstarbound.world.*
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEf
|
|
||||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||||
import ru.dbotthepony.kvector.util2d.AABB
|
import ru.dbotthepony.kvector.util2d.AABB
|
||||||
import ru.dbotthepony.kvector.vector.Vector2f
|
import ru.dbotthepony.kvector.vector.Vector2f
|
||||||
@ -23,30 +22,32 @@ class ClientWorld(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun chunkFactory(pos: ChunkPos): ClientChunk {
|
override fun chunkFactory(pos: ChunkPos): ClientChunk {
|
||||||
return ClientChunk(
|
return ClientChunk(this, pos, chunkWidth, chunkHeight)
|
||||||
world = this,
|
|
||||||
pos = pos,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addLayers(
|
fun addLayers(
|
||||||
size: AABB,
|
size: AABB,
|
||||||
layers: LayeredRenderer
|
layers: LayeredRenderer
|
||||||
) {
|
) {
|
||||||
for (chunk in collectWithPosition(size.encasingChunkPosAABB())) {
|
val rx = roundTowardsNegativeInfinity(size.mins.x) / chunkWidth - 1
|
||||||
val (x, y) = chunk.first
|
val ry = roundTowardsNegativeInfinity(size.mins.y) / chunkHeight - 1
|
||||||
|
|
||||||
val renderer = chunk.second.Renderer(
|
val dx = roundTowardsNegativeInfinity(size.maxs.x - size.mins.x) / chunkWidth + 2
|
||||||
Vector2f(x * CHUNK_SIZEf + chunkMap.x.cellOffset(x), y * CHUNK_SIZEf + chunkMap.y.cellOffset(y))
|
val dy = roundTowardsNegativeInfinity(size.maxs.y - size.mins.y) / chunkWidth + 2
|
||||||
)
|
|
||||||
|
|
||||||
renderer.addLayers(layers)
|
for (x in rx .. rx + dx) {
|
||||||
chunk.second.bake()
|
for (y in ry .. ry + dy) {
|
||||||
|
val chunk = chunkMap[x, y] ?: continue
|
||||||
|
val renderer = chunk.Renderer(Vector2f(x * chunkWidth.toFloat(), y * chunkHeight.toFloat()))
|
||||||
|
|
||||||
|
renderer.addLayers(layers)
|
||||||
|
chunk.bake()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val pos = client.screenToWorld(client.mouseCoordinatesF)
|
val pos = client.screenToWorld(client.mouseCoordinatesF)
|
||||||
|
|
||||||
layers.add(-999999) {
|
/*layers.add(-999999) {
|
||||||
val lightsize = 16
|
val lightsize = 16
|
||||||
|
|
||||||
val lightmap = floodLight(
|
val lightmap = floodLight(
|
||||||
@ -62,7 +63,7 @@ class ClientWorld(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
/*
|
/*
|
||||||
val rayFan = ArrayList<Vector2d>()
|
val rayFan = ArrayList<Vector2d>()
|
||||||
|
@ -227,7 +227,7 @@ class StarboundClient(val starbound: Starbound) : Closeable {
|
|||||||
|
|
||||||
val tileRenderers = TileRenderers(this)
|
val tileRenderers = TileRenderers(this)
|
||||||
|
|
||||||
var world: ClientWorld? = ClientWorld(this, 0L, Vector2i(3000, 2000), true)
|
var world: ClientWorld? = ClientWorld(this, 0L, null, true)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
putDebugLog("Initialized OpenGL context")
|
putDebugLog("Initialized OpenGL context")
|
||||||
|
@ -168,14 +168,14 @@ class TileRenderers(val client: StarboundClient) {
|
|||||||
private fun vertexTextureBuilder() = VertexBuilder(GLAttributeList.TILE, GeometryType.QUADS)
|
private fun vertexTextureBuilder() = VertexBuilder(GLAttributeList.TILE, GeometryType.QUADS)
|
||||||
|
|
||||||
private class TileEqualityTester(val definition: TileDefinition) : EqualityRuleTester {
|
private class TileEqualityTester(val definition: TileDefinition) : EqualityRuleTester {
|
||||||
override fun test(thisTile: ITileState, otherTile: ITileState): Boolean {
|
override fun test(thisTile: ITileState?, otherTile: ITileState?): Boolean {
|
||||||
return otherTile.material == definition && thisTile.hueShift == otherTile.hueShift
|
return otherTile?.material == definition && thisTile?.hueShift == otherTile.hueShift
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ModifierEqualityTester(val definition: MaterialModifier) : EqualityRuleTester {
|
private class ModifierEqualityTester(val definition: MaterialModifier) : EqualityRuleTester {
|
||||||
override fun test(thisTile: ITileState, otherTile: ITileState): Boolean {
|
override fun test(thisTile: ITileState?, otherTile: ITileState?): Boolean {
|
||||||
return otherTile.modifier == definition
|
return otherTile?.modifier == definition
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ data class RenderPiece(
|
|||||||
)
|
)
|
||||||
|
|
||||||
fun interface EqualityRuleTester {
|
fun interface EqualityRuleTester {
|
||||||
fun test(thisTile: ITileState, otherTile: ITileState): Boolean
|
fun test(thisTile: ITileState?, otherTile: ITileState?): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
@ -50,7 +50,7 @@ data class RenderRuleList(
|
|||||||
|
|
||||||
private object Conntects : ActualTester {
|
private object Conntects : ActualTester {
|
||||||
override fun doTest(getter: ITileAccess, equalityTester: EqualityRuleTester, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
|
override fun doTest(getter: ITileAccess, equalityTester: EqualityRuleTester, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
|
||||||
return getter.getTile(thisPos + offsetPos).material != null
|
return getter.getTile(thisPos + offsetPos)?.material != null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,13 +12,6 @@ fun AABB.encasingIntAABB(): AABBi {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun AABB.encasingChunkPosAABB(): AABBi {
|
|
||||||
return AABBi(
|
|
||||||
Vector2i(ChunkPos.component(roundTowardsNegativeInfinity(mins.x)), ChunkPos.component(roundTowardsNegativeInfinity(mins.y))),
|
|
||||||
Vector2i(ChunkPos.component(roundTowardsPositiveInfinity(maxs.x)), ChunkPos.component(roundTowardsPositiveInfinity(maxs.y))),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Iterator<T>(private val aabb: AABBi, private val factory: (x: Int, y: Int) -> T) : kotlin.collections.Iterator<T> {
|
private class Iterator<T>(private val aabb: AABBi, private val factory: (x: Int, y: Int) -> T) : kotlin.collections.Iterator<T> {
|
||||||
private var x = aabb.mins.x
|
private var x = aabb.mins.x
|
||||||
private var y = aabb.mins.y
|
private var y = aabb.mins.y
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package ru.dbotthepony.kstarbound.world
|
package ru.dbotthepony.kstarbound.world
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet
|
||||||
import ru.dbotthepony.kbox2d.api.BodyDef
|
import ru.dbotthepony.kbox2d.api.BodyDef
|
||||||
import ru.dbotthepony.kbox2d.api.FixtureDef
|
import ru.dbotthepony.kbox2d.api.FixtureDef
|
||||||
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
|
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
|
||||||
@ -7,10 +8,10 @@ import ru.dbotthepony.kbox2d.dynamics.B2Fixture
|
|||||||
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
|
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_BITS
|
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_BITS
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE
|
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_FF
|
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEd
|
import ru.dbotthepony.kstarbound.world.CHUNK_SIZEd
|
||||||
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
||||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||||
import ru.dbotthepony.kstarbound.world.api.ILiquidState
|
import ru.dbotthepony.kstarbound.world.api.ILiquidState
|
||||||
@ -38,9 +39,22 @@ import kotlin.collections.HashSet
|
|||||||
*
|
*
|
||||||
* Весь игровой мир будет измеряться в Starbound Unit'ах
|
* Весь игровой мир будет измеряться в Starbound Unit'ах
|
||||||
*/
|
*/
|
||||||
abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType, This>>(val world: WorldType, val pos: ChunkPos) : ICellAccess {
|
abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType, This>>(
|
||||||
|
val world: WorldType,
|
||||||
|
val pos: ChunkPos,
|
||||||
|
val width: Int,
|
||||||
|
val height: Int
|
||||||
|
) : ICellAccess {
|
||||||
var changeset = 0
|
var changeset = 0
|
||||||
private set
|
private set(value) {
|
||||||
|
field = value
|
||||||
|
|
||||||
|
if (isEmpty) {
|
||||||
|
isEmpty = false
|
||||||
|
world.chunkMap.promote(this as This)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var tileChangeset = 0
|
var tileChangeset = 0
|
||||||
private set
|
private set
|
||||||
var liquidChangeset = 0
|
var liquidChangeset = 0
|
||||||
@ -55,24 +69,21 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
var backgroundChangeset = 0
|
var backgroundChangeset = 0
|
||||||
private set
|
private set
|
||||||
|
|
||||||
protected val cells = Object2DArray(CHUNK_SIZE, CHUNK_SIZE, ::Cell)
|
private var isEmpty = true
|
||||||
|
|
||||||
override fun getCell(x: Int, y: Int): IChunkCell {
|
protected val cells by lazy {
|
||||||
return cells[x, y]
|
Object2DArray.nulls<Cell>(width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
// dimensions of this chunk with accounting for world boundaries
|
override fun getCell(x: Int, y: Int): IChunkCell {
|
||||||
val inWorldWidth: Int
|
var get = cells[x, y]
|
||||||
val inWorldHeight: Int
|
|
||||||
|
|
||||||
init {
|
if (get == null) {
|
||||||
if (world.size == null) {
|
get = Cell(x, y)
|
||||||
inWorldWidth = CHUNK_SIZE
|
cells[x, y] = get
|
||||||
inWorldHeight = CHUNK_SIZE
|
|
||||||
} else {
|
|
||||||
inWorldWidth = (world.size.x - pos.tileX).coerceAtMost(CHUNK_SIZE)
|
|
||||||
inWorldHeight = (world.size.y - pos.tileY).coerceAtMost(CHUNK_SIZE)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return get
|
||||||
}
|
}
|
||||||
|
|
||||||
// local cells' tile access
|
// local cells' tile access
|
||||||
@ -80,7 +91,7 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
val localForegroundView = TileView.Foreground(this)
|
val localForegroundView = TileView.Foreground(this)
|
||||||
|
|
||||||
// relative world cells access (accessing 0, 0 will lookup cell in world, relative to this chunk)
|
// relative world cells access (accessing 0, 0 will lookup cell in world, relative to this chunk)
|
||||||
val worldView = OffsetCellAccess(world.chunkMap, pos)
|
val worldView = OffsetCellAccess(world.chunkMap, pos.x * width, pos.y * height)
|
||||||
val worldBackgroundView = TileView.Background(worldView)
|
val worldBackgroundView = TileView.Background(worldView)
|
||||||
val worldForegroundView = TileView.Foreground(worldView)
|
val worldForegroundView = TileView.Foreground(worldView)
|
||||||
|
|
||||||
@ -91,10 +102,12 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
private val collisionCache = ArrayList<AABB>()
|
private val collisionCache = ArrayList<AABB>()
|
||||||
private val collisionCacheView = Collections.unmodifiableCollection(collisionCache)
|
private val collisionCacheView = Collections.unmodifiableCollection(collisionCache)
|
||||||
|
|
||||||
private val body = world.physics.createBody(BodyDef(
|
private val body by lazy {
|
||||||
position = pos.tile.toDoubleVector(),
|
world.physics.createBody(BodyDef(
|
||||||
userData = this
|
position = pos.tile(width, height).toDoubleVector(),
|
||||||
))
|
userData = this
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
private val collisionChains = ArrayList<B2Fixture>()
|
private val collisionChains = ArrayList<B2Fixture>()
|
||||||
|
|
||||||
@ -135,7 +148,10 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
world.dirtyPhysicsChunks.add(this as This)
|
world.dirtyPhysicsChunks.add(this as This)
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class Cell(val x: Int, val y: Int) : IChunkCell {
|
inner class Cell(x: Int, y: Int) : IChunkCell {
|
||||||
|
override val x: Int = x + pos.x * width
|
||||||
|
override val y: Int = y + pos.y * width
|
||||||
|
|
||||||
inner class Tile(private val foreground: Boolean) : ITileState {
|
inner class Tile(private val foreground: Boolean) : ITileState {
|
||||||
private fun change() {
|
private fun change() {
|
||||||
if (foreground) {
|
if (foreground) {
|
||||||
@ -183,9 +199,6 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
change()
|
change()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val isEmpty: Boolean
|
|
||||||
get() = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class Liquid : ILiquidState {
|
inner class Liquid : ILiquidState {
|
||||||
@ -226,9 +239,6 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
change()
|
change()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val isEmpty: Boolean
|
|
||||||
get() = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun change() {
|
private fun change() {
|
||||||
@ -271,9 +281,6 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
change()
|
change()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val isEmpty: Boolean
|
|
||||||
get() = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bakeCollisions() {
|
fun bakeCollisions() {
|
||||||
@ -292,11 +299,11 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
val xyAdd = Vector2d(pos.x * CHUNK_SIZEd, pos.y * CHUNK_SIZEd)
|
val xyAdd = Vector2d(pos.x * CHUNK_SIZEd, pos.y * CHUNK_SIZEd)
|
||||||
val seen = BooleanArray(CHUNK_SIZE * CHUNK_SIZE)
|
val seen = BooleanArray(CHUNK_SIZE * CHUNK_SIZE)
|
||||||
|
|
||||||
for (y in 0 .. CHUNK_SIZE_FF) {
|
/*for (y in 0 .. CHUNK_SIZE_FF) {
|
||||||
for (x in 0..CHUNK_SIZE_FF) {
|
for (x in 0..CHUNK_SIZE_FF) {
|
||||||
if (!seen[x or (y shl CHUNK_SIZE_BITS)] && cells[x, y].foreground.material != null) {
|
if (!seen[x or (y shl CHUNK_SIZE_BITS)] && cells[x, y].foreground.material != null) {
|
||||||
val depthFirst = RectTileFlooderDepthFirst(cells, seen, x, y)
|
val depthFirst = RectTileFlooderDepthFirst(this, seen, x, y)
|
||||||
val sizeFirst = RectTileFlooderSizeFirst(cells, seen, x, y)
|
val sizeFirst = RectTileFlooderSizeFirst(this, seen, x, y)
|
||||||
val xSpanDepth = depthFirst.maxs.x - depthFirst.mins.x
|
val xSpanDepth = depthFirst.maxs.x - depthFirst.mins.x
|
||||||
val ySpanDepth = depthFirst.maxs.y - depthFirst.mins.y
|
val ySpanDepth = depthFirst.maxs.y - depthFirst.mins.y
|
||||||
val xSpanSize = sizeFirst.maxs.x - sizeFirst.mins.x
|
val xSpanSize = sizeFirst.maxs.x - sizeFirst.mins.x
|
||||||
@ -320,7 +327,7 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
collisionCache.add(aabb + xyAdd)
|
collisionCache.add(aabb + xyAdd)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -338,8 +345,7 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
return world.chunkMap.randomLongFor(x or pos.x shl CHUNK_SIZE_BITS, y or pos.y shl CHUNK_SIZE_BITS)
|
return world.chunkMap.randomLongFor(x or pos.x shl CHUNK_SIZE_BITS, y or pos.y shl CHUNK_SIZE_BITS)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected val entities = HashSet<Entity>()
|
protected val entities = ReferenceOpenHashSet<Entity>()
|
||||||
val entitiesAccess: Set<Entity> = Collections.unmodifiableSet(entities)
|
|
||||||
|
|
||||||
protected abstract fun onEntityAdded(entity: Entity)
|
protected abstract fun onEntityAdded(entity: Entity)
|
||||||
protected abstract fun onEntityTransferedToThis(entity: Entity, otherChunk: This)
|
protected abstract fun onEntityTransferedToThis(entity: Entity, otherChunk: This)
|
||||||
@ -351,6 +357,7 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
throw IllegalArgumentException("Already having having entity $entity")
|
throw IllegalArgumentException("Already having having entity $entity")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeset++
|
||||||
onEntityAdded(entity)
|
onEntityAdded(entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -366,6 +373,7 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
throw IllegalArgumentException("Already containing $entity")
|
throw IllegalArgumentException("Already containing $entity")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeset++
|
||||||
onEntityTransferedToThis(entity, otherChunk as This)
|
onEntityTransferedToThis(entity, otherChunk as This)
|
||||||
otherChunk.onEntityTransferedFromThis(entity, this as This)
|
otherChunk.onEntityTransferedFromThis(entity, this as This)
|
||||||
|
|
||||||
@ -379,6 +387,7 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
throw IllegalArgumentException("Already not having entity $entity")
|
throw IllegalArgumentException("Already not having entity $entity")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeset++
|
||||||
onEntityRemoved(entity)
|
onEntityRemoved(entity)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.world
|
package ru.dbotthepony.kstarbound.world
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.math.roundByAbsoluteValue
|
import ru.dbotthepony.kstarbound.math.roundByAbsoluteValue
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_BITS
|
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_BITS
|
||||||
import ru.dbotthepony.kvector.api.IStruct2d
|
import ru.dbotthepony.kvector.api.IStruct2d
|
||||||
import ru.dbotthepony.kvector.api.IStruct2i
|
import ru.dbotthepony.kvector.api.IStruct2i
|
||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
@ -29,48 +29,39 @@ private fun circulate(value: Int, bounds: Int): Int {
|
|||||||
data class ChunkPos(val x: Int, val y: Int) : IStruct2i, Comparable<ChunkPos> {
|
data class ChunkPos(val x: Int, val y: Int) : IStruct2i, Comparable<ChunkPos> {
|
||||||
constructor(pos: IStruct2i) : this(pos.component1(), pos.component2())
|
constructor(pos: IStruct2i) : this(pos.component1(), pos.component2())
|
||||||
|
|
||||||
// bottom left corner
|
fun tile(width: Int, height: Int): Vector2i {
|
||||||
val tileX: Int = x shl CHUNK_SIZE_BITS
|
return Vector2i(x * width, y * height)
|
||||||
val tileY: Int = y shl CHUNK_SIZE_BITS
|
}
|
||||||
val tile = Vector2i(tileX, tileY)
|
|
||||||
|
|
||||||
val top: ChunkPos
|
val top: ChunkPos get() {
|
||||||
get() {
|
|
||||||
return ChunkPos(x, y + 1)
|
return ChunkPos(x, y + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
val bottom: ChunkPos
|
val bottom: ChunkPos get() {
|
||||||
get() {
|
|
||||||
return ChunkPos(x, y - 1)
|
return ChunkPos(x, y - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
val left: ChunkPos
|
val left: ChunkPos get() {
|
||||||
get() {
|
|
||||||
return ChunkPos(x - 1, y)
|
return ChunkPos(x - 1, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
val topLeft: ChunkPos
|
val topLeft: ChunkPos get() {
|
||||||
get() {
|
|
||||||
return ChunkPos(x - 1, y + 1)
|
return ChunkPos(x - 1, y + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
val topRight: ChunkPos
|
val topRight: ChunkPos get() {
|
||||||
get() {
|
|
||||||
return ChunkPos(x + 1, y + 1)
|
return ChunkPos(x + 1, y + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
val bottomLeft: ChunkPos
|
val bottomLeft: ChunkPos get() {
|
||||||
get() {
|
|
||||||
return ChunkPos(x - 1, y - 1)
|
return ChunkPos(x - 1, y - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
val bottomRight: ChunkPos
|
val bottomRight: ChunkPos get() {
|
||||||
get() {
|
|
||||||
return ChunkPos(x + 1, y - 1)
|
return ChunkPos(x + 1, y - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
val right: ChunkPos
|
val right: ChunkPos get() {
|
||||||
get() {
|
|
||||||
return ChunkPos(x + 1, y)
|
return ChunkPos(x + 1, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,54 +105,8 @@ data class ChunkPos(val x: Int, val y: Int) : IStruct2i, Comparable<ChunkPos> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val ZERO = ChunkPos(0, 0)
|
|
||||||
|
|
||||||
fun toLong(x: Int, y: Int): Long {
|
fun toLong(x: Int, y: Int): Long {
|
||||||
return x.toLong() or (y.toLong() shl 32)
|
return x.toLong() or (y.toLong() shl 32)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun longFromPosition(x: Int, y: Int): Long {
|
|
||||||
return toLong(component(x), component(y))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fromPosition(input: IStruct2i): ChunkPos {
|
|
||||||
val (x, y) = input
|
|
||||||
return ChunkPos(component(x), component(y))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fromPosition(input: IStruct2d): ChunkPos {
|
|
||||||
val (x, y) = input
|
|
||||||
return fromPosition(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fromPosition(x: Int, y: Int): ChunkPos {
|
|
||||||
return ChunkPos(component(x), component(y))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fromPosition(x: Int, y: Int, xWrap: Int): ChunkPos {
|
|
||||||
return ChunkPos(circulate(component(x), xWrap), component(y))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fromPosition(x: Double, y: Double): ChunkPos {
|
|
||||||
return ChunkPos(
|
|
||||||
component(roundByAbsoluteValue(x)),
|
|
||||||
component(roundByAbsoluteValue(y))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun fromPosition(x: Double, y: Double, xWrap: Int): ChunkPos {
|
|
||||||
return ChunkPos(
|
|
||||||
circulate(component(roundByAbsoluteValue(x)), xWrap),
|
|
||||||
component(roundByAbsoluteValue(y))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun component(value: Int): Int {
|
|
||||||
if (value < 0) {
|
|
||||||
return -((-value) shr CHUNK_SIZE_BITS) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return value shr CHUNK_SIZE_BITS
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,114 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.world
|
||||||
|
|
||||||
|
abstract class CoordinateMapper {
|
||||||
|
protected fun positiveModulo(a: Int, b: Int): Int {
|
||||||
|
val result = a % b
|
||||||
|
return if (result < 0) result + b else result
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun positiveModulo(a: Double, b: Int): Double {
|
||||||
|
val result = a % b
|
||||||
|
return if (result < 0.0) result + b else result
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun positiveModulo(a: Float, b: Int): Float {
|
||||||
|
val result = a % b
|
||||||
|
return if (result < 0f) result + b else result
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract fun cell(value: Int): Int
|
||||||
|
abstract fun cell(value: Double): Double
|
||||||
|
abstract fun cell(value: Float): Float
|
||||||
|
abstract fun chunk(value: Int): Int
|
||||||
|
|
||||||
|
abstract fun cellToChunk(value: Int): Int
|
||||||
|
fun cellToChunk(value: Float): Int = cellToChunk(value.toInt())
|
||||||
|
fun cellToChunk(value: Double): Int = cellToChunk(value.toInt())
|
||||||
|
|
||||||
|
abstract fun cellModulus(value: Int): Int
|
||||||
|
|
||||||
|
// inside world bounds
|
||||||
|
abstract fun inBoundsCell(value: Int): Boolean
|
||||||
|
abstract fun inBoundsChunk(value: Int): Boolean
|
||||||
|
|
||||||
|
// whenever provided index is legal (if world is wrapped around then provided index is legal outside world bounds)
|
||||||
|
open fun isValidCellIndex(value: Int): Boolean = inBoundsCell(value)
|
||||||
|
open fun isValidChunkIndex(value: Int): Boolean = inBoundsChunk(value)
|
||||||
|
|
||||||
|
object Infinite : CoordinateMapper() {
|
||||||
|
override fun cell(value: Int): Int = value
|
||||||
|
override fun cell(value: Double): Double = value
|
||||||
|
override fun cell(value: Float): Float = value
|
||||||
|
override fun chunk(value: Int): Int = value
|
||||||
|
|
||||||
|
override fun cellToChunk(value: Int): Int {
|
||||||
|
return value shr CHUNK_SIZE_BITS
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cellModulus(value: Int): Int {
|
||||||
|
return value and CHUNK_SIZE_MASK
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inBoundsCell(value: Int) = true
|
||||||
|
override fun inBoundsChunk(value: Int) = true
|
||||||
|
}
|
||||||
|
|
||||||
|
class Wrapper(private val cells: Int, private val chunkSize: Int) : CoordinateMapper() {
|
||||||
|
private val chunks = cells / chunkSize
|
||||||
|
|
||||||
|
override fun inBoundsCell(value: Int) = value in 0 until cells
|
||||||
|
override fun inBoundsChunk(value: Int) = value in 0 until chunks
|
||||||
|
|
||||||
|
override fun isValidCellIndex(value: Int) = true
|
||||||
|
override fun isValidChunkIndex(value: Int) = true
|
||||||
|
|
||||||
|
override fun cellToChunk(value: Int): Int {
|
||||||
|
return chunk(value / chunkSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cellModulus(value: Int): Int {
|
||||||
|
return positiveModulo(value, chunkSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cell(value: Int): Int = positiveModulo(value, cells)
|
||||||
|
override fun cell(value: Double): Double = positiveModulo(value, cells)
|
||||||
|
override fun cell(value: Float): Float = positiveModulo(value, cells)
|
||||||
|
override fun chunk(value: Int): Int = positiveModulo(value, chunks)
|
||||||
|
}
|
||||||
|
|
||||||
|
class Clamper(private val cells: Int, private val chunkSize: Int) : CoordinateMapper() {
|
||||||
|
private val chunks = cells / chunkSize
|
||||||
|
|
||||||
|
override fun inBoundsCell(value: Int): Boolean {
|
||||||
|
return value in 0 until cells
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cellModulus(value: Int): Int {
|
||||||
|
return positiveModulo(value, chunkSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cellToChunk(value: Int): Int {
|
||||||
|
return chunk(value / chunkSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun inBoundsChunk(value: Int): Boolean {
|
||||||
|
return value in 0 until chunks
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cell(value: Int): Int {
|
||||||
|
return value.coerceIn(0, cells - 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cell(value: Double): Double {
|
||||||
|
return value.coerceIn(0.0, cells - 1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cell(value: Float): Float {
|
||||||
|
return value.coerceIn(0f, cells - 1f)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chunk(value: Int): Int {
|
||||||
|
return value.coerceIn(0, chunks - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,64 +0,0 @@
|
|||||||
package ru.dbotthepony.kstarbound.world
|
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE
|
|
||||||
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
|
||||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
|
||||||
import ru.dbotthepony.kstarbound.world.api.ITileAccess
|
|
||||||
import ru.dbotthepony.kstarbound.world.api.ITileState
|
|
||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
|
||||||
|
|
||||||
fun ICellAccess.iterate(fromX: Int = 0, fromY: Int = 0, toX: Int = fromX + CHUNK_SIZE, toY: Int = fromY + CHUNK_SIZE): Iterator<Pair<Vector2i, IChunkCell>> {
|
|
||||||
return object : Iterator<Pair<Vector2i, IChunkCell>> {
|
|
||||||
private var x = fromX
|
|
||||||
private var y = fromY
|
|
||||||
|
|
||||||
override fun hasNext(): Boolean {
|
|
||||||
return x < toX && y < toY
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun next(): Pair<Vector2i, IChunkCell> {
|
|
||||||
if (!hasNext())
|
|
||||||
throw NoSuchElementException()
|
|
||||||
|
|
||||||
val tile = getCell(x, y)
|
|
||||||
val pos = Vector2i(x, y)
|
|
||||||
|
|
||||||
x++
|
|
||||||
|
|
||||||
if (x >= toX) {
|
|
||||||
y++
|
|
||||||
x = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return pos to tile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ITileAccess.iterateTiles(fromX: Int = 0, fromY: Int = 0, toX: Int = fromX + CHUNK_SIZE, toY: Int = fromY + CHUNK_SIZE): Iterator<Pair<Vector2i, ITileState>> {
|
|
||||||
return object : Iterator<Pair<Vector2i, ITileState>> {
|
|
||||||
private var x = fromX
|
|
||||||
private var y = fromY
|
|
||||||
|
|
||||||
override fun hasNext(): Boolean {
|
|
||||||
return x < toX && y < toY
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun next(): Pair<Vector2i, ITileState> {
|
|
||||||
if (!hasNext())
|
|
||||||
throw NoSuchElementException()
|
|
||||||
|
|
||||||
val tile = getTile(x, y)
|
|
||||||
val pos = Vector2i(x, y)
|
|
||||||
|
|
||||||
x++
|
|
||||||
|
|
||||||
if (x >= toX) {
|
|
||||||
y++
|
|
||||||
x = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return pos to tile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -124,7 +124,7 @@ fun ICellAccess.castRayNaive(
|
|||||||
val tilePos = Vector2i(x.roundToInt(), y.roundToInt())
|
val tilePos = Vector2i(x.roundToInt(), y.roundToInt())
|
||||||
|
|
||||||
if (tilePos != prev) {
|
if (tilePos != prev) {
|
||||||
val tile = getCell(tilePos)
|
val tile = getCell(tilePos) ?: break
|
||||||
|
|
||||||
when (filter.test(tile, t, tilePos)) {
|
when (filter.test(tile, t, tilePos)) {
|
||||||
RayFilterResult.HIT -> {
|
RayFilterResult.HIT -> {
|
||||||
|
@ -13,9 +13,6 @@ import ru.dbotthepony.kbox2d.dynamics.B2World
|
|||||||
import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
|
import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
|
||||||
import ru.dbotthepony.kstarbound.math.*
|
import ru.dbotthepony.kstarbound.math.*
|
||||||
import ru.dbotthepony.kstarbound.util.Timer
|
import ru.dbotthepony.kstarbound.util.Timer
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE
|
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_BITS
|
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_MASK
|
|
||||||
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
||||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||||
import ru.dbotthepony.kstarbound.world.api.TileView
|
import ru.dbotthepony.kstarbound.world.api.TileView
|
||||||
@ -28,6 +25,14 @@ import ru.dbotthepony.kvector.util2d.AABB
|
|||||||
import ru.dbotthepony.kvector.util2d.AABBi
|
import ru.dbotthepony.kvector.util2d.AABBi
|
||||||
import ru.dbotthepony.kvector.vector.Vector2d
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
|
import java.lang.ref.ReferenceQueue
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
|
|
||||||
|
const val CHUNK_SIZE_BITS = 5
|
||||||
|
const val CHUNK_SIZE_MASK = 1 or 2 or 4 or 8 or 16
|
||||||
|
const val CHUNK_SIZE = 1 shl CHUNK_SIZE_BITS // 32
|
||||||
|
const val CHUNK_SIZE_FF = CHUNK_SIZE - 1
|
||||||
|
const val CHUNK_SIZEd = CHUNK_SIZE.toDouble()
|
||||||
|
|
||||||
abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, ChunkType>>(
|
abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, ChunkType>>(
|
||||||
val seed: Long,
|
val seed: Long,
|
||||||
@ -35,164 +40,73 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
val loopX: Boolean,
|
val loopX: Boolean,
|
||||||
val loopY: Boolean
|
val loopY: Boolean
|
||||||
) {
|
) {
|
||||||
abstract class AbstractCoordinatesWrapper {
|
private fun determineChunkSize(cells: Int): Int {
|
||||||
abstract fun cell(value: Int): Int
|
for (i in 32 downTo 1) {
|
||||||
abstract fun cell(value: Double): Double
|
if (cells % i == 0) {
|
||||||
abstract fun cell(value: Float): Float
|
return i
|
||||||
|
|
||||||
abstract fun chunk(value: Int): Int
|
|
||||||
abstract fun cellOffset(chunk: Int): Int
|
|
||||||
|
|
||||||
abstract fun inBounds(value: Int): Boolean
|
|
||||||
abstract fun inBoundsChunk(value: Int): Boolean
|
|
||||||
|
|
||||||
protected fun positiveModulo(a: Int, b: Int): Int {
|
|
||||||
val result = a % b
|
|
||||||
return if (result < 0) result + b else result
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun positiveModulo(a: Double, b: Int): Double {
|
|
||||||
val result = a % b
|
|
||||||
return if (result < 0.0) result + b else result
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun positiveModulo(a: Float, b: Int): Float {
|
|
||||||
val result = a % b
|
|
||||||
return if (result < 0f) result + b else result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object PassthroughWrapper : AbstractCoordinatesWrapper() {
|
|
||||||
override fun cell(value: Int): Int = value
|
|
||||||
override fun cell(value: Double): Double = value
|
|
||||||
override fun cell(value: Float): Float = value
|
|
||||||
override fun chunk(value: Int): Int = value
|
|
||||||
override fun cellOffset(chunk: Int): Int = 0
|
|
||||||
|
|
||||||
override fun inBounds(value: Int) = true
|
|
||||||
override fun inBoundsChunk(value: Int) = true
|
|
||||||
}
|
|
||||||
|
|
||||||
class CoordinatesWrapper(private val cell: Int, private val chunk: Int) : AbstractCoordinatesWrapper() {
|
|
||||||
override fun inBounds(value: Int): Boolean {
|
|
||||||
return value in 0 until cell
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun inBoundsChunk(value: Int): Boolean {
|
|
||||||
return value in 0 until chunk
|
|
||||||
}
|
|
||||||
|
|
||||||
private val misalignedTiles = cell and CHUNK_SIZE_MASK
|
|
||||||
private val misalignedTiles2 = misalignedTiles - CHUNK_SIZE
|
|
||||||
|
|
||||||
override fun cellOffset(chunk: Int): Int {
|
|
||||||
if (misalignedTiles == 0) {
|
|
||||||
return 0
|
|
||||||
} else if (chunk >= 0) {
|
|
||||||
return (chunk / this.chunk) * misalignedTiles2
|
|
||||||
} else {
|
|
||||||
return ((chunk + 1) / this.chunk - 1) * misalignedTiles2
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cell(value: Int): Int {
|
throw RuntimeException("unreachable code")
|
||||||
return positiveModulo(value, cell)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cell(value: Double): Double {
|
|
||||||
return positiveModulo(value, cell)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cell(value: Float): Float {
|
|
||||||
return positiveModulo(value, cell)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chunk(value: Int): Int {
|
|
||||||
return positiveModulo(value, chunk)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class CoordinatesClamper(private val cell: Int, private val chunk: Int) : AbstractCoordinatesWrapper() {
|
val chunkWidth = size?.x?.let(::determineChunkSize) ?: CHUNK_SIZE
|
||||||
override fun inBounds(value: Int): Boolean {
|
val chunkHeight = size?.y?.let(::determineChunkSize) ?: CHUNK_SIZE
|
||||||
return value in 0 until cell
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cellOffset(chunk: Int): Int {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun inBoundsChunk(value: Int): Boolean {
|
|
||||||
return value in 0 until chunk
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cell(value: Int): Int {
|
|
||||||
return value.coerceIn(0, cell - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cell(value: Double): Double {
|
|
||||||
return value.coerceIn(0.0, cell - 1.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cell(value: Float): Float {
|
|
||||||
return value.coerceIn(0f, cell - 1f)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chunk(value: Int): Int {
|
|
||||||
return value.coerceIn(0, chunk - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract inner class ChunkMap : ICellAccess {
|
abstract inner class ChunkMap : ICellAccess {
|
||||||
abstract val x: AbstractCoordinatesWrapper
|
abstract val x: CoordinateMapper
|
||||||
abstract val y: AbstractCoordinatesWrapper
|
abstract val y: CoordinateMapper
|
||||||
|
|
||||||
fun inBounds(x: Int, y: Int) = this.x.inBounds(x) && this.y.inBounds(y)
|
|
||||||
fun inBounds(value: IStruct2i) = this.x.inBounds(value.component1()) && this.y.inBounds(value.component2())
|
|
||||||
|
|
||||||
override fun randomLongFor(x: Int, y: Int): Long {
|
|
||||||
return super.randomLongFor(x, y) xor seed
|
|
||||||
}
|
|
||||||
|
|
||||||
val background = TileView.Background(this)
|
val background = TileView.Background(this)
|
||||||
val foreground = TileView.Foreground(this)
|
val foreground = TileView.Foreground(this)
|
||||||
|
|
||||||
|
fun inBounds(x: Int, y: Int) = this.x.inBoundsCell(x) && this.y.inBoundsCell(y)
|
||||||
|
fun inBounds(value: IStruct2i) = this.x.inBoundsCell(value.component1()) && this.y.inBoundsCell(value.component2())
|
||||||
|
|
||||||
|
fun cellToChunk(x: Int, y: Int) = ChunkPos(this.x.cellToChunk(x), this.y.cellToChunk(y))
|
||||||
|
fun cellToChunk(x: Double, y: Double) = ChunkPos(this.x.cellToChunk(x.toInt()), this.y.cellToChunk(y.toInt()))
|
||||||
|
fun cellToChunk(value: IStruct2i) = cellToChunk(value.component1(), value.component2())
|
||||||
|
fun cellToChunk(value: IStruct2d) = cellToChunk(value.component1(), value.component2())
|
||||||
|
|
||||||
|
final override fun randomLongFor(x: Int, y: Int) = super.randomLongFor(x, y) xor seed
|
||||||
|
|
||||||
abstract operator fun get(x: Int, y: Int): ChunkType?
|
abstract operator fun get(x: Int, y: Int): ChunkType?
|
||||||
operator fun get(pos: ChunkPos) = get(pos.x, pos.y)
|
operator fun get(pos: ChunkPos) = get(pos.x, pos.y)
|
||||||
|
|
||||||
open fun chunkFromCell(x: Int, y: Int): ChunkType? {
|
abstract fun promote(self: ChunkType)
|
||||||
return get(ChunkPos.component(x), ChunkPos.component(y))
|
|
||||||
|
protected val queue = ReferenceQueue<ChunkType>()
|
||||||
|
|
||||||
|
abstract fun purge()
|
||||||
|
|
||||||
|
protected inner class Ref(chunk: ChunkType) : WeakReference<ChunkType>(chunk, queue) {
|
||||||
|
val pos = chunk.pos
|
||||||
}
|
}
|
||||||
|
|
||||||
fun chunkFromCell(pos: IStruct2i) = chunkFromCell(pos.component1(), pos.component2())
|
|
||||||
|
|
||||||
override fun getCell(x: Int, y: Int): IChunkCell {
|
|
||||||
return chunkFromCell(x, y)?.getCell(x and CHUNK_SIZE_MASK, y and CHUNK_SIZE_MASK) ?: IChunkCell.Companion
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract operator fun set(x: Int, y: Int, chunk: ChunkType)
|
|
||||||
abstract fun remove(x: Int, y: Int)
|
abstract fun remove(x: Int, y: Int)
|
||||||
|
|
||||||
fun cellToGrid(x: Int, y: Int) = ChunkPos.fromPosition(this.x.cell(x), this.y.cell(y))
|
override fun getCell(x: Int, y: Int): IChunkCell? {
|
||||||
fun cellToGrid(x: Double, y: Double) = ChunkPos.fromPosition(this.x.cell(x), this.y.cell(y))
|
if (!this.x.isValidCellIndex(x) || !this.y.isValidCellIndex(y)) return null
|
||||||
fun cellToGrid(position: IStruct2i) = cellToGrid(position.component1(), position.component2())
|
val ix = this.x.cell(x)
|
||||||
fun cellToGrid(position: IStruct2d) = cellToGrid(position.component1(), position.component2())
|
val iy = this.y.cell(y)
|
||||||
|
return get(this.x.cellToChunk(ix), this.y.cellToChunk(iy))?.getCell(this.x.cellModulus(ix), this.y.cellModulus(iy))
|
||||||
|
}
|
||||||
|
|
||||||
fun computeIfAbsent(pos: ChunkPos) = computeIfAbsent(pos.x, pos.y)
|
override fun getCellDirect(x: Int, y: Int): IChunkCell? {
|
||||||
|
if (!this.x.inBoundsCell(x) || !this.y.inBoundsCell(y)) return null
|
||||||
|
return getCell(x, y)
|
||||||
|
}
|
||||||
|
|
||||||
fun computeIfAbsent(x: Int, y: Int): ChunkType {
|
protected fun create(x: Int, y: Int): ChunkType {
|
||||||
val existing = get(x, y)
|
purge()
|
||||||
|
val pos = ChunkPos(x, y)
|
||||||
if (existing != null)
|
|
||||||
return existing
|
|
||||||
|
|
||||||
val pos = ChunkPos(this.x.chunk(x), this.y.chunk(y))
|
|
||||||
val chunk = chunkFactory(pos)
|
val chunk = chunkFactory(pos)
|
||||||
val orphanedInThisChunk = ArrayList<Entity>()
|
val orphanedInThisChunk = ArrayList<Entity>()
|
||||||
|
|
||||||
for (ent in orphanedEntities) {
|
for (ent in orphanedEntities) {
|
||||||
val (ex, ey) = ent.position
|
val (ex, ey) = ent.position
|
||||||
|
|
||||||
if (ChunkPos.fromPosition(this.x.cell(ex), this.y.cell(ey)) == pos) {
|
if (this.x.cellToChunk(ex) == x && this.y.cellToChunk(ey) == y) {
|
||||||
orphanedInThisChunk.add(ent)
|
orphanedInThisChunk.add(ent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,65 +115,124 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
ent.chunk = chunk
|
ent.chunk = chunk
|
||||||
}
|
}
|
||||||
|
|
||||||
set(x, y, chunk)
|
|
||||||
return chunk
|
return chunk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// infinite chunk map is around 30% slower than rectangular one
|
// infinite chunk map is around 30% slower than rectangular one
|
||||||
inner class InfiniteChunkMap : ChunkMap() {
|
inner class InfiniteChunkMap : ChunkMap() {
|
||||||
private val map = Long2ObjectOpenHashMap<ChunkType>()
|
private val map = Long2ObjectOpenHashMap<Any>()
|
||||||
|
override val x = CoordinateMapper.Infinite
|
||||||
|
override val y = CoordinateMapper.Infinite
|
||||||
|
|
||||||
override fun get(x: Int, y: Int): ChunkType? {
|
override fun get(x: Int, y: Int): ChunkType {
|
||||||
return map[ChunkPos.toLong(x, y)]
|
return map[ChunkPos.toLong(x, y)]?.let {
|
||||||
|
if (it is World<*, *>.ChunkMap.Ref) {
|
||||||
|
it.get() as ChunkType?
|
||||||
|
} else {
|
||||||
|
it as ChunkType?
|
||||||
|
}
|
||||||
|
} ?: create(x, y).also {
|
||||||
|
map[ChunkPos.toLong(x, y)] = Ref(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun set(x: Int, y: Int, chunk: ChunkType) {
|
override fun purge() {
|
||||||
map[ChunkPos.toLong(x, y)] = chunk
|
var next = queue.poll() as World<*, *>.ChunkMap.Ref?
|
||||||
|
|
||||||
|
while (next != null) {
|
||||||
|
val get = map[ChunkPos.toLong(next.pos.x, next.pos.y)]
|
||||||
|
|
||||||
|
if (get === next) {
|
||||||
|
map.remove(ChunkPos.toLong(next.pos.x, next.pos.y))
|
||||||
|
}
|
||||||
|
|
||||||
|
next = queue.poll() as World<*, *>.ChunkMap.Ref?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun promote(self: ChunkType) {
|
||||||
|
val (x, y) = self.pos
|
||||||
|
val ref = map[ChunkPos.toLong(x, y)]
|
||||||
|
|
||||||
|
if (ref !is World<*, *>.ChunkMap.Ref) {
|
||||||
|
throw IllegalStateException("Tried to promote chunk from weak to strong storage at $x, $y; but there is $ref")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(ref as World<*, ChunkType>.ChunkMap.Ref).refersTo(self)) {
|
||||||
|
throw IllegalStateException("Tried to promote chunk from weak to strong storage at $x, $y; but it doesn't refer to valid object (self: $self, referent: ${ref.get()})")
|
||||||
|
}
|
||||||
|
|
||||||
|
map[ChunkPos.toLong(x, y)] = self
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun remove(x: Int, y: Int) {
|
override fun remove(x: Int, y: Int) {
|
||||||
map.remove(ChunkPos.toLong(x, y))
|
val ref = map.remove(ChunkPos.toLong(x, y))
|
||||||
}
|
|
||||||
|
|
||||||
override val x = PassthroughWrapper
|
if (ref is World<*, *>.ChunkMap.Ref) {
|
||||||
override val y = PassthroughWrapper
|
ref.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class RectChunkMap : ChunkMap() {
|
inner class RectChunkMap : ChunkMap() {
|
||||||
val width = size!!.x
|
val width = size!!.x
|
||||||
val height = size!!.y
|
val height = size!!.y
|
||||||
|
|
||||||
val widthInChunks = if (width and CHUNK_SIZE_MASK == 0) width / CHUNK_SIZE else width / CHUNK_SIZE + 1
|
override val x: CoordinateMapper = if (loopX) CoordinateMapper.Wrapper(width, chunkWidth) else CoordinateMapper.Clamper(width, chunkWidth)
|
||||||
val heightInChunks = if (height and CHUNK_SIZE_MASK == 0) height / CHUNK_SIZE else height / CHUNK_SIZE + 1
|
override val y: CoordinateMapper = if (loopY) CoordinateMapper.Wrapper(height, chunkHeight) else CoordinateMapper.Clamper(height, chunkHeight)
|
||||||
|
|
||||||
override val x: AbstractCoordinatesWrapper = if (loopX) CoordinatesWrapper(width, widthInChunks) else CoordinatesClamper(width, widthInChunks)
|
private val map = Object2DArray.nulls<Any>(width / chunkWidth, height / chunkHeight)
|
||||||
override val y: AbstractCoordinatesWrapper = if (loopY) CoordinatesWrapper(height, heightInChunks) else CoordinatesClamper(height, heightInChunks)
|
|
||||||
|
|
||||||
private val map = Object2DArray.nulls<ChunkType>(widthInChunks, heightInChunks)
|
|
||||||
|
|
||||||
override fun get(x: Int, y: Int): ChunkType? {
|
override fun get(x: Int, y: Int): ChunkType? {
|
||||||
return map[this.x.chunk(x), this.y.chunk(y)]
|
if (!this.x.isValidChunkIndex(x) || !this.y.isValidChunkIndex(y)) return null
|
||||||
|
val ix = this.x.chunk(x)
|
||||||
|
val iy = this.x.chunk(y)
|
||||||
|
|
||||||
|
return map[ix, iy]?.let {
|
||||||
|
if (it is World<*, *>.ChunkMap.Ref) {
|
||||||
|
it.get() as ChunkType?
|
||||||
|
} else {
|
||||||
|
it as ChunkType?
|
||||||
|
}
|
||||||
|
} ?: create(ix, iy).also {
|
||||||
|
map[ix, iy] = Ref(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chunkFromCell(x: Int, y: Int): ChunkType? {
|
override fun purge() {
|
||||||
val ix = this.x.cell(x)
|
while (queue.poll() != null) {}
|
||||||
val iy = this.y.cell(y)
|
|
||||||
return get(ix ushr CHUNK_SIZE_BITS, iy ushr CHUNK_SIZE_BITS)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCell(x: Int, y: Int): IChunkCell {
|
override fun promote(self: ChunkType) {
|
||||||
val ix = this.x.cell(x)
|
val (x, y) = self.pos
|
||||||
val iy = this.y.cell(y)
|
|
||||||
return get(ix ushr CHUNK_SIZE_BITS, iy ushr CHUNK_SIZE_BITS)?.getCell(ix and CHUNK_SIZE_MASK, iy and CHUNK_SIZE_MASK) ?: IChunkCell.Companion
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun set(x: Int, y: Int, chunk: ChunkType) {
|
val ref = map[x, y]
|
||||||
map[this.x.chunk(x), this.y.chunk(y)] = chunk
|
|
||||||
|
if (ref !is World<*, *>.ChunkMap.Ref) {
|
||||||
|
throw IllegalStateException("Tried to promote chunk from weak to strong storage at $x, $y; but there is $ref")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(ref as World<*, ChunkType>.ChunkMap.Ref).refersTo(self)) {
|
||||||
|
throw IllegalStateException("Tried to promote chunk from weak to strong storage at $x, $y; but it doesn't refer to valid object (self: $self, referent: ${ref.get()})")
|
||||||
|
}
|
||||||
|
|
||||||
|
map[x, y] = self
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun remove(x: Int, y: Int) {
|
override fun remove(x: Int, y: Int) {
|
||||||
map[this.x.chunk(x), this.y.chunk(y)] = null
|
if (!this.x.isValidChunkIndex(x) || !this.y.isValidChunkIndex(y)) return
|
||||||
|
|
||||||
|
val ix = this.x.chunk(x)
|
||||||
|
val iy = this.x.chunk(y)
|
||||||
|
|
||||||
|
val old = map[ix, iy]
|
||||||
|
|
||||||
|
if (old is World<*, *>.ChunkMap.Ref) {
|
||||||
|
old.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
map[ix, iy] = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -380,7 +353,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
|
|
||||||
dirtyPhysicsChunks.clear()
|
dirtyPhysicsChunks.clear()
|
||||||
|
|
||||||
physics.step(delta, 6, 4)
|
//physics.step(delta, 6, 4)
|
||||||
|
|
||||||
timer += delta
|
timer += delta
|
||||||
|
|
||||||
@ -432,29 +405,12 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
|
|
||||||
protected abstract fun chunkFactory(pos: ChunkPos): ChunkType
|
protected abstract fun chunkFactory(pos: ChunkPos): ChunkType
|
||||||
|
|
||||||
/**
|
|
||||||
* Возвращает все чанки, которые пересекаются с заданным [boundingBox]
|
|
||||||
*/
|
|
||||||
fun collectWithPosition(boundingBox: AABBi): List<Pair<ChunkPos, ChunkType>> {
|
|
||||||
val output = ArrayList<Pair<ChunkPos, ChunkType>>()
|
|
||||||
|
|
||||||
for (pos in boundingBox.chunkPositions) {
|
|
||||||
val chunk = chunkMap[pos]
|
|
||||||
|
|
||||||
if (chunk != null && (loopX || chunkMap.x.inBoundsChunk(pos.x)) && (loopY || chunkMap.y.inBoundsChunk(pos.y))) {
|
|
||||||
output.add(pos to chunk)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return output
|
|
||||||
}
|
|
||||||
|
|
||||||
fun testSpace(aabb: AABB, predicate: Predicate<IChunkCell> = Predicate { it.foreground.material != null }): Boolean {
|
fun testSpace(aabb: AABB, predicate: Predicate<IChunkCell> = Predicate { it.foreground.material != null }): Boolean {
|
||||||
val tiles = aabb.encasingIntAABB()
|
val tiles = aabb.encasingIntAABB()
|
||||||
|
|
||||||
for (x in tiles.mins.x .. tiles.maxs.x) {
|
for (x in tiles.mins.x .. tiles.maxs.x) {
|
||||||
for (y in tiles.mins.y .. tiles.maxs.y) {
|
for (y in tiles.mins.y .. tiles.maxs.y) {
|
||||||
if (predicate.test(chunkMap.getCell(x, y))) {
|
if (predicate.test(chunkMap.getCell(x, y) ?: continue)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -482,13 +438,13 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
|
|
||||||
var newIntensity: Int
|
var newIntensity: Int
|
||||||
|
|
||||||
if (tile.foreground.material?.renderParameters?.lightTransparent == false) {
|
if (tile?.foreground?.material?.renderParameters?.lightTransparent == false) {
|
||||||
newIntensity = thisIntensity - lightBlockerStrength
|
newIntensity = thisIntensity - lightBlockerStrength
|
||||||
} else {
|
} else {
|
||||||
newIntensity = thisIntensity - 1
|
newIntensity = thisIntensity - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tile.foreground.material != null)
|
if (tile?.foreground?.material != null)
|
||||||
newIntensity = 0
|
newIntensity = 0
|
||||||
|
|
||||||
lightmap[posX, posY] = newIntensity.coerceAtLeast(0)
|
lightmap[posX, posY] = newIntensity.coerceAtLeast(0)
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.api
|
|
||||||
|
|
||||||
const val CHUNK_SIZE_BITS = 5
|
|
||||||
const val CHUNK_SIZE_MASK = 1 or 2 or 4 or 8 or 16
|
|
||||||
const val CHUNK_SIZE = 1 shl CHUNK_SIZE_BITS // 32
|
|
||||||
const val CHUNK_SIZE_FF = CHUNK_SIZE - 1
|
|
||||||
const val CHUNK_SIZEf = CHUNK_SIZE.toFloat()
|
|
||||||
const val CHUNK_SIZEd = CHUNK_SIZE.toDouble()
|
|
@ -1,14 +1,31 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.api
|
package ru.dbotthepony.kstarbound.world.api
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
|
||||||
import ru.dbotthepony.kvector.api.IStruct2i
|
import ru.dbotthepony.kvector.api.IStruct2i
|
||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
|
|
||||||
interface ICellAccess {
|
interface ICellAccess {
|
||||||
// relative
|
/**
|
||||||
fun getCell(x: Int, y: Int): IChunkCell
|
* non-null - valid cell (maybe wrapped around)
|
||||||
|
* null - invalid cell (outside world bounds)
|
||||||
|
*/
|
||||||
|
fun getCell(x: Int, y: Int): IChunkCell?
|
||||||
fun getCell(pos: IStruct2i) = getCell(pos.component1(), pos.component2())
|
fun getCell(pos: IStruct2i) = getCell(pos.component1(), pos.component2())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* non-null - valid cell and not wrapped around
|
||||||
|
* null - invalid cell (outside world bounds)
|
||||||
|
*/
|
||||||
|
fun getCellDirect(x: Int, y: Int): IChunkCell? {
|
||||||
|
val cell = getCell(x, y)
|
||||||
|
|
||||||
|
if (cell == null || cell.x != x || cell.y != y)
|
||||||
|
return null
|
||||||
|
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCellDirect(pos: IStruct2i) = getCellDirect(pos.component1(), pos.component2())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Возвращает псевдослучайное Long для заданной позиции
|
* Возвращает псевдослучайное Long для заданной позиции
|
||||||
*
|
*
|
||||||
@ -38,9 +55,8 @@ interface ICellAccess {
|
|||||||
|
|
||||||
class OffsetCellAccess(private val parent: ICellAccess, private val x: Int, private val y: Int) : ICellAccess {
|
class OffsetCellAccess(private val parent: ICellAccess, private val x: Int, private val y: Int) : ICellAccess {
|
||||||
constructor(parent: ICellAccess, offset: IStruct2i) : this(parent, offset.component1(), offset.component2())
|
constructor(parent: ICellAccess, offset: IStruct2i) : this(parent, offset.component1(), offset.component2())
|
||||||
constructor(parent: ICellAccess, offset: ChunkPos) : this(parent, offset.tile)
|
|
||||||
|
|
||||||
override fun getCell(x: Int, y: Int): IChunkCell {
|
override fun getCell(x: Int, y: Int): IChunkCell? {
|
||||||
return parent.getCell(x + this.x, y + this.y)
|
return parent.getCell(x + this.x, y + this.y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,10 @@ import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
|||||||
import java.io.DataInputStream
|
import java.io.DataInputStream
|
||||||
|
|
||||||
interface IChunkCell {
|
interface IChunkCell {
|
||||||
|
// absolute in-world coordinates
|
||||||
|
val x: Int
|
||||||
|
val y: Int
|
||||||
|
|
||||||
val foreground: ITileState
|
val foreground: ITileState
|
||||||
val background: ITileState
|
val background: ITileState
|
||||||
val liquid: ILiquidState
|
val liquid: ILiquidState
|
||||||
@ -16,8 +20,6 @@ interface IChunkCell {
|
|||||||
var envBiome: Int
|
var envBiome: Int
|
||||||
var isIndestructible: Boolean
|
var isIndestructible: Boolean
|
||||||
|
|
||||||
val isEmpty: Boolean
|
|
||||||
|
|
||||||
fun read(
|
fun read(
|
||||||
materialAccess: (Int) -> RegistryObject<TileDefinition>?,
|
materialAccess: (Int) -> RegistryObject<TileDefinition>?,
|
||||||
modifierAccess: (Int) -> RegistryObject<MaterialModifier>?,
|
modifierAccess: (Int) -> RegistryObject<MaterialModifier>?,
|
||||||
@ -28,36 +30,22 @@ interface IChunkCell {
|
|||||||
background.read(materialAccess, modifierAccess, stream)
|
background.read(materialAccess, modifierAccess, stream)
|
||||||
liquid.read(liquidAccess, stream)
|
liquid.read(liquidAccess, stream)
|
||||||
|
|
||||||
stream.skipBytes(1) // collisionMap
|
stream.skipNBytes(1) // collisionMap
|
||||||
|
|
||||||
dungeonId = stream.readUnsignedShort()
|
dungeonId = stream.readUnsignedShort()
|
||||||
biome = stream.readUnsignedByte()
|
biome = stream.readUnsignedByte()
|
||||||
envBiome = stream.readUnsignedByte()
|
envBiome = stream.readUnsignedByte()
|
||||||
isIndestructible = stream.readBoolean()
|
isIndestructible = stream.readBoolean()
|
||||||
|
|
||||||
stream.skipBytes(1) // unknown
|
stream.skipNBytes(1) // unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : IChunkCell {
|
companion object {
|
||||||
override val foreground: ITileState
|
fun skip(stream: DataInputStream) {
|
||||||
get() = ITileState
|
ITileState.skip(stream)
|
||||||
override val background: ITileState
|
ITileState.skip(stream)
|
||||||
get() = ITileState
|
ILiquidState.skip(stream)
|
||||||
override val liquid: ILiquidState
|
stream.skipNBytes(1 + 2 + 1 + 1 + 1 + 1)
|
||||||
get() = ILiquidState
|
}
|
||||||
override var dungeonId: Int
|
|
||||||
get() = 0
|
|
||||||
set(value) { throw UnsupportedOperationException("Empty chunk cell") }
|
|
||||||
override var biome: Int
|
|
||||||
get() = 0
|
|
||||||
set(value) { throw UnsupportedOperationException("Empty chunk cell") }
|
|
||||||
override var envBiome: Int
|
|
||||||
get() = 0
|
|
||||||
set(value) { throw UnsupportedOperationException("Empty chunk cell") }
|
|
||||||
override var isIndestructible: Boolean
|
|
||||||
get() = false
|
|
||||||
set(value) { throw UnsupportedOperationException("Empty chunk cell") }
|
|
||||||
override val isEmpty: Boolean
|
|
||||||
get() = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -10,8 +10,6 @@ interface ILiquidState {
|
|||||||
var pressure: Float
|
var pressure: Float
|
||||||
var isInfinite: Boolean
|
var isInfinite: Boolean
|
||||||
|
|
||||||
val isEmpty: Boolean
|
|
||||||
|
|
||||||
fun read(
|
fun read(
|
||||||
liquidAccess: (Int) -> RegistryObject<LiquidDefinition>?,
|
liquidAccess: (Int) -> RegistryObject<LiquidDefinition>?,
|
||||||
stream: DataInputStream
|
stream: DataInputStream
|
||||||
@ -22,20 +20,9 @@ interface ILiquidState {
|
|||||||
isInfinite = stream.readBoolean()
|
isInfinite = stream.readBoolean()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : ILiquidState {
|
companion object {
|
||||||
override var def: LiquidDefinition?
|
fun skip(stream: DataInputStream) {
|
||||||
get() = null
|
stream.skipNBytes(1 + 4 + 4 + 1)
|
||||||
set(value) { throw UnsupportedOperationException("Empty liquid state") }
|
}
|
||||||
override var level: Float
|
|
||||||
get() = 0f
|
|
||||||
set(value) { throw UnsupportedOperationException("Empty liquid state") }
|
|
||||||
override var pressure: Float
|
|
||||||
get() = 0f
|
|
||||||
set(value) { throw UnsupportedOperationException("Empty liquid state") }
|
|
||||||
override var isInfinite: Boolean
|
|
||||||
get() = false
|
|
||||||
set(value) { throw UnsupportedOperationException("Empty liquid state") }
|
|
||||||
override val isEmpty: Boolean
|
|
||||||
get() = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,20 +5,30 @@ import ru.dbotthepony.kvector.api.IStruct2i
|
|||||||
// for getting tiles directly, avoiding manual layer specification
|
// for getting tiles directly, avoiding manual layer specification
|
||||||
interface ITileAccess : ICellAccess {
|
interface ITileAccess : ICellAccess {
|
||||||
// relative
|
// relative
|
||||||
fun getTile(x: Int, y: Int): ITileState
|
fun getTile(x: Int, y: Int): ITileState?
|
||||||
fun getTile(pos: IStruct2i) = getTile(pos.component1(), pos.component2())
|
fun getTile(pos: IStruct2i) = getTile(pos.component1(), pos.component2())
|
||||||
|
fun getTileDirect(x: Int, y: Int): ITileState?
|
||||||
|
fun getTileDirect(pos: IStruct2i) = getTile(pos.component1(), pos.component2())
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class TileView(parent: ICellAccess) : ITileAccess, ICellAccess by parent {
|
sealed class TileView(parent: ICellAccess) : ITileAccess, ICellAccess by parent {
|
||||||
class Foreground(parent: ICellAccess) : TileView(parent) {
|
class Foreground(parent: ICellAccess) : TileView(parent) {
|
||||||
override fun getTile(x: Int, y: Int): ITileState {
|
override fun getTile(x: Int, y: Int): ITileState? {
|
||||||
return getCell(x, y).foreground
|
return getCell(x, y)?.foreground
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTileDirect(x: Int, y: Int): ITileState? {
|
||||||
|
return getCellDirect(x, y)?.foreground
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Background(parent: ICellAccess) : TileView(parent) {
|
class Background(parent: ICellAccess) : TileView(parent) {
|
||||||
override fun getTile(x: Int, y: Int): ITileState {
|
override fun getTile(x: Int, y: Int): ITileState? {
|
||||||
return getCell(x, y).background
|
return getCell(x, y)?.background
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTileDirect(x: Int, y: Int): ITileState? {
|
||||||
|
return getCellDirect(x, y)?.background
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,6 @@ interface ITileState {
|
|||||||
var hueShift: Float
|
var hueShift: Float
|
||||||
var modifierHueShift: Float
|
var modifierHueShift: Float
|
||||||
|
|
||||||
val isEmpty: Boolean
|
|
||||||
|
|
||||||
fun setHueShift(value: Int) {
|
fun setHueShift(value: Int) {
|
||||||
if (value < 0) {
|
if (value < 0) {
|
||||||
hueShift = 0f
|
hueShift = 0f
|
||||||
@ -46,23 +44,9 @@ interface ITileState {
|
|||||||
setModHueShift(stream.read())
|
setModHueShift(stream.read())
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : ITileState {
|
companion object {
|
||||||
override var material: TileDefinition?
|
fun skip(stream: DataInputStream) {
|
||||||
get() = null
|
stream.skipNBytes(2 + 1 + 1 + 2 + 1)
|
||||||
set(value) { throw UnsupportedOperationException("Empty tile state") }
|
}
|
||||||
override var modifier: MaterialModifier?
|
|
||||||
get() = null
|
|
||||||
set(value) { throw UnsupportedOperationException("Empty tile state") }
|
|
||||||
override var color: TileColor
|
|
||||||
get() = TileColor.DEFAULT
|
|
||||||
set(value) { throw UnsupportedOperationException("Empty tile state") }
|
|
||||||
override var hueShift: Float
|
|
||||||
get() = 0f
|
|
||||||
set(value) { throw UnsupportedOperationException("Empty tile state") }
|
|
||||||
override var modifierHueShift: Float
|
|
||||||
get() = 0f
|
|
||||||
set(value) { throw UnsupportedOperationException("Empty tile state") }
|
|
||||||
override val isEmpty: Boolean
|
|
||||||
get() = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -94,7 +94,7 @@ abstract class Entity(override val world: World<*, *>) : IEntity {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val chunkPos = world.chunkMap.cellToGrid(position)
|
val chunkPos = world.chunkMap.cellToChunk(position)
|
||||||
|
|
||||||
if (value != null && chunkPos != value.pos) {
|
if (value != null && chunkPos != value.pos) {
|
||||||
throw IllegalStateException("Set proper position before setting chunk this Entity belongs to (expected chunk $chunkPos, got chunk ${value.pos})")
|
throw IllegalStateException("Set proper position before setting chunk this Entity belongs to (expected chunk $chunkPos, got chunk ${value.pos})")
|
||||||
@ -125,8 +125,8 @@ abstract class Entity(override val world: World<*, *>) : IEntity {
|
|||||||
movement.notifyPositionChanged()
|
movement.notifyPositionChanged()
|
||||||
|
|
||||||
if (isSpawned && !isRemoved) {
|
if (isSpawned && !isRemoved) {
|
||||||
val oldChunkPos = world.chunkMap.cellToGrid(old)
|
val oldChunkPos = world.chunkMap.cellToChunk(old)
|
||||||
val newChunkPos = world.chunkMap.cellToGrid(field)
|
val newChunkPos = world.chunkMap.cellToChunk(field)
|
||||||
|
|
||||||
if (oldChunkPos != newChunkPos) {
|
if (oldChunkPos != newChunkPos) {
|
||||||
chunk = world.chunkMap[newChunkPos]
|
chunk = world.chunkMap[newChunkPos]
|
||||||
@ -154,7 +154,7 @@ abstract class Entity(override val world: World<*, *>) : IEntity {
|
|||||||
|
|
||||||
isSpawned = true
|
isSpawned = true
|
||||||
world.entities.add(this)
|
world.entities.add(this)
|
||||||
chunk = world.chunkMap[world.chunkMap.cellToGrid(position)]
|
chunk = world.chunkMap[world.chunkMap.cellToChunk(position)]
|
||||||
|
|
||||||
if (chunk == null) {
|
if (chunk == null) {
|
||||||
world.orphanedEntities.add(this)
|
world.orphanedEntities.add(this)
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.phys
|
package ru.dbotthepony.kstarbound.world.phys
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_BITS
|
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_BITS
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_FF
|
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF
|
||||||
|
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
||||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||||
import ru.dbotthepony.kvector.arrays.Object2DArray
|
import ru.dbotthepony.kvector.arrays.Object2DArray
|
||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
|
|
||||||
class RectTileFlooderDepthFirst(
|
class RectTileFlooderDepthFirst(
|
||||||
private val tiles: Object2DArray<out IChunkCell>,
|
private val tiles: ICellAccess,
|
||||||
private val seen: BooleanArray,
|
private val seen: BooleanArray,
|
||||||
rootx: Int,
|
rootx: Int,
|
||||||
rooty: Int
|
rooty: Int
|
||||||
@ -24,7 +25,7 @@ class RectTileFlooderDepthFirst(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return !seen[x or (y shl CHUNK_SIZE_BITS)] && tiles[x, y].foreground.material != null
|
return !seen[x or (y shl CHUNK_SIZE_BITS)] && tiles.getCell(x, y)?.foreground?.material != null
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.phys
|
package ru.dbotthepony.kstarbound.world.phys
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_BITS
|
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_BITS
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_FF
|
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF
|
||||||
|
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
||||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||||
import ru.dbotthepony.kvector.arrays.Object2DArray
|
import ru.dbotthepony.kvector.arrays.Object2DArray
|
||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
|
|
||||||
class RectTileFlooderSizeFirst(
|
class RectTileFlooderSizeFirst(
|
||||||
private val tiles: Object2DArray<out IChunkCell>,
|
private val tiles: ICellAccess,
|
||||||
private val seen: BooleanArray,
|
private val seen: BooleanArray,
|
||||||
private val rootx: Int,
|
private val rootx: Int,
|
||||||
private val rooty: Int
|
private val rooty: Int
|
||||||
@ -24,7 +25,7 @@ class RectTileFlooderSizeFirst(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return !seen[x or (y shl CHUNK_SIZE_BITS)] && tiles[x, y].foreground.material != null
|
return !seen[x or (y shl CHUNK_SIZE_BITS)] && tiles.getCell(x, y)?.foreground?.material != null
|
||||||
}
|
}
|
||||||
|
|
||||||
private var widthPositive = 0
|
private var widthPositive = 0
|
||||||
|
@ -3,8 +3,8 @@ package ru.dbotthepony.kstarbound.test
|
|||||||
import org.junit.jupiter.api.DisplayName
|
import org.junit.jupiter.api.DisplayName
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import ru.dbotthepony.kstarbound.math.*
|
import ru.dbotthepony.kstarbound.math.*
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE
|
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_FF
|
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF
|
||||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||||
|
|
||||||
object MathTests {
|
object MathTests {
|
||||||
|
Loading…
Reference in New Issue
Block a user