From aa9d379d41bf00f7cdd6aa658021b7a14c7fb3e7 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Fri, 29 Jul 2022 14:06:03 +0700 Subject: [PATCH] Move classes around --- .../kstarbound/client/ClientWorld.kt | 25 +- .../kstarbound/util/DoubleEdgeProgression.kt | 25 + .../ru/dbotthepony/kstarbound/util/Timer.kt | 35 + .../ru/dbotthepony/kstarbound/world/Chunk.kt | 739 +----------------- .../dbotthepony/kstarbound/world/ChunkAPI.kt | 133 ++++ .../dbotthepony/kstarbound/world/ChunkPos.kt | 130 +++ .../dbotthepony/kstarbound/world/TileView.kt | 113 +++ .../ru/dbotthepony/kstarbound/world/Tuples.kt | 59 ++ .../ru/dbotthepony/kstarbound/world/World.kt | 92 +-- .../world/phys/RectTileFlooderDepthFirst.kt | 175 +++++ .../world/phys/RectTileFlooderSizeFirst.kt | 144 ++++ .../kstarbound/world/phys/TileFlooder.kt | 74 ++ 12 files changed, 892 insertions(+), 852 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/util/DoubleEdgeProgression.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/util/Timer.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkAPI.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkPos.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/TileView.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/Tuples.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderDepthFirst.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderSizeFirst.kt create mode 100644 src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/TileFlooder.kt diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt index 3837041d..e265f4da 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt @@ -6,34 +6,11 @@ import ru.dbotthepony.kstarbound.client.gl.VertexTransformers import ru.dbotthepony.kstarbound.client.render.renderLayeredList import ru.dbotthepony.kstarbound.defs.ParallaxPrototype import ru.dbotthepony.kstarbound.math.encasingChunkPosAABB +import ru.dbotthepony.kstarbound.util.DoubleEdgeProgression import ru.dbotthepony.kstarbound.world.* import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kvector.util2d.AABB -class DoubleEdgeProgression : Iterator { - var value = 0 - - override fun hasNext(): Boolean { - return true - } - - override fun next(): Int { - return nextInt() - } - - fun nextInt(): Int { - return if (value > 0) { - val ret = value - value = -value - ret - } else { - val ret = value - value = -value + 1 - ret - } - } -} - class ClientWorld(val client: StarboundClient, seed: Long = 0L) : World(seed) { init { physics.debugDraw = client.gl.box2dRenderer diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/DoubleEdgeProgression.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/DoubleEdgeProgression.kt new file mode 100644 index 00000000..27f4dc04 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/DoubleEdgeProgression.kt @@ -0,0 +1,25 @@ +package ru.dbotthepony.kstarbound.util + +class DoubleEdgeProgression : Iterator { + var value = 0 + + override fun hasNext(): Boolean { + return true + } + + override fun next(): Int { + return nextInt() + } + + fun nextInt(): Int { + return if (value > 0) { + val ret = value + value = -value + ret + } else { + val ret = value + value = -value + 1 + ret + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/util/Timer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Timer.kt new file mode 100644 index 00000000..09e75066 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/util/Timer.kt @@ -0,0 +1,35 @@ +package ru.dbotthepony.kstarbound.util + +class Timer(val period: Double, val executionTimes: Int, val func: (Timer) -> Unit) { + private var counter = 0.0 + var cycles = 0 + private set + var destroyed: Boolean = false + private set + + fun think(delta: Double) { + if (destroyed) { + throw IllegalStateException("This timer is destroyed") + } + + counter += delta + + if (counter >= period) { + counter -= period + func.invoke(this) + cycles++ + } + + if (!destroyed && executionTimes > 0 && cycles >= executionTimes) { + destroy() + } + } + + fun destroy() { + if (destroyed) { + throw IllegalStateException("Already destroyed") + } + + destroyed = true + } +} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt index 97e949eb..166fb39c 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt @@ -5,17 +5,14 @@ import ru.dbotthepony.kbox2d.api.FixtureDef import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape import ru.dbotthepony.kbox2d.dynamics.B2Fixture import ru.dbotthepony.kstarbound.defs.TileDefinition -import ru.dbotthepony.kstarbound.math.* import ru.dbotthepony.kstarbound.world.entities.Entity -import ru.dbotthepony.kvector.api.IStruct2d -import ru.dbotthepony.kvector.api.IStruct2i +import ru.dbotthepony.kstarbound.world.phys.RectTileFlooderDepthFirst +import ru.dbotthepony.kstarbound.world.phys.RectTileFlooderSizeFirst import ru.dbotthepony.kvector.util2d.AABB import ru.dbotthepony.kvector.vector.ndouble.Vector2d -import ru.dbotthepony.kvector.vector.nint.Vector2i import java.util.* import kotlin.collections.ArrayList import kotlin.collections.HashSet -import kotlin.math.absoluteValue /** * Представляет из себя класс, который содержит состояние тайла на заданной позиции @@ -34,732 +31,6 @@ data class ChunkTile(val chunk: Chunk<*, *>.TileLayer, val def: TileDefinition) } } -interface ITileMap { - /** - * Относительная проверка находится ли координата вне границ чанка - */ - fun isOutside(x: Int, y: Int): Boolean { - return x !in 0 until CHUNK_SIZE || y !in 0 until CHUNK_SIZE - } -} - -/** - * Предоставляет интерфейс для доступа к тайлам в чанке - */ -interface ITileGetter : ITileMap { - /** - * Возвращает тайл по ОТНОСИТЕЛЬНЫМ координатам внутри чанка - */ - operator fun get(x: Int, y: Int): ChunkTile? - - /** - * Возвращает тайл по ОТНОСИТЕЛЬНЫМ координатам внутри чанка - */ - operator fun get(pos: Vector2i) = get(pos.x, pos.y) - - /** - * Возвращает итератор пар - * - * Вектор имеет ОТНОСИТЕЛЬНЫЕ значения внутри самого чанка - */ - val posToTile: Iterator> get() { - return object : Iterator> { - private var x = 0 - private var y = 0 - - private fun idx() = x + CHUNK_SIZE * y - - override fun hasNext(): Boolean { - return idx() < CHUNK_SIZE * CHUNK_SIZE - } - - override fun next(): Pair { - if (!hasNext()) { - throw IllegalStateException("Already iterated everything!") - } - - val tile = this@ITileGetter[x, y] - val pos = Vector2i(x, y) - - x++ - - if (x >= CHUNK_SIZE) { - y++ - x = 0 - } - - return pos to tile - } - } - } -} - -/** - * Интерфейс предоставляет из себя описание класса, который имеет координаты чанка - */ -interface IChunkPositionable : ITileMap { - val pos: ChunkPos - - /** - * Возвращает псевдослучайное Long для заданной позиции - * - * Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции - */ - fun randomLongFor(x: Int, y: Int): Long { - var long = (x or (pos.x shl CHUNK_SHIFT)) * 738548L + (y or (pos.y shl CHUNK_SHIFT)) * 2191293543L - long = long xor 8339437585692L - long = (long ushr 4) or (long shl 52) - long *= 7848344324L - long = (long ushr 12) or (long shl 44) - return long - } - - /** - * Возвращает псевдослучайное нормализированное Double для заданной позиции - * - * Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции - */ - fun randomDoubleFor(x: Int, y: Int): Double { - return (randomLongFor(x, y) / 9.223372036854776E18) / 2.0 + 0.5 - } - - /** - * Возвращает псевдослучайное Long для заданной позиции - * - * Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции - */ - fun randomLongFor(pos: Vector2i) = randomLongFor(pos.x, pos.y) - - /** - * Возвращает псевдослучайное нормализированное Double для заданной позиции - * - * Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции - */ - fun randomDoubleFor(pos: Vector2i) = randomDoubleFor(pos.x, pos.y) -} - -/** - * Предоставляет интерфейс по установке тайлов в чанке - */ -interface ITileSetter : ITileMap { - /** - * Устанавливает тайл по ОТНОСИТЕЛЬНЫМ координатам внутри чанка - */ - operator fun set(x: Int, y: Int, tile: TileDefinition?): ChunkTile? - /** - * Устанавливает тайл по ОТНОСИТЕЛЬНЫМ координатам внутри чанка - */ - operator fun set(pos: Vector2i, tile: TileDefinition?) = set(pos.x, pos.y, tile) -} - -interface ITileGetterSetter : ITileGetter, ITileSetter -interface ITileChunk : ITileGetter, IChunkPositionable -interface IMutableTileChunk : ITileChunk, ITileSetter - -const val CHUNK_SHIFT = 5 -const val CHUNK_SIZE = 1 shl CHUNK_SHIFT // 32 - -const val CHUNK_SIZE_FF = CHUNK_SIZE - 1 -const val CHUNK_SIZEf = CHUNK_SIZE.toFloat() -const val CHUNK_SIZEd = CHUNK_SIZE.toDouble() - -/** - * Сетка чанков идёт как и сетка тайлов. - * - * * Вправо у нас положительный X - * * Влево у нас отрицательный X - * * Вверх у нас положительный Y - * * Вниз у нас отрицательный Y - */ -class ChunkPos(val x: Int, val y: Int) : Comparable { - constructor(pos: IStruct2i) : this(pos.component1(), pos.component2()) - - val firstBlock get() = Vector2i(x shl CHUNK_SHIFT, y shl CHUNK_SHIFT) - val lastBlock get() = Vector2i(((x + 1) shl CHUNK_SHIFT) - 1, ((y + 1) shl CHUNK_SHIFT) - 1) - - val top: ChunkPos get() { - return ChunkPos(x, y + 1) - } - - val bottom: ChunkPos get() { - return ChunkPos(x, y - 1) - } - - val left: ChunkPos get() { - return ChunkPos(x - 1, y) - } - - val topLeft: ChunkPos get() { - return ChunkPos(x - 1, y + 1) - } - - val topRight: ChunkPos get() { - return ChunkPos(x + 1, y + 1) - } - - val bottomLeft: ChunkPos get() { - return ChunkPos(x - 1, y - 1) - } - - val bottomRight: ChunkPos get() { - return ChunkPos(x + 1, y - 1) - } - - val right: ChunkPos get() { - return ChunkPos(x + 1, y) - } - - override fun equals(other: Any?): Boolean { - if (other is ChunkPos) - return other.x == x && other.y == y - - return false - } - - override fun hashCode(): Int { - return (y shl 16) xor x - } - - override fun toString(): String { - return "ChunkPos[$x $y]" - } - - override fun compareTo(other: ChunkPos): Int { - if (x > other.x) { - return 1 - } else if (x < other.x) { - return -1 - } - - return y.compareTo(other.y) - } - - companion object { - val ZERO = ChunkPos(0, 0) - - fun fromTilePosition(input: IStruct2i): ChunkPos { - val (x, y) = input - return ChunkPos(tileToChunkComponent(x), tileToChunkComponent(y)) - } - - fun fromTilePosition(input: IStruct2d): ChunkPos { - val (x, y) = input - return fromTilePosition(x, y) - } - - fun fromTilePosition(x: Int, y: Int): ChunkPos { - return ChunkPos(tileToChunkComponent(x), tileToChunkComponent(y)) - } - - fun fromTilePosition(x: Double, y: Double): ChunkPos { - return ChunkPos(tileToChunkComponent(roundByAbsoluteValue(x)), tileToChunkComponent(roundByAbsoluteValue(y))) - } - - fun normalizeCoordinate(input: Int): Int { - val band = input and CHUNK_SIZE_FF - - if (band < 0) { - return band + CHUNK_SIZE_FF - } - - return band - } - - fun tileToChunkComponent(comp: Int): Int { - if (comp < 0) { - return -(comp.absoluteValue shr CHUNK_SHIFT) - 1 - } - - return comp shr CHUNK_SHIFT - } - } -} - -/** - * Предоставляет доступ к чанку и его соседям - * - * В основном для использования в местах, где нужен не мир, а определённый чанк мира, - * и при этом координаты проверяются относительно чанка и могут спокойно выйти за его пределы, - * с желанием получить тайл из соседнего чанка - */ -open class TileView( - open val center: ITileChunk, - - open val right: ITileChunk?, - open val top: ITileChunk?, - open val topRight: ITileChunk?, - open val topLeft: ITileChunk?, - - open val left: ITileChunk?, - open val bottom: ITileChunk?, - open val bottomLeft: ITileChunk?, - open val bottomRight: ITileChunk?, -) : ITileChunk { - override fun get(x: Int, y: Int): ChunkTile? { - if (x in 0 .. CHUNK_SIZE_FF) { - if (y in 0 .. CHUNK_SIZE_FF) { - return center[x, y] - } - - if (y < 0) { - return bottom?.get(x, y + CHUNK_SIZE) - } else { - return top?.get(x, y - CHUNK_SIZE) - } - } - - if (x < 0) { - if (y in 0 .. CHUNK_SIZE_FF) { - return left?.get(x + CHUNK_SIZE, y) - } - - if (y < 0) { - return bottomLeft?.get(x + CHUNK_SIZE, y + CHUNK_SIZE) - } else { - return topLeft?.get(x + CHUNK_SIZE, y - CHUNK_SIZE) - } - } else { - if (y in 0 .. CHUNK_SIZE_FF) { - return right?.get(x - CHUNK_SIZE, y) - } - - if (y < 0) { - return bottomRight?.get(x - CHUNK_SIZE, y + CHUNK_SIZE) - } else { - return topRight?.get(x - CHUNK_SIZE, y - CHUNK_SIZE) - } - } - } - - override val pos: ChunkPos - get() = center.pos -} - -class MutableTileView( - override val center: IMutableTileChunk, - - override val right: IMutableTileChunk?, - override val top: IMutableTileChunk?, - override val topRight: IMutableTileChunk?, - override val topLeft: IMutableTileChunk?, - - override val left: IMutableTileChunk?, - override val bottom: IMutableTileChunk?, - override val bottomLeft: IMutableTileChunk?, - override val bottomRight: IMutableTileChunk?, -) : TileView(center, right, top, topRight, topLeft, left, bottom, bottomLeft, bottomRight), IMutableTileChunk { - override fun set(x: Int, y: Int, tile: TileDefinition?): ChunkTile? { - if (x in 0 .. CHUNK_SIZE_FF) { - if (y in 0 .. CHUNK_SIZE_FF) { - return center.set(x, y, tile) - } - - if (y < 0) { - return bottom?.set(x, y + CHUNK_SIZE, tile) - } else { - return top?.set(x, y - CHUNK_SIZE, tile) - } - } - - if (x < 0) { - if (y in 0 .. CHUNK_SIZE_FF) { - return left?.set(x + CHUNK_SIZE, y, tile) - } - - if (y < 0) { - return bottomLeft?.set(x + CHUNK_SIZE, y + CHUNK_SIZE, tile) - } else { - return topLeft?.set(x + CHUNK_SIZE, y - CHUNK_SIZE, tile) - } - } else { - if (y in 0 .. CHUNK_SIZE_FF) { - return right?.set(x - CHUNK_SIZE, y, tile) - } - - if (y < 0) { - return bottomRight?.set(x - CHUNK_SIZE, y + CHUNK_SIZE, tile) - } else { - return topRight?.set(x - CHUNK_SIZE, y - CHUNK_SIZE, tile) - } - } - } -} - -private data class TileExposure( - var pos: Vector2d, - val left: Boolean, - val right: Boolean, - val up: Boolean, - val down: Boolean, -) - -private class TileFlooder( - private val tiles: Array, - private val seen: BooleanArray, - rootx: Int, - rooty: Int -) { - /** - * Tile positions which have at least one face free of neighbours - */ - val exposed = ArrayList() - - private fun get(x: Int, y: Int): Boolean { - if (x < 0 || x > CHUNK_SIZE_FF) { - return false - } - - if (y < 0 || y > CHUNK_SIZE_FF) { - return false - } - - return tiles[x or (y shl CHUNK_SHIFT)] != null - } - - private fun visit(x: Int, y: Int) { - if (seen[x or (y shl CHUNK_SHIFT)]) - return - - seen[x or (y shl CHUNK_SHIFT)] = true - - val left = get(x - 1, y) - val right = get(x + 1, y) - val up = get(x, y + 1) - val down = get(x, y - 1) - - if (!left || !right || !up || !down) { - exposed.add(TileExposure(Vector2d(x.toDouble() + 0.5, y.toDouble() + 0.5), !left, !right, !up, !down)) - } - - if (left) { - visit(x - 1, y) - } - - if (right) { - visit(x + 1, y) - } - - if (up) { - visit(x, y + 1) - } - - if (down) { - visit(x, y - 1) - } - } - - init { - visit(rootx, rooty) - } -} - -private class RectTileFlooderDepthFirst( - private val tiles: Array, - private val seen: BooleanArray, - rootx: Int, - rooty: Int -) { - val mins: Vector2i - val maxs: Vector2i - - private fun filled(x: Int, y: Int): Boolean { - if (x < 0 || x > CHUNK_SIZE_FF) { - return false - } - - if (y < 0 || y > CHUNK_SIZE_FF) { - return false - } - - return !seen[x or (y shl CHUNK_SHIFT)] && tiles[x or (y shl CHUNK_SHIFT)] != null - } - - init { - // expand wide - var widthPositive = 1 - - while (true) { - if (!filled(rootx + widthPositive, rooty)) { - break - } - - widthPositive++ - } - - var widthNegative = 1 - - while (true) { - if (!filled(rootx - widthNegative, rooty)) { - break - } - - widthNegative++ - } - - // expand tall - var heightPositive = 1 - - while (true) { - if (!filled(rootx, rooty + heightPositive)) { - break - } - - heightPositive++ - } - - var heightNegative = 1 - - while (true) { - if (!filled(rootx, rooty - heightNegative)) { - break - } - - heightNegative++ - } - - widthPositive -= 1 - widthNegative -= 1 - - heightNegative -= 1 - heightPositive -= 1 - - if (heightPositive + heightNegative > widthPositive + widthNegative) { - // height is bigger - // try to expand wide - widthPositive = 0 - widthNegative = 0 - - while (true) { - var escape = false - - for (i in -heightNegative .. heightPositive) { - if (!filled(rootx + widthPositive, rooty + i)) { - escape = true - break - } - } - - if (escape) { - break - } - - widthPositive++ - } - - while (true) { - var escape = false - - for (i in -heightNegative .. heightPositive) { - if (!filled(rootx - widthNegative, rooty + i)) { - escape = true - break - } - } - - if (escape) { - break - } - - widthNegative++ - } - - widthNegative -= 1 - widthPositive -= 1 - } else { - // height is equal or lesser than width - // expand high - heightNegative = 0 - heightPositive = 0 - - while (true) { - var escape = false - - for (i in -widthNegative .. widthPositive) { - if (!filled(rootx + i, rooty + heightPositive)) { - escape = true - break - } - } - - if (escape) { - break - } - - heightPositive++ - } - - while (true) { - var escape = false - - for (i in -widthNegative .. widthPositive) { - if (!filled(rootx + i, rooty - heightNegative)) { - escape = true - break - } - } - - if (escape) { - break - } - - heightNegative++ - } - - heightNegative -= 1 - heightPositive -= 1 - } - - mins = Vector2i(rootx - widthNegative, rooty - heightNegative) - maxs = Vector2i(rootx + widthPositive, rooty + heightPositive) - } - - fun markSeen() { - for (x in mins.x .. maxs.x) { - for (y in mins.y .. maxs.y) { - seen[x or (y shl CHUNK_SHIFT)] = true - } - } - } -} - -private class RectTileFlooderSizeFirst( - private val tiles: Array, - private val seen: BooleanArray, - private val rootx: Int, - private val rooty: Int -) { - val mins: Vector2i - val maxs: Vector2i - - private fun filled(x: Int, y: Int): Boolean { - if (x < 0 || x > CHUNK_SIZE_FF) { - return false - } - - if (y < 0 || y > CHUNK_SIZE_FF) { - return false - } - - return !seen[x or (y shl CHUNK_SHIFT)] && tiles[x or (y shl CHUNK_SHIFT)] != null - } - - private var widthPositive = 0 - private var widthNegative = 0 - private var heightPositive = 0 - private var heightNegative = 0 - - private fun checkLeft(): Boolean { - for (i in -heightNegative .. heightPositive) { - if (!filled(rootx - widthNegative, rooty + i)) { - return false - } - } - - return true - } - - private fun checkRight(): Boolean { - for (i in -heightNegative .. heightPositive) { - if (!filled(rootx + widthPositive, rooty + i)) { - return false - } - } - - return true - } - - private fun checkUp(): Boolean { - for (i in -widthNegative .. widthPositive) { - if (!filled(rootx + i, rooty + heightPositive)) { - return false - } - } - - return true - } - - private fun checkDown(): Boolean { - for (i in -widthNegative .. widthPositive) { - if (!filled(rootx + i, rooty - heightNegative)) { - return false - } - } - - return true - } - - init { - var expanded = true - var hitLeft = false - var hitRight = false - var hitUp = false - var hitDown = false - - while (expanded) { - expanded = false - - // expand left - if (!hitLeft) { - widthNegative++ - - if (!checkLeft()) { - widthNegative-- - hitLeft = true - } else { - expanded = true - } - } - - // expand up - if (!hitUp) { - heightPositive++ - - if (!checkUp()) { - heightPositive-- - hitUp = true - } else { - expanded = true - } - } - - // expand right - if (!hitRight) { - widthPositive++ - - if (!checkRight()) { - widthPositive-- - hitRight = true - } else { - expanded = true - } - } - - // expand down - if (!hitDown) { - heightNegative++ - - if (!checkDown()) { - heightNegative-- - hitDown = true - } else { - expanded = true - } - } - } - - mins = Vector2i(rootx - widthNegative, rooty - heightNegative) - maxs = Vector2i(rootx + widthPositive, rooty + heightPositive) - } - - fun markSeen() { - for (x in mins.x .. maxs.x) { - for (y in mins.y .. maxs.y) { - seen[x or (y shl CHUNK_SHIFT)] = true - } - } - } -} - private fun ccwSortScore(point: Vector2d, axis: Vector2d): Double { if (point.x > 0.0) { return point.dot(axis) @@ -1105,9 +376,3 @@ abstract class Chunk, This : Chunk AbstractList.addU(e: E) { - if (indexOf(e) == -1) { - add(e) - } -} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkAPI.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkAPI.kt new file mode 100644 index 00000000..e063e620 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkAPI.kt @@ -0,0 +1,133 @@ +package ru.dbotthepony.kstarbound.world + +import ru.dbotthepony.kstarbound.defs.TileDefinition +import ru.dbotthepony.kvector.vector.nint.Vector2i + +const val CHUNK_SHIFT = 5 +const val CHUNK_SIZE = 1 shl CHUNK_SHIFT // 32 + +const val CHUNK_SIZE_FF = CHUNK_SIZE - 1 +const val CHUNK_SIZEf = CHUNK_SIZE.toFloat() +const val CHUNK_SIZEd = CHUNK_SIZE.toDouble() + +interface ITileMap { + /** + * Относительная проверка находится ли координата вне границ чанка + */ + fun isOutside(x: Int, y: Int): Boolean { + return x !in 0 until CHUNK_SIZE || y !in 0 until CHUNK_SIZE + } +} + +/** + * Предоставляет интерфейс для доступа к тайлам в чанке + */ +interface ITileGetter : ITileMap { + /** + * Возвращает тайл по ОТНОСИТЕЛЬНЫМ координатам внутри чанка + */ + operator fun get(x: Int, y: Int): ChunkTile? + + /** + * Возвращает тайл по ОТНОСИТЕЛЬНЫМ координатам внутри чанка + */ + operator fun get(pos: Vector2i) = get(pos.x, pos.y) + + /** + * Возвращает итератор пар + * + * Вектор имеет ОТНОСИТЕЛЬНЫЕ значения внутри самого чанка + */ + val posToTile: Iterator> get() { + return object : Iterator> { + private var x = 0 + private var y = 0 + + private fun idx() = x + CHUNK_SIZE * y + + override fun hasNext(): Boolean { + return idx() < CHUNK_SIZE * CHUNK_SIZE + } + + override fun next(): Pair { + if (!hasNext()) { + throw IllegalStateException("Already iterated everything!") + } + + val tile = this@ITileGetter[x, y] + val pos = Vector2i(x, y) + + x++ + + if (x >= CHUNK_SIZE) { + y++ + x = 0 + } + + return pos to tile + } + } + } +} + +/** + * Интерфейс предоставляет из себя описание класса, который имеет координаты чанка + */ +interface IChunkPositionable : ITileMap { + val pos: ChunkPos + + /** + * Возвращает псевдослучайное Long для заданной позиции + * + * Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции + */ + fun randomLongFor(x: Int, y: Int): Long { + var long = (x or (pos.x shl CHUNK_SHIFT)) * 738548L + (y or (pos.y shl CHUNK_SHIFT)) * 2191293543L + long = long xor 8339437585692L + long = (long ushr 4) or (long shl 52) + long *= 7848344324L + long = (long ushr 12) or (long shl 44) + return long + } + + /** + * Возвращает псевдослучайное нормализированное Double для заданной позиции + * + * Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции + */ + fun randomDoubleFor(x: Int, y: Int): Double { + return (randomLongFor(x, y) / 9.223372036854776E18) / 2.0 + 0.5 + } + + /** + * Возвращает псевдослучайное Long для заданной позиции + * + * Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции + */ + fun randomLongFor(pos: Vector2i) = randomLongFor(pos.x, pos.y) + + /** + * Возвращает псевдослучайное нормализированное Double для заданной позиции + * + * Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции + */ + fun randomDoubleFor(pos: Vector2i) = randomDoubleFor(pos.x, pos.y) +} + +/** + * Предоставляет интерфейс по установке тайлов в чанке + */ +interface ITileSetter : ITileMap { + /** + * Устанавливает тайл по ОТНОСИТЕЛЬНЫМ координатам внутри чанка + */ + operator fun set(x: Int, y: Int, tile: TileDefinition?): ChunkTile? + /** + * Устанавливает тайл по ОТНОСИТЕЛЬНЫМ координатам внутри чанка + */ + operator fun set(pos: Vector2i, tile: TileDefinition?) = set(pos.x, pos.y, tile) +} + +interface ITileGetterSetter : ITileGetter, ITileSetter +interface ITileChunk : ITileGetter, IChunkPositionable +interface IMutableTileChunk : ITileChunk, ITileSetter diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkPos.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkPos.kt new file mode 100644 index 00000000..4e2b6938 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkPos.kt @@ -0,0 +1,130 @@ +package ru.dbotthepony.kstarbound.world + +import ru.dbotthepony.kstarbound.math.roundByAbsoluteValue +import ru.dbotthepony.kvector.api.IStruct2d +import ru.dbotthepony.kvector.api.IStruct2i +import ru.dbotthepony.kvector.vector.nint.Vector2i +import kotlin.math.absoluteValue + +/** + * Сетка чанков идёт как и сетка тайлов. + * + * * Вправо у нас положительный X + * * Влево у нас отрицательный X + * * Вверх у нас положительный Y + * * Вниз у нас отрицательный Y + */ +class ChunkPos(val x: Int, val y: Int) : Comparable { + constructor(pos: IStruct2i) : this(pos.component1(), pos.component2()) + + val firstBlock get() = Vector2i(x shl CHUNK_SHIFT, y shl CHUNK_SHIFT) + val lastBlock get() = Vector2i(((x + 1) shl CHUNK_SHIFT) - 1, ((y + 1) shl CHUNK_SHIFT) - 1) + + val top: ChunkPos + get() { + return ChunkPos(x, y + 1) + } + + val bottom: ChunkPos + get() { + return ChunkPos(x, y - 1) + } + + val left: ChunkPos + get() { + return ChunkPos(x - 1, y) + } + + val topLeft: ChunkPos + get() { + return ChunkPos(x - 1, y + 1) + } + + val topRight: ChunkPos + get() { + return ChunkPos(x + 1, y + 1) + } + + val bottomLeft: ChunkPos + get() { + return ChunkPos(x - 1, y - 1) + } + + val bottomRight: ChunkPos + get() { + return ChunkPos(x + 1, y - 1) + } + + val right: ChunkPos + get() { + return ChunkPos(x + 1, y) + } + + override fun equals(other: Any?): Boolean { + if (other is ChunkPos) + return other.x == x && other.y == y + + return false + } + + override fun hashCode(): Int { + return (y shl 16) xor x + } + + override fun toString(): String { + return "ChunkPos[$x $y]" + } + + override fun compareTo(other: ChunkPos): Int { + if (x > other.x) { + return 1 + } else if (x < other.x) { + return -1 + } + + return y.compareTo(other.y) + } + + companion object { + val ZERO = ChunkPos(0, 0) + + fun fromTilePosition(input: IStruct2i): ChunkPos { + val (x, y) = input + return ChunkPos(tileToChunkComponent(x), tileToChunkComponent(y)) + } + + fun fromTilePosition(input: IStruct2d): ChunkPos { + val (x, y) = input + return fromTilePosition(x, y) + } + + fun fromTilePosition(x: Int, y: Int): ChunkPos { + return ChunkPos(tileToChunkComponent(x), tileToChunkComponent(y)) + } + + fun fromTilePosition(x: Double, y: Double): ChunkPos { + return ChunkPos( + tileToChunkComponent(roundByAbsoluteValue(x)), + tileToChunkComponent(roundByAbsoluteValue(y)) + ) + } + + fun normalizeCoordinate(input: Int): Int { + val band = input and CHUNK_SIZE_FF + + if (band < 0) { + return band + CHUNK_SIZE_FF + } + + return band + } + + fun tileToChunkComponent(comp: Int): Int { + if (comp < 0) { + return -(comp.absoluteValue shr CHUNK_SHIFT) - 1 + } + + return comp shr CHUNK_SHIFT + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/TileView.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/TileView.kt new file mode 100644 index 00000000..61455f1d --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/TileView.kt @@ -0,0 +1,113 @@ +package ru.dbotthepony.kstarbound.world + +import ru.dbotthepony.kstarbound.defs.TileDefinition + +/** + * Предоставляет доступ к чанку и его соседям + * + * В основном для использования в местах, где нужен не мир, а определённый чанк мира, + * и при этом координаты проверяются относительно чанка и могут спокойно выйти за его пределы, + * с желанием получить тайл из соседнего чанка + */ +open class TileView( + open val center: ITileChunk, + + open val right: ITileChunk?, + open val top: ITileChunk?, + open val topRight: ITileChunk?, + open val topLeft: ITileChunk?, + + open val left: ITileChunk?, + open val bottom: ITileChunk?, + open val bottomLeft: ITileChunk?, + open val bottomRight: ITileChunk?, +) : ITileChunk { + override fun get(x: Int, y: Int): ChunkTile? { + if (x in 0 ..CHUNK_SIZE_FF) { + if (y in 0 ..CHUNK_SIZE_FF) { + return center[x, y] + } + + if (y < 0) { + return bottom?.get(x, y + CHUNK_SIZE) + } else { + return top?.get(x, y - CHUNK_SIZE) + } + } + + if (x < 0) { + if (y in 0 ..CHUNK_SIZE_FF) { + return left?.get(x + CHUNK_SIZE, y) + } + + if (y < 0) { + return bottomLeft?.get(x + CHUNK_SIZE, y + CHUNK_SIZE) + } else { + return topLeft?.get(x + CHUNK_SIZE, y - CHUNK_SIZE) + } + } else { + if (y in 0 ..CHUNK_SIZE_FF) { + return right?.get(x - CHUNK_SIZE, y) + } + + if (y < 0) { + return bottomRight?.get(x - CHUNK_SIZE, y + CHUNK_SIZE) + } else { + return topRight?.get(x - CHUNK_SIZE, y - CHUNK_SIZE) + } + } + } + + override val pos: ChunkPos + get() = center.pos +} + +class MutableTileView( + override val center: IMutableTileChunk, + + override val right: IMutableTileChunk?, + override val top: IMutableTileChunk?, + override val topRight: IMutableTileChunk?, + override val topLeft: IMutableTileChunk?, + + override val left: IMutableTileChunk?, + override val bottom: IMutableTileChunk?, + override val bottomLeft: IMutableTileChunk?, + override val bottomRight: IMutableTileChunk?, +) : TileView(center, right, top, topRight, topLeft, left, bottom, bottomLeft, bottomRight), IMutableTileChunk { + override fun set(x: Int, y: Int, tile: TileDefinition?): ChunkTile? { + if (x in 0 .. CHUNK_SIZE_FF) { + if (y in 0 .. CHUNK_SIZE_FF) { + return center.set(x, y, tile) + } + + if (y < 0) { + return bottom?.set(x, y + CHUNK_SIZE, tile) + } else { + return top?.set(x, y - CHUNK_SIZE, tile) + } + } + + if (x < 0) { + if (y in 0 .. CHUNK_SIZE_FF) { + return left?.set(x + CHUNK_SIZE, y, tile) + } + + if (y < 0) { + return bottomLeft?.set(x + CHUNK_SIZE, y + CHUNK_SIZE, tile) + } else { + return topLeft?.set(x + CHUNK_SIZE, y - CHUNK_SIZE, tile) + } + } else { + if (y in 0 .. CHUNK_SIZE_FF) { + return right?.set(x - CHUNK_SIZE, y, tile) + } + + if (y < 0) { + return bottomRight?.set(x - CHUNK_SIZE, y + CHUNK_SIZE, tile) + } else { + return topRight?.set(x - CHUNK_SIZE, y - CHUNK_SIZE, tile) + } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/Tuples.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Tuples.kt new file mode 100644 index 00000000..6e7df40d --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/Tuples.kt @@ -0,0 +1,59 @@ +package ru.dbotthepony.kstarbound.world + + +/** + * Кортеж чанка, который содержит родителя (мир) и соседей (кортежи чанков) + */ +interface IWorldChunkTuple, ChunkType : Chunk> { + val world: WorldType + val chunk: ChunkType + val top: IWorldChunkTuple? + val left: IWorldChunkTuple? + val right: IWorldChunkTuple? + val bottom: IWorldChunkTuple? + + val topLeft: IWorldChunkTuple? + val topRight: IWorldChunkTuple? + val bottomLeft: IWorldChunkTuple? + val bottomRight: IWorldChunkTuple? +} + +class ProxiedWorldChunkTuple, ChunkType : Chunk>( + private val parent: IWorldChunkTuple +) : IWorldChunkTuple { + override val world get() = parent.world + override val chunk get() = parent.chunk + + override val top: IWorldChunkTuple? get() = parent.top?.let(::ProxiedWorldChunkTuple) + override val left: IWorldChunkTuple? get() = parent.left?.let(::ProxiedWorldChunkTuple) + override val right: IWorldChunkTuple? get() = parent.right?.let(::ProxiedWorldChunkTuple) + override val bottom: IWorldChunkTuple? get() = parent.bottom?.let(::ProxiedWorldChunkTuple) + override val topLeft: IWorldChunkTuple? get() = parent.topLeft?.let(::ProxiedWorldChunkTuple) + override val topRight: IWorldChunkTuple? get() = parent.topRight?.let(::ProxiedWorldChunkTuple) + override val bottomLeft: IWorldChunkTuple? get() = parent.bottomLeft?.let(::ProxiedWorldChunkTuple) + override val bottomRight: IWorldChunkTuple? get() = parent.bottomRight?.let(::ProxiedWorldChunkTuple) +} + +class InstantWorldChunkTuple, ChunkType : Chunk>( + override val world: WorldType, + override val chunk: ChunkType +) : IWorldChunkTuple { + + private val _top = world[chunk.top] + private val _left = world[chunk.left] + private val _right = world[chunk.right] + private val _bottom = world[chunk.bottom] + private val _topLeft = world[chunk.topLeft] + private val _topRight = world[chunk.topRight] + private val _bottomLeft = world[chunk.bottomLeft] + private val _bottomRight = world[chunk.bottomRight] + + override val top: IWorldChunkTuple? by lazy { _top?.let { InstantWorldChunkTuple(world, it) } } + override val left: IWorldChunkTuple? by lazy { _left?.let { InstantWorldChunkTuple(world, it) } } + override val right: IWorldChunkTuple? by lazy { _right?.let { InstantWorldChunkTuple(world, it) } } + override val bottom: IWorldChunkTuple? by lazy { _bottom?.let { InstantWorldChunkTuple(world, it) } } + override val topLeft: IWorldChunkTuple? by lazy { _topLeft?.let { InstantWorldChunkTuple(world, it) } } + override val topRight: IWorldChunkTuple? by lazy { _topRight?.let { InstantWorldChunkTuple(world, it) } } + override val bottomLeft: IWorldChunkTuple? by lazy { _bottomLeft?.let { InstantWorldChunkTuple(world, it) } } + override val bottomRight: IWorldChunkTuple? by lazy { _bottomRight?.let { InstantWorldChunkTuple(world, it) } } +} diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt index 1096e85f..6c684caa 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt @@ -12,6 +12,7 @@ import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact import ru.dbotthepony.kstarbound.METRES_IN_STARBOUND_UNIT import ru.dbotthepony.kstarbound.defs.TileDefinition import ru.dbotthepony.kstarbound.math.* +import ru.dbotthepony.kstarbound.util.Timer import ru.dbotthepony.kstarbound.world.entities.CollisionResolution import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kstarbound.world.entities.projectile.AbstractProjectileMovementController @@ -21,63 +22,6 @@ import ru.dbotthepony.kvector.vector.ndouble.Vector2d import ru.dbotthepony.kvector.vector.nint.Vector2i import kotlin.math.absoluteValue -/** - * Кортеж чанка, который содержит родителя (мир) и соседей (кортежи чанков) - */ -interface IWorldChunkTuple, ChunkType : Chunk> { - val world: WorldType - val chunk: ChunkType - val top: IWorldChunkTuple? - val left: IWorldChunkTuple? - val right: IWorldChunkTuple? - val bottom: IWorldChunkTuple? - - val topLeft: IWorldChunkTuple? - val topRight: IWorldChunkTuple? - val bottomLeft: IWorldChunkTuple? - val bottomRight: IWorldChunkTuple? -} - -class ProxiedWorldChunkTuple, ChunkType : Chunk>( - private val parent: IWorldChunkTuple -) : IWorldChunkTuple { - override val world get() = parent.world - override val chunk get() = parent.chunk - - override val top: IWorldChunkTuple? get() = parent.top?.let(::ProxiedWorldChunkTuple) - override val left: IWorldChunkTuple? get() = parent.left?.let(::ProxiedWorldChunkTuple) - override val right: IWorldChunkTuple? get() = parent.right?.let(::ProxiedWorldChunkTuple) - override val bottom: IWorldChunkTuple? get() = parent.bottom?.let(::ProxiedWorldChunkTuple) - override val topLeft: IWorldChunkTuple? get() = parent.topLeft?.let(::ProxiedWorldChunkTuple) - override val topRight: IWorldChunkTuple? get() = parent.topRight?.let(::ProxiedWorldChunkTuple) - override val bottomLeft: IWorldChunkTuple? get() = parent.bottomLeft?.let(::ProxiedWorldChunkTuple) - override val bottomRight: IWorldChunkTuple? get() = parent.bottomRight?.let(::ProxiedWorldChunkTuple) -} - -class InstantWorldChunkTuple, ChunkType : Chunk>( - override val world: WorldType, - override val chunk: ChunkType -) : IWorldChunkTuple { - - private val _top = world[chunk.top] - private val _left = world[chunk.left] - private val _right = world[chunk.right] - private val _bottom = world[chunk.bottom] - private val _topLeft = world[chunk.topLeft] - private val _topRight = world[chunk.topRight] - private val _bottomLeft = world[chunk.bottomLeft] - private val _bottomRight = world[chunk.bottomRight] - - override val top: IWorldChunkTuple? by lazy { _top?.let { InstantWorldChunkTuple(world, it) } } - override val left: IWorldChunkTuple? by lazy { _left?.let { InstantWorldChunkTuple(world, it) } } - override val right: IWorldChunkTuple? by lazy { _right?.let { InstantWorldChunkTuple(world, it) } } - override val bottom: IWorldChunkTuple? by lazy { _bottom?.let { InstantWorldChunkTuple(world, it) } } - override val topLeft: IWorldChunkTuple? by lazy { _topLeft?.let { InstantWorldChunkTuple(world, it) } } - override val topRight: IWorldChunkTuple? by lazy { _topRight?.let { InstantWorldChunkTuple(world, it) } } - override val bottomLeft: IWorldChunkTuple? by lazy { _bottomLeft?.let { InstantWorldChunkTuple(world, it) } } - override val bottomRight: IWorldChunkTuple? by lazy { _bottomRight?.let { InstantWorldChunkTuple(world, it) } } -} - const val EARTH_FREEFALL_ACCELERATION = 9.8312 / METRES_IN_STARBOUND_UNIT data class WorldSweepResult( @@ -89,40 +33,6 @@ data class WorldSweepResult( private const val EPSILON = 0.00001 -class Timer(val period: Double, val executionTimes: Int, val func: (Timer) -> Unit) { - private var counter = 0.0 - var cycles = 0 - private set - var destroyed: Boolean = false - private set - - fun think(delta: Double) { - if (destroyed) { - throw IllegalStateException("This timer is destroyed") - } - - counter += delta - - if (counter >= period) { - counter -= period - func.invoke(this) - cycles++ - } - - if (!destroyed && executionTimes > 0 && cycles >= executionTimes) { - destroy() - } - } - - fun destroy() { - if (destroyed) { - throw IllegalStateException("Already destroyed") - } - - destroyed = true - } -} - abstract class World, ChunkType : Chunk>(val seed: Long = 0L) { protected val chunkMap = Object2ObjectAVLTreeMap cmp@{ a, b -> return@cmp a.compareTo(b) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderDepthFirst.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderDepthFirst.kt new file mode 100644 index 00000000..3fb686ff --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderDepthFirst.kt @@ -0,0 +1,175 @@ +package ru.dbotthepony.kstarbound.world.phys + +import ru.dbotthepony.kstarbound.world.CHUNK_SHIFT +import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF +import ru.dbotthepony.kstarbound.world.ChunkTile +import ru.dbotthepony.kvector.vector.nint.Vector2i + +class RectTileFlooderDepthFirst( + private val tiles: Array, + private val seen: BooleanArray, + rootx: Int, + rooty: Int +) { + val mins: Vector2i + val maxs: Vector2i + + private fun filled(x: Int, y: Int): Boolean { + if (x < 0 || x > CHUNK_SIZE_FF) { + return false + } + + if (y < 0 || y > CHUNK_SIZE_FF) { + return false + } + + return !seen[x or (y shl CHUNK_SHIFT)] && tiles[x or (y shl CHUNK_SHIFT)] != null + } + + init { + // expand wide + var widthPositive = 1 + + while (true) { + if (!filled(rootx + widthPositive, rooty)) { + break + } + + widthPositive++ + } + + var widthNegative = 1 + + while (true) { + if (!filled(rootx - widthNegative, rooty)) { + break + } + + widthNegative++ + } + + // expand tall + var heightPositive = 1 + + while (true) { + if (!filled(rootx, rooty + heightPositive)) { + break + } + + heightPositive++ + } + + var heightNegative = 1 + + while (true) { + if (!filled(rootx, rooty - heightNegative)) { + break + } + + heightNegative++ + } + + widthPositive -= 1 + widthNegative -= 1 + + heightNegative -= 1 + heightPositive -= 1 + + if (heightPositive + heightNegative > widthPositive + widthNegative) { + // height is bigger + // try to expand wide + widthPositive = 0 + widthNegative = 0 + + while (true) { + var escape = false + + for (i in -heightNegative .. heightPositive) { + if (!filled(rootx + widthPositive, rooty + i)) { + escape = true + break + } + } + + if (escape) { + break + } + + widthPositive++ + } + + while (true) { + var escape = false + + for (i in -heightNegative .. heightPositive) { + if (!filled(rootx - widthNegative, rooty + i)) { + escape = true + break + } + } + + if (escape) { + break + } + + widthNegative++ + } + + widthNegative -= 1 + widthPositive -= 1 + } else { + // height is equal or lesser than width + // expand high + heightNegative = 0 + heightPositive = 0 + + while (true) { + var escape = false + + for (i in -widthNegative .. widthPositive) { + if (!filled(rootx + i, rooty + heightPositive)) { + escape = true + break + } + } + + if (escape) { + break + } + + heightPositive++ + } + + while (true) { + var escape = false + + for (i in -widthNegative .. widthPositive) { + if (!filled(rootx + i, rooty - heightNegative)) { + escape = true + break + } + } + + if (escape) { + break + } + + heightNegative++ + } + + heightNegative -= 1 + heightPositive -= 1 + } + + mins = Vector2i(rootx - widthNegative, rooty - heightNegative) + maxs = Vector2i(rootx + widthPositive, rooty + heightPositive) + } + + fun markSeen() { + for (x in mins.x .. maxs.x) { + for (y in mins.y .. maxs.y) { + seen[x or (y shl CHUNK_SHIFT)] = true + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderSizeFirst.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderSizeFirst.kt new file mode 100644 index 00000000..e32c97a4 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/RectTileFlooderSizeFirst.kt @@ -0,0 +1,144 @@ +package ru.dbotthepony.kstarbound.world.phys + +import ru.dbotthepony.kstarbound.world.CHUNK_SHIFT +import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF +import ru.dbotthepony.kstarbound.world.ChunkTile +import ru.dbotthepony.kvector.vector.nint.Vector2i + +class RectTileFlooderSizeFirst( + private val tiles: Array, + private val seen: BooleanArray, + private val rootx: Int, + private val rooty: Int +) { + val mins: Vector2i + val maxs: Vector2i + + private fun filled(x: Int, y: Int): Boolean { + if (x < 0 || x > CHUNK_SIZE_FF) { + return false + } + + if (y < 0 || y > CHUNK_SIZE_FF) { + return false + } + + return !seen[x or (y shl CHUNK_SHIFT)] && tiles[x or (y shl CHUNK_SHIFT)] != null + } + + private var widthPositive = 0 + private var widthNegative = 0 + private var heightPositive = 0 + private var heightNegative = 0 + + private fun checkLeft(): Boolean { + for (i in -heightNegative .. heightPositive) { + if (!filled(rootx - widthNegative, rooty + i)) { + return false + } + } + + return true + } + + private fun checkRight(): Boolean { + for (i in -heightNegative .. heightPositive) { + if (!filled(rootx + widthPositive, rooty + i)) { + return false + } + } + + return true + } + + private fun checkUp(): Boolean { + for (i in -widthNegative .. widthPositive) { + if (!filled(rootx + i, rooty + heightPositive)) { + return false + } + } + + return true + } + + private fun checkDown(): Boolean { + for (i in -widthNegative .. widthPositive) { + if (!filled(rootx + i, rooty - heightNegative)) { + return false + } + } + + return true + } + + init { + var expanded = true + var hitLeft = false + var hitRight = false + var hitUp = false + var hitDown = false + + while (expanded) { + expanded = false + + // expand left + if (!hitLeft) { + widthNegative++ + + if (!checkLeft()) { + widthNegative-- + hitLeft = true + } else { + expanded = true + } + } + + // expand up + if (!hitUp) { + heightPositive++ + + if (!checkUp()) { + heightPositive-- + hitUp = true + } else { + expanded = true + } + } + + // expand right + if (!hitRight) { + widthPositive++ + + if (!checkRight()) { + widthPositive-- + hitRight = true + } else { + expanded = true + } + } + + // expand down + if (!hitDown) { + heightNegative++ + + if (!checkDown()) { + heightNegative-- + hitDown = true + } else { + expanded = true + } + } + } + + mins = Vector2i(rootx - widthNegative, rooty - heightNegative) + maxs = Vector2i(rootx + widthPositive, rooty + heightPositive) + } + + fun markSeen() { + for (x in mins.x .. maxs.x) { + for (y in mins.y .. maxs.y) { + seen[x or (y shl CHUNK_SHIFT)] = true + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/TileFlooder.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/TileFlooder.kt new file mode 100644 index 00000000..68eb397b --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/phys/TileFlooder.kt @@ -0,0 +1,74 @@ +package ru.dbotthepony.kstarbound.world.phys + +import ru.dbotthepony.kstarbound.world.CHUNK_SHIFT +import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF +import ru.dbotthepony.kstarbound.world.ChunkTile +import ru.dbotthepony.kvector.vector.ndouble.Vector2d + +private data class TileExposure( + var pos: Vector2d, + val left: Boolean, + val right: Boolean, + val up: Boolean, + val down: Boolean, +) + +private class TileFlooder( + private val tiles: Array, + private val seen: BooleanArray, + rootx: Int, + rooty: Int +) { + /** + * Tile positions which have at least one face free of neighbours + */ + val exposed = ArrayList() + + private fun get(x: Int, y: Int): Boolean { + if (x < 0 || x > CHUNK_SIZE_FF) { + return false + } + + if (y < 0 || y > CHUNK_SIZE_FF) { + return false + } + + return tiles[x or (y shl CHUNK_SHIFT)] != null + } + + private fun visit(x: Int, y: Int) { + if (seen[x or (y shl CHUNK_SHIFT)]) + return + + seen[x or (y shl CHUNK_SHIFT)] = true + + val left = get(x - 1, y) + val right = get(x + 1, y) + val up = get(x, y + 1) + val down = get(x, y - 1) + + if (!left || !right || !up || !down) { + exposed.add(TileExposure(Vector2d(x.toDouble() + 0.5, y.toDouble() + 0.5), !left, !right, !up, !down)) + } + + if (left) { + visit(x - 1, y) + } + + if (right) { + visit(x + 1, y) + } + + if (up) { + visit(x, y + 1) + } + + if (down) { + visit(x, y - 1) + } + } + + init { + visit(rootx, rooty) + } +} \ No newline at end of file