From 94fe3662ad81e5393303a973022f1ee3c4cf9962 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Tue, 5 Sep 2023 16:09:09 +0700 Subject: [PATCH] Arbitrary dimension chunks, null/not null cell distinction --- .../kotlin/ru/dbotthepony/kstarbound/Main.kt | 17 +- .../kstarbound/client/ClientChunk.kt | 41 ++- .../kstarbound/client/ClientWorld.kt | 31 +- .../kstarbound/client/StarboundClient.kt | 2 +- .../kstarbound/client/render/TileRenderer.kt | 8 +- .../kstarbound/defs/tile/RenderTemplate.kt | 4 +- .../ru/dbotthepony/kstarbound/math/AABB.kt | 7 - .../ru/dbotthepony/kstarbound/world/Chunk.kt | 89 ++--- .../dbotthepony/kstarbound/world/ChunkPos.kt | 79 +---- .../kstarbound/world/CoordinateMapper.kt | 114 ++++++ .../dbotthepony/kstarbound/world/Helpers.kt | 64 ---- .../kstarbound/world/Raycasting.kt | 2 +- .../ru/dbotthepony/kstarbound/world/World.kt | 326 ++++++++---------- .../kstarbound/world/api/Constants.kt | 8 - .../kstarbound/world/api/ICellAccess.kt | 26 +- .../kstarbound/world/api/IChunkCell.kt | 38 +- .../kstarbound/world/api/ILiquidState.kt | 21 +- .../kstarbound/world/api/ITileAccess.kt | 20 +- .../kstarbound/world/api/ITileState.kt | 24 +- .../kstarbound/world/entities/Entity.kt | 8 +- .../world/phys/RectTileFlooderDepthFirst.kt | 9 +- .../world/phys/RectTileFlooderSizeFirst.kt | 9 +- .../dbotthepony/kstarbound/test/MathTests.kt | 4 +- 23 files changed, 444 insertions(+), 507 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/CoordinateMapper.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/Helpers.kt delete mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/api/Constants.kt diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index 10b96b3c..17c2e0e6 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -13,6 +13,7 @@ import ru.dbotthepony.kstarbound.player.QuestDescriptor import ru.dbotthepony.kstarbound.player.QuestInstance import ru.dbotthepony.kstarbound.util.JVMTimeSource 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.PlayerEntity import ru.dbotthepony.kvector.vector.Vector2d @@ -74,7 +75,6 @@ fun main() { find += System.currentTimeMillis() - t if (data != null) { - val chunk = client.world!!.chunkMap.computeIfAbsent(ChunkPos(chunkX, chunkY)) val inflater = Inflater() inflater.setInput(data) @@ -90,7 +90,13 @@ fun main() { for (y 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(600.0 + 16.0, 721.0 + 48.0) - client.camera.pos = Vector2f(-2967f, 685f) + client.camera.pos = Vector2f(238f, 685f) client.onDrawGUI { 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("${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: ${client.world!!.chunkMap.cellToGrid(client.camera.pos.toDoubleVector())}", y = 180f, scale = 0.25f) + client.gl.font.render("Camera: ${client.camera.pos} ${client.settings.zoom}", y = 140f, scale = 0.25f) + client.gl.font.render("World chunk: ${client.world!!.chunkMap.cellToChunk(client.camera.pos.toDoubleVector())}", y = 160f, scale = 0.25f) } client.onPreDrawWorld { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt index 49068cab..e990bfeb 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt @@ -8,14 +8,12 @@ import ru.dbotthepony.kstarbound.client.render.LayeredRenderer import ru.dbotthepony.kstarbound.client.render.TileLayerList import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition import ru.dbotthepony.kstarbound.world.* -import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE -import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEd -import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEf +import ru.dbotthepony.kstarbound.world.CHUNK_SIZEd import ru.dbotthepony.kstarbound.world.api.ITileAccess import ru.dbotthepony.kstarbound.world.entities.Entity -import ru.dbotthepony.kvector.arrays.Matrix4fStack import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2f +import ru.dbotthepony.kvector.vector.Vector2i import java.io.Closeable import java.util.LinkedList @@ -28,13 +26,13 @@ import java.util.LinkedList const val Z_LEVEL_BACKGROUND = 60000 const val Z_LEVEL_LIQUID = 10000 -class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk(world, pos), Closeable { +class ClientChunk(world: ClientWorld, pos: ChunkPos, width: Int, height: Int) : Chunk(world, pos, width, height), Closeable { val state: GLStateTracker get() = world.client.gl private inner class TileLayerRenderer(private val view: ITileAccess, private val isBackground: Boolean) : AutoCloseable { private val layers = TileLayerList() val bakedMeshes = LinkedList>() - var isDirty = true + var isDirty = false fun bake() { if (!isDirty) return @@ -50,19 +48,20 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index 0d347088..a533de5d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -227,7 +227,7 @@ class StarboundClient(val starbound: Starbound) : Closeable { val tileRenderers = TileRenderers(this) - var world: ClientWorld? = ClientWorld(this, 0L, Vector2i(3000, 2000), true) + var world: ClientWorld? = ClientWorld(this, 0L, null, true) init { putDebugLog("Initialized OpenGL context") diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt index 8b458a85..ad3ebf8d 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/TileRenderer.kt @@ -168,14 +168,14 @@ class TileRenderers(val client: StarboundClient) { private fun vertexTextureBuilder() = VertexBuilder(GLAttributeList.TILE, GeometryType.QUADS) private class TileEqualityTester(val definition: TileDefinition) : EqualityRuleTester { - override fun test(thisTile: ITileState, otherTile: ITileState): Boolean { - return otherTile.material == definition && thisTile.hueShift == otherTile.hueShift + override fun test(thisTile: ITileState?, otherTile: ITileState?): Boolean { + return otherTile?.material == definition && thisTile?.hueShift == otherTile.hueShift } } private class ModifierEqualityTester(val definition: MaterialModifier) : EqualityRuleTester { - override fun test(thisTile: ITileState, otherTile: ITileState): Boolean { - return otherTile.modifier == definition + override fun test(thisTile: ITileState?, otherTile: ITileState?): Boolean { + return otherTile?.modifier == definition } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt index 4b3c680d..51c2880f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/defs/tile/RenderTemplate.kt @@ -20,7 +20,7 @@ data class RenderPiece( ) fun interface EqualityRuleTester { - fun test(thisTile: ITileState, otherTile: ITileState): Boolean + fun test(thisTile: ITileState?, otherTile: ITileState?): Boolean } @JsonFactory @@ -50,7 +50,7 @@ data class RenderRuleList( private object Conntects : ActualTester { 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 } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/math/AABB.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/math/AABB.kt index fee7bfb1..b6fee9a5 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/math/AABB.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/math/AABB.kt @@ -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(private val aabb: AABBi, private val factory: (x: Int, y: Int) -> T) : kotlin.collections.Iterator { private var x = aabb.mins.x private var y = aabb.mins.y diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt index be757259..5e1d8105 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt @@ -1,5 +1,6 @@ package ru.dbotthepony.kstarbound.world +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet import ru.dbotthepony.kbox2d.api.BodyDef import ru.dbotthepony.kbox2d.api.FixtureDef 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.MaterialModifier import ru.dbotthepony.kstarbound.defs.tile.TileDefinition -import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_BITS -import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE -import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_FF -import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEd +import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_BITS +import ru.dbotthepony.kstarbound.world.CHUNK_SIZE +import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF +import ru.dbotthepony.kstarbound.world.CHUNK_SIZEd import ru.dbotthepony.kstarbound.world.api.ICellAccess import ru.dbotthepony.kstarbound.world.api.IChunkCell import ru.dbotthepony.kstarbound.world.api.ILiquidState @@ -38,9 +39,22 @@ import kotlin.collections.HashSet * * Весь игровой мир будет измеряться в Starbound Unit'ах */ -abstract class Chunk, This : Chunk>(val world: WorldType, val pos: ChunkPos) : ICellAccess { +abstract class Chunk, This : Chunk>( + val world: WorldType, + val pos: ChunkPos, + val width: Int, + val height: Int +) : ICellAccess { var changeset = 0 - private set + private set(value) { + field = value + + if (isEmpty) { + isEmpty = false + world.chunkMap.promote(this as This) + } + } + var tileChangeset = 0 private set var liquidChangeset = 0 @@ -55,24 +69,21 @@ abstract class Chunk, This : Chunk(width, height) } - // dimensions of this chunk with accounting for world boundaries - val inWorldWidth: Int - val inWorldHeight: Int + override fun getCell(x: Int, y: Int): IChunkCell { + var get = cells[x, y] - init { - if (world.size == null) { - inWorldWidth = CHUNK_SIZE - inWorldHeight = CHUNK_SIZE - } else { - inWorldWidth = (world.size.x - pos.tileX).coerceAtMost(CHUNK_SIZE) - inWorldHeight = (world.size.y - pos.tileY).coerceAtMost(CHUNK_SIZE) + if (get == null) { + get = Cell(x, y) + cells[x, y] = get } + + return get } // local cells' tile access @@ -80,7 +91,7 @@ abstract class Chunk, This : Chunk, This : Chunk() private val collisionCacheView = Collections.unmodifiableCollection(collisionCache) - private val body = world.physics.createBody(BodyDef( - position = pos.tile.toDoubleVector(), - userData = this - )) + private val body by lazy { + world.physics.createBody(BodyDef( + position = pos.tile(width, height).toDoubleVector(), + userData = this + )) + } private val collisionChains = ArrayList() @@ -135,7 +148,10 @@ abstract class Chunk, This : Chunk, This : Chunk, This : Chunk, This : Chunk, This : Chunk, This : Chunk, This : Chunk() - val entitiesAccess: Set = Collections.unmodifiableSet(entities) + protected val entities = ReferenceOpenHashSet() protected abstract fun onEntityAdded(entity: Entity) protected abstract fun onEntityTransferedToThis(entity: Entity, otherChunk: This) @@ -351,6 +357,7 @@ abstract class Chunk, This : Chunk, This : Chunk, This : Chunk { constructor(pos: IStruct2i) : this(pos.component1(), pos.component2()) - // bottom left corner - val tileX: Int = x shl CHUNK_SIZE_BITS - val tileY: Int = y shl CHUNK_SIZE_BITS - val tile = Vector2i(tileX, tileY) + fun tile(width: Int, height: Int): Vector2i { + return Vector2i(x * width, y * height) + } - val top: ChunkPos - get() { + val top: ChunkPos get() { return ChunkPos(x, y + 1) } - val bottom: ChunkPos - get() { + val bottom: ChunkPos get() { return ChunkPos(x, y - 1) } - val left: ChunkPos - get() { + val left: ChunkPos get() { return ChunkPos(x - 1, y) } - val topLeft: ChunkPos - get() { + val topLeft: ChunkPos get() { return ChunkPos(x - 1, y + 1) } - val topRight: ChunkPos - get() { + val topRight: ChunkPos get() { return ChunkPos(x + 1, y + 1) } - val bottomLeft: ChunkPos - get() { + val bottomLeft: ChunkPos get() { return ChunkPos(x - 1, y - 1) } - val bottomRight: ChunkPos - get() { + val bottomRight: ChunkPos get() { return ChunkPos(x + 1, y - 1) } - val right: ChunkPos - get() { + val right: ChunkPos get() { return ChunkPos(x + 1, y) } @@ -114,54 +105,8 @@ data class ChunkPos(val x: Int, val y: Int) : IStruct2i, Comparable { } companion object { - val ZERO = ChunkPos(0, 0) - fun toLong(x: Int, y: Int): Long { 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 - } } } \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/CoordinateMapper.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/CoordinateMapper.kt new file mode 100644 index 00000000..ccac407d --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/CoordinateMapper.kt @@ -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) + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Helpers.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Helpers.kt deleted file mode 100644 index 6d729610..00000000 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Helpers.kt +++ /dev/null @@ -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> { - return object : Iterator> { - private var x = fromX - private var y = fromY - - override fun hasNext(): Boolean { - return x < toX && y < toY - } - - override fun next(): Pair { - 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> { - return object : Iterator> { - private var x = fromX - private var y = fromY - - override fun hasNext(): Boolean { - return x < toX && y < toY - } - - override fun next(): Pair { - 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 - } - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Raycasting.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Raycasting.kt index 86e939a2..00949194 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Raycasting.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Raycasting.kt @@ -124,7 +124,7 @@ fun ICellAccess.castRayNaive( val tilePos = Vector2i(x.roundToInt(), y.roundToInt()) if (tilePos != prev) { - val tile = getCell(tilePos) + val tile = getCell(tilePos) ?: break when (filter.test(tile, t, tilePos)) { RayFilterResult.HIT -> { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt index a0a59445..b7fe12e4 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt @@ -13,9 +13,6 @@ import ru.dbotthepony.kbox2d.dynamics.B2World import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact import ru.dbotthepony.kstarbound.math.* 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.IChunkCell 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.vector.Vector2d 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, ChunkType : Chunk>( val seed: Long, @@ -35,164 +40,73 @@ abstract class World, ChunkType : Chunk= 0) { - return (chunk / this.chunk) * misalignedTiles2 - } else { - return ((chunk + 1) / this.chunk - 1) * misalignedTiles2 + private fun determineChunkSize(cells: Int): Int { + for (i in 32 downTo 1) { + if (cells % i == 0) { + return i } } - override fun cell(value: Int): Int { - 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) - } + throw RuntimeException("unreachable code") } - class CoordinatesClamper(private val cell: Int, private val chunk: Int) : AbstractCoordinatesWrapper() { - override fun inBounds(value: Int): Boolean { - 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) - } - } + val chunkWidth = size?.x?.let(::determineChunkSize) ?: CHUNK_SIZE + val chunkHeight = size?.y?.let(::determineChunkSize) ?: CHUNK_SIZE abstract inner class ChunkMap : ICellAccess { - abstract val x: AbstractCoordinatesWrapper - abstract val y: AbstractCoordinatesWrapper - - 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 - } + abstract val x: CoordinateMapper + abstract val y: CoordinateMapper val background = TileView.Background(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? operator fun get(pos: ChunkPos) = get(pos.x, pos.y) - open fun chunkFromCell(x: Int, y: Int): ChunkType? { - return get(ChunkPos.component(x), ChunkPos.component(y)) + abstract fun promote(self: ChunkType) + + protected val queue = ReferenceQueue() + + abstract fun purge() + + protected inner class Ref(chunk: ChunkType) : WeakReference(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) - fun cellToGrid(x: Int, y: Int) = ChunkPos.fromPosition(this.x.cell(x), this.y.cell(y)) - fun cellToGrid(x: Double, y: Double) = ChunkPos.fromPosition(this.x.cell(x), this.y.cell(y)) - fun cellToGrid(position: IStruct2i) = cellToGrid(position.component1(), position.component2()) - fun cellToGrid(position: IStruct2d) = cellToGrid(position.component1(), position.component2()) + override fun getCell(x: Int, y: Int): IChunkCell? { + if (!this.x.isValidCellIndex(x) || !this.y.isValidCellIndex(y)) return null + val ix = this.x.cell(x) + 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 { - val existing = get(x, y) - - if (existing != null) - return existing - - val pos = ChunkPos(this.x.chunk(x), this.y.chunk(y)) + protected fun create(x: Int, y: Int): ChunkType { + purge() + val pos = ChunkPos(x, y) val chunk = chunkFactory(pos) val orphanedInThisChunk = ArrayList() for (ent in orphanedEntities) { 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) } } @@ -201,65 +115,124 @@ abstract class World, ChunkType : Chunk() + private val map = Long2ObjectOpenHashMap() + override val x = CoordinateMapper.Infinite + override val y = CoordinateMapper.Infinite - override fun get(x: Int, y: Int): ChunkType? { - return map[ChunkPos.toLong(x, y)] + override fun get(x: Int, y: Int): ChunkType { + 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) { - map[ChunkPos.toLong(x, y)] = chunk + override fun purge() { + 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) { - map.remove(ChunkPos.toLong(x, y)) - } + val ref = map.remove(ChunkPos.toLong(x, y)) - override val x = PassthroughWrapper - override val y = PassthroughWrapper + if (ref is World<*, *>.ChunkMap.Ref) { + ref.clear() + } + } } inner class RectChunkMap : ChunkMap() { val width = size!!.x val height = size!!.y - val widthInChunks = if (width and CHUNK_SIZE_MASK == 0) width / CHUNK_SIZE else width / CHUNK_SIZE + 1 - val heightInChunks = if (height and CHUNK_SIZE_MASK == 0) height / CHUNK_SIZE else height / CHUNK_SIZE + 1 + override val x: CoordinateMapper = if (loopX) CoordinateMapper.Wrapper(width, chunkWidth) else CoordinateMapper.Clamper(width, chunkWidth) + 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) - override val y: AbstractCoordinatesWrapper = if (loopY) CoordinatesWrapper(height, heightInChunks) else CoordinatesClamper(height, heightInChunks) - - private val map = Object2DArray.nulls(widthInChunks, heightInChunks) + private val map = Object2DArray.nulls(width / chunkWidth, height / chunkHeight) 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? { - val ix = this.x.cell(x) - val iy = this.y.cell(y) - return get(ix ushr CHUNK_SIZE_BITS, iy ushr CHUNK_SIZE_BITS) + override fun purge() { + while (queue.poll() != null) {} } - override fun getCell(x: Int, y: Int): IChunkCell { - val ix = this.x.cell(x) - 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 promote(self: ChunkType) { + val (x, y) = self.pos - override fun set(x: Int, y: Int, chunk: ChunkType) { - map[this.x.chunk(x), this.y.chunk(y)] = chunk + val ref = map[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[x, y] = self } 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, ChunkType : Chunk, ChunkType : Chunk> { - val output = ArrayList>() - - 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 = Predicate { it.foreground.material != null }): Boolean { val tiles = aabb.encasingIntAABB() for (x in tiles.mins.x .. tiles.maxs.x) { 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 } } @@ -482,13 +438,13 @@ abstract class World, ChunkType : Chunk RegistryObject?, modifierAccess: (Int) -> RegistryObject?, @@ -28,36 +30,22 @@ interface IChunkCell { background.read(materialAccess, modifierAccess, stream) liquid.read(liquidAccess, stream) - stream.skipBytes(1) // collisionMap + stream.skipNBytes(1) // collisionMap dungeonId = stream.readUnsignedShort() biome = stream.readUnsignedByte() envBiome = stream.readUnsignedByte() isIndestructible = stream.readBoolean() - stream.skipBytes(1) // unknown + stream.skipNBytes(1) // unknown } - companion object : IChunkCell { - override val foreground: ITileState - get() = ITileState - override val background: ITileState - get() = ITileState - override val liquid: ILiquidState - 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 + companion object { + fun skip(stream: DataInputStream) { + ITileState.skip(stream) + ITileState.skip(stream) + ILiquidState.skip(stream) + stream.skipNBytes(1 + 2 + 1 + 1 + 1 + 1) + } } } \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ILiquidState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ILiquidState.kt index 92ef99bc..592c280f 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ILiquidState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ILiquidState.kt @@ -10,8 +10,6 @@ interface ILiquidState { var pressure: Float var isInfinite: Boolean - val isEmpty: Boolean - fun read( liquidAccess: (Int) -> RegistryObject?, stream: DataInputStream @@ -22,20 +20,9 @@ interface ILiquidState { isInfinite = stream.readBoolean() } - companion object : ILiquidState { - override var def: LiquidDefinition? - get() = null - 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 + companion object { + fun skip(stream: DataInputStream) { + stream.skipNBytes(1 + 4 + 4 + 1) + } } } \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ITileAccess.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ITileAccess.kt index 831ef4e9..7411d3dd 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ITileAccess.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ITileAccess.kt @@ -5,20 +5,30 @@ import ru.dbotthepony.kvector.api.IStruct2i // for getting tiles directly, avoiding manual layer specification interface ITileAccess : ICellAccess { // 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 getTileDirect(x: Int, y: Int): ITileState? + fun getTileDirect(pos: IStruct2i) = getTile(pos.component1(), pos.component2()) } sealed class TileView(parent: ICellAccess) : ITileAccess, ICellAccess by parent { class Foreground(parent: ICellAccess) : TileView(parent) { - override fun getTile(x: Int, y: Int): ITileState { - return getCell(x, y).foreground + override fun getTile(x: Int, y: Int): ITileState? { + return getCell(x, y)?.foreground + } + + override fun getTileDirect(x: Int, y: Int): ITileState? { + return getCellDirect(x, y)?.foreground } } class Background(parent: ICellAccess) : TileView(parent) { - override fun getTile(x: Int, y: Int): ITileState { - return getCell(x, y).background + override fun getTile(x: Int, y: Int): ITileState? { + return getCell(x, y)?.background + } + + override fun getTileDirect(x: Int, y: Int): ITileState? { + return getCellDirect(x, y)?.background } } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ITileState.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ITileState.kt index d83a6048..cab744c5 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ITileState.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/api/ITileState.kt @@ -12,8 +12,6 @@ interface ITileState { var hueShift: Float var modifierHueShift: Float - val isEmpty: Boolean - fun setHueShift(value: Int) { if (value < 0) { hueShift = 0f @@ -46,23 +44,9 @@ interface ITileState { setModHueShift(stream.read()) } - companion object : ITileState { - override var material: TileDefinition? - get() = null - 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 + companion object { + fun skip(stream: DataInputStream) { + stream.skipNBytes(2 + 1 + 1 + 2 + 1) + } } } \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/Entity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/Entity.kt index b801db7a..35cd5025 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/Entity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/Entity.kt @@ -94,7 +94,7 @@ abstract class Entity(override val world: World<*, *>) : IEntity { return } - val chunkPos = world.chunkMap.cellToGrid(position) + val chunkPos = world.chunkMap.cellToChunk(position) 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})") @@ -125,8 +125,8 @@ abstract class Entity(override val world: World<*, *>) : IEntity { movement.notifyPositionChanged() if (isSpawned && !isRemoved) { - val oldChunkPos = world.chunkMap.cellToGrid(old) - val newChunkPos = world.chunkMap.cellToGrid(field) + val oldChunkPos = world.chunkMap.cellToChunk(old) + val newChunkPos = world.chunkMap.cellToChunk(field) if (oldChunkPos != newChunkPos) { chunk = world.chunkMap[newChunkPos] @@ -154,7 +154,7 @@ abstract class Entity(override val world: World<*, *>) : IEntity { isSpawned = true world.entities.add(this) - chunk = world.chunkMap[world.chunkMap.cellToGrid(position)] + chunk = world.chunkMap[world.chunkMap.cellToChunk(position)] if (chunk == null) { world.orphanedEntities.add(this) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderDepthFirst.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderDepthFirst.kt index 15b2a75c..8ee5ef88 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderDepthFirst.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderDepthFirst.kt @@ -1,13 +1,14 @@ package ru.dbotthepony.kstarbound.world.phys -import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_BITS -import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_FF +import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_BITS +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.kvector.arrays.Object2DArray import ru.dbotthepony.kvector.vector.Vector2i class RectTileFlooderDepthFirst( - private val tiles: Object2DArray, + private val tiles: ICellAccess, private val seen: BooleanArray, rootx: Int, rooty: Int @@ -24,7 +25,7 @@ class RectTileFlooderDepthFirst( 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 { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderSizeFirst.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderSizeFirst.kt index 2f022713..ade6cec3 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderSizeFirst.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderSizeFirst.kt @@ -1,13 +1,14 @@ package ru.dbotthepony.kstarbound.world.phys -import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_BITS -import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_FF +import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_BITS +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.kvector.arrays.Object2DArray import ru.dbotthepony.kvector.vector.Vector2i class RectTileFlooderSizeFirst( - private val tiles: Object2DArray, + private val tiles: ICellAccess, private val seen: BooleanArray, private val rootx: Int, private val rooty: Int @@ -24,7 +25,7 @@ class RectTileFlooderSizeFirst( 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 diff --git a/src/test/kotlin/ru/dbotthepony/kstarbound/test/MathTests.kt b/src/test/kotlin/ru/dbotthepony/kstarbound/test/MathTests.kt index 34552e93..462143d3 100644 --- a/src/test/kotlin/ru/dbotthepony/kstarbound/test/MathTests.kt +++ b/src/test/kotlin/ru/dbotthepony/kstarbound/test/MathTests.kt @@ -3,8 +3,8 @@ package ru.dbotthepony.kstarbound.test import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import ru.dbotthepony.kstarbound.math.* -import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE -import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_FF +import ru.dbotthepony.kstarbound.world.CHUNK_SIZE +import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF import ru.dbotthepony.kstarbound.world.ChunkPos object MathTests {