Move classes around
This commit is contained in:
parent
af97c80cdd
commit
aa9d379d41
@ -6,34 +6,11 @@ import ru.dbotthepony.kstarbound.client.gl.VertexTransformers
|
|||||||
import ru.dbotthepony.kstarbound.client.render.renderLayeredList
|
import ru.dbotthepony.kstarbound.client.render.renderLayeredList
|
||||||
import ru.dbotthepony.kstarbound.defs.ParallaxPrototype
|
import ru.dbotthepony.kstarbound.defs.ParallaxPrototype
|
||||||
import ru.dbotthepony.kstarbound.math.encasingChunkPosAABB
|
import ru.dbotthepony.kstarbound.math.encasingChunkPosAABB
|
||||||
|
import ru.dbotthepony.kstarbound.util.DoubleEdgeProgression
|
||||||
import ru.dbotthepony.kstarbound.world.*
|
import ru.dbotthepony.kstarbound.world.*
|
||||||
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
|
||||||
|
|
||||||
class DoubleEdgeProgression : Iterator<Int> {
|
|
||||||
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<ClientWorld, ClientChunk>(seed) {
|
class ClientWorld(val client: StarboundClient, seed: Long = 0L) : World<ClientWorld, ClientChunk>(seed) {
|
||||||
init {
|
init {
|
||||||
physics.debugDraw = client.gl.box2dRenderer
|
physics.debugDraw = client.gl.box2dRenderer
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.util
|
||||||
|
|
||||||
|
class DoubleEdgeProgression : Iterator<Int> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
src/main/kotlin/ru/dbotthepony/kstarbound/util/Timer.kt
Normal file
35
src/main/kotlin/ru/dbotthepony/kstarbound/util/Timer.kt
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -5,17 +5,14 @@ import ru.dbotthepony.kbox2d.api.FixtureDef
|
|||||||
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
|
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
|
||||||
import ru.dbotthepony.kbox2d.dynamics.B2Fixture
|
import ru.dbotthepony.kbox2d.dynamics.B2Fixture
|
||||||
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
||||||
import ru.dbotthepony.kstarbound.math.*
|
|
||||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||||
import ru.dbotthepony.kvector.api.IStruct2d
|
import ru.dbotthepony.kstarbound.world.phys.RectTileFlooderDepthFirst
|
||||||
import ru.dbotthepony.kvector.api.IStruct2i
|
import ru.dbotthepony.kstarbound.world.phys.RectTileFlooderSizeFirst
|
||||||
import ru.dbotthepony.kvector.util2d.AABB
|
import ru.dbotthepony.kvector.util2d.AABB
|
||||||
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
|
import ru.dbotthepony.kvector.vector.ndouble.Vector2d
|
||||||
import ru.dbotthepony.kvector.vector.nint.Vector2i
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.collections.HashSet
|
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)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Возвращает итератор пар <Vector2i, Тайл?>
|
|
||||||
*
|
|
||||||
* Вектор имеет ОТНОСИТЕЛЬНЫЕ значения внутри самого чанка
|
|
||||||
*/
|
|
||||||
val posToTile: Iterator<Pair<Vector2i, ChunkTile?>> get() {
|
|
||||||
return object : Iterator<Pair<Vector2i, ChunkTile?>> {
|
|
||||||
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<Vector2i, ChunkTile?> {
|
|
||||||
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<ChunkPos> {
|
|
||||||
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<ChunkTile?>,
|
|
||||||
private val seen: BooleanArray,
|
|
||||||
rootx: Int,
|
|
||||||
rooty: Int
|
|
||||||
) {
|
|
||||||
/**
|
|
||||||
* Tile positions which have at least one face free of neighbours
|
|
||||||
*/
|
|
||||||
val exposed = ArrayList<TileExposure>()
|
|
||||||
|
|
||||||
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<ChunkTile?>,
|
|
||||||
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<ChunkTile?>,
|
|
||||||
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 {
|
private fun ccwSortScore(point: Vector2d, axis: Vector2d): Double {
|
||||||
if (point.x > 0.0) {
|
if (point.x > 0.0) {
|
||||||
return point.dot(axis)
|
return point.dot(axis)
|
||||||
@ -1105,9 +376,3 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <E> AbstractList<E>.addU(e: E) {
|
|
||||||
if (indexOf(e) == -1) {
|
|
||||||
add(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
133
src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkAPI.kt
Normal file
133
src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkAPI.kt
Normal file
@ -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)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Возвращает итератор пар <Vector2i, Тайл?>
|
||||||
|
*
|
||||||
|
* Вектор имеет ОТНОСИТЕЛЬНЫЕ значения внутри самого чанка
|
||||||
|
*/
|
||||||
|
val posToTile: Iterator<Pair<Vector2i, ChunkTile?>> get() {
|
||||||
|
return object : Iterator<Pair<Vector2i, ChunkTile?>> {
|
||||||
|
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<Vector2i, ChunkTile?> {
|
||||||
|
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
|
130
src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkPos.kt
Normal file
130
src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkPos.kt
Normal file
@ -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<ChunkPos> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
113
src/main/kotlin/ru/dbotthepony/kstarbound/world/TileView.kt
Normal file
113
src/main/kotlin/ru/dbotthepony/kstarbound/world/TileView.kt
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
59
src/main/kotlin/ru/dbotthepony/kstarbound/world/Tuples.kt
Normal file
59
src/main/kotlin/ru/dbotthepony/kstarbound/world/Tuples.kt
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.world
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Кортеж чанка, который содержит родителя (мир) и соседей (кортежи чанков)
|
||||||
|
*/
|
||||||
|
interface IWorldChunkTuple<WorldType : World<WorldType, ChunkType>, ChunkType : Chunk<WorldType, ChunkType>> {
|
||||||
|
val world: WorldType
|
||||||
|
val chunk: ChunkType
|
||||||
|
val top: IWorldChunkTuple<WorldType, ChunkType>?
|
||||||
|
val left: IWorldChunkTuple<WorldType, ChunkType>?
|
||||||
|
val right: IWorldChunkTuple<WorldType, ChunkType>?
|
||||||
|
val bottom: IWorldChunkTuple<WorldType, ChunkType>?
|
||||||
|
|
||||||
|
val topLeft: IWorldChunkTuple<WorldType, ChunkType>?
|
||||||
|
val topRight: IWorldChunkTuple<WorldType, ChunkType>?
|
||||||
|
val bottomLeft: IWorldChunkTuple<WorldType, ChunkType>?
|
||||||
|
val bottomRight: IWorldChunkTuple<WorldType, ChunkType>?
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProxiedWorldChunkTuple<WorldType : World<WorldType, ChunkType>, ChunkType : Chunk<WorldType, ChunkType>>(
|
||||||
|
private val parent: IWorldChunkTuple<WorldType, ChunkType>
|
||||||
|
) : IWorldChunkTuple<WorldType, ChunkType> {
|
||||||
|
override val world get() = parent.world
|
||||||
|
override val chunk get() = parent.chunk
|
||||||
|
|
||||||
|
override val top: IWorldChunkTuple<WorldType, ChunkType>? get() = parent.top?.let(::ProxiedWorldChunkTuple)
|
||||||
|
override val left: IWorldChunkTuple<WorldType, ChunkType>? get() = parent.left?.let(::ProxiedWorldChunkTuple)
|
||||||
|
override val right: IWorldChunkTuple<WorldType, ChunkType>? get() = parent.right?.let(::ProxiedWorldChunkTuple)
|
||||||
|
override val bottom: IWorldChunkTuple<WorldType, ChunkType>? get() = parent.bottom?.let(::ProxiedWorldChunkTuple)
|
||||||
|
override val topLeft: IWorldChunkTuple<WorldType, ChunkType>? get() = parent.topLeft?.let(::ProxiedWorldChunkTuple)
|
||||||
|
override val topRight: IWorldChunkTuple<WorldType, ChunkType>? get() = parent.topRight?.let(::ProxiedWorldChunkTuple)
|
||||||
|
override val bottomLeft: IWorldChunkTuple<WorldType, ChunkType>? get() = parent.bottomLeft?.let(::ProxiedWorldChunkTuple)
|
||||||
|
override val bottomRight: IWorldChunkTuple<WorldType, ChunkType>? get() = parent.bottomRight?.let(::ProxiedWorldChunkTuple)
|
||||||
|
}
|
||||||
|
|
||||||
|
class InstantWorldChunkTuple<WorldType : World<WorldType, ChunkType>, ChunkType : Chunk<WorldType, ChunkType>>(
|
||||||
|
override val world: WorldType,
|
||||||
|
override val chunk: ChunkType
|
||||||
|
) : IWorldChunkTuple<WorldType, ChunkType> {
|
||||||
|
|
||||||
|
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<WorldType, ChunkType>? by lazy { _top?.let { InstantWorldChunkTuple(world, it) } }
|
||||||
|
override val left: IWorldChunkTuple<WorldType, ChunkType>? by lazy { _left?.let { InstantWorldChunkTuple(world, it) } }
|
||||||
|
override val right: IWorldChunkTuple<WorldType, ChunkType>? by lazy { _right?.let { InstantWorldChunkTuple(world, it) } }
|
||||||
|
override val bottom: IWorldChunkTuple<WorldType, ChunkType>? by lazy { _bottom?.let { InstantWorldChunkTuple(world, it) } }
|
||||||
|
override val topLeft: IWorldChunkTuple<WorldType, ChunkType>? by lazy { _topLeft?.let { InstantWorldChunkTuple(world, it) } }
|
||||||
|
override val topRight: IWorldChunkTuple<WorldType, ChunkType>? by lazy { _topRight?.let { InstantWorldChunkTuple(world, it) } }
|
||||||
|
override val bottomLeft: IWorldChunkTuple<WorldType, ChunkType>? by lazy { _bottomLeft?.let { InstantWorldChunkTuple(world, it) } }
|
||||||
|
override val bottomRight: IWorldChunkTuple<WorldType, ChunkType>? by lazy { _bottomRight?.let { InstantWorldChunkTuple(world, it) } }
|
||||||
|
}
|
@ -12,6 +12,7 @@ import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
|
|||||||
import ru.dbotthepony.kstarbound.METRES_IN_STARBOUND_UNIT
|
import ru.dbotthepony.kstarbound.METRES_IN_STARBOUND_UNIT
|
||||||
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
import ru.dbotthepony.kstarbound.defs.TileDefinition
|
||||||
import ru.dbotthepony.kstarbound.math.*
|
import ru.dbotthepony.kstarbound.math.*
|
||||||
|
import ru.dbotthepony.kstarbound.util.Timer
|
||||||
import ru.dbotthepony.kstarbound.world.entities.CollisionResolution
|
import ru.dbotthepony.kstarbound.world.entities.CollisionResolution
|
||||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.projectile.AbstractProjectileMovementController
|
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 ru.dbotthepony.kvector.vector.nint.Vector2i
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
|
||||||
/**
|
|
||||||
* Кортеж чанка, который содержит родителя (мир) и соседей (кортежи чанков)
|
|
||||||
*/
|
|
||||||
interface IWorldChunkTuple<WorldType : World<WorldType, ChunkType>, ChunkType : Chunk<WorldType, ChunkType>> {
|
|
||||||
val world: WorldType
|
|
||||||
val chunk: ChunkType
|
|
||||||
val top: IWorldChunkTuple<WorldType, ChunkType>?
|
|
||||||
val left: IWorldChunkTuple<WorldType, ChunkType>?
|
|
||||||
val right: IWorldChunkTuple<WorldType, ChunkType>?
|
|
||||||
val bottom: IWorldChunkTuple<WorldType, ChunkType>?
|
|
||||||
|
|
||||||
val topLeft: IWorldChunkTuple<WorldType, ChunkType>?
|
|
||||||
val topRight: IWorldChunkTuple<WorldType, ChunkType>?
|
|
||||||
val bottomLeft: IWorldChunkTuple<WorldType, ChunkType>?
|
|
||||||
val bottomRight: IWorldChunkTuple<WorldType, ChunkType>?
|
|
||||||
}
|
|
||||||
|
|
||||||
class ProxiedWorldChunkTuple<WorldType : World<WorldType, ChunkType>, ChunkType : Chunk<WorldType, ChunkType>>(
|
|
||||||
private val parent: IWorldChunkTuple<WorldType, ChunkType>
|
|
||||||
) : IWorldChunkTuple<WorldType, ChunkType> {
|
|
||||||
override val world get() = parent.world
|
|
||||||
override val chunk get() = parent.chunk
|
|
||||||
|
|
||||||
override val top: IWorldChunkTuple<WorldType, ChunkType>? get() = parent.top?.let(::ProxiedWorldChunkTuple)
|
|
||||||
override val left: IWorldChunkTuple<WorldType, ChunkType>? get() = parent.left?.let(::ProxiedWorldChunkTuple)
|
|
||||||
override val right: IWorldChunkTuple<WorldType, ChunkType>? get() = parent.right?.let(::ProxiedWorldChunkTuple)
|
|
||||||
override val bottom: IWorldChunkTuple<WorldType, ChunkType>? get() = parent.bottom?.let(::ProxiedWorldChunkTuple)
|
|
||||||
override val topLeft: IWorldChunkTuple<WorldType, ChunkType>? get() = parent.topLeft?.let(::ProxiedWorldChunkTuple)
|
|
||||||
override val topRight: IWorldChunkTuple<WorldType, ChunkType>? get() = parent.topRight?.let(::ProxiedWorldChunkTuple)
|
|
||||||
override val bottomLeft: IWorldChunkTuple<WorldType, ChunkType>? get() = parent.bottomLeft?.let(::ProxiedWorldChunkTuple)
|
|
||||||
override val bottomRight: IWorldChunkTuple<WorldType, ChunkType>? get() = parent.bottomRight?.let(::ProxiedWorldChunkTuple)
|
|
||||||
}
|
|
||||||
|
|
||||||
class InstantWorldChunkTuple<WorldType : World<WorldType, ChunkType>, ChunkType : Chunk<WorldType, ChunkType>>(
|
|
||||||
override val world: WorldType,
|
|
||||||
override val chunk: ChunkType
|
|
||||||
) : IWorldChunkTuple<WorldType, ChunkType> {
|
|
||||||
|
|
||||||
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<WorldType, ChunkType>? by lazy { _top?.let { InstantWorldChunkTuple(world, it) } }
|
|
||||||
override val left: IWorldChunkTuple<WorldType, ChunkType>? by lazy { _left?.let { InstantWorldChunkTuple(world, it) } }
|
|
||||||
override val right: IWorldChunkTuple<WorldType, ChunkType>? by lazy { _right?.let { InstantWorldChunkTuple(world, it) } }
|
|
||||||
override val bottom: IWorldChunkTuple<WorldType, ChunkType>? by lazy { _bottom?.let { InstantWorldChunkTuple(world, it) } }
|
|
||||||
override val topLeft: IWorldChunkTuple<WorldType, ChunkType>? by lazy { _topLeft?.let { InstantWorldChunkTuple(world, it) } }
|
|
||||||
override val topRight: IWorldChunkTuple<WorldType, ChunkType>? by lazy { _topRight?.let { InstantWorldChunkTuple(world, it) } }
|
|
||||||
override val bottomLeft: IWorldChunkTuple<WorldType, ChunkType>? by lazy { _bottomLeft?.let { InstantWorldChunkTuple(world, it) } }
|
|
||||||
override val bottomRight: IWorldChunkTuple<WorldType, ChunkType>? by lazy { _bottomRight?.let { InstantWorldChunkTuple(world, it) } }
|
|
||||||
}
|
|
||||||
|
|
||||||
const val EARTH_FREEFALL_ACCELERATION = 9.8312 / METRES_IN_STARBOUND_UNIT
|
const val EARTH_FREEFALL_ACCELERATION = 9.8312 / METRES_IN_STARBOUND_UNIT
|
||||||
|
|
||||||
data class WorldSweepResult(
|
data class WorldSweepResult(
|
||||||
@ -89,40 +33,6 @@ data class WorldSweepResult(
|
|||||||
|
|
||||||
private const val EPSILON = 0.00001
|
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<This : World<This, ChunkType>, ChunkType : Chunk<This, ChunkType>>(val seed: Long = 0L) {
|
abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, ChunkType>>(val seed: Long = 0L) {
|
||||||
protected val chunkMap = Object2ObjectAVLTreeMap<ChunkPos, ChunkType> cmp@{ a, b ->
|
protected val chunkMap = Object2ObjectAVLTreeMap<ChunkPos, ChunkType> cmp@{ a, b ->
|
||||||
return@cmp a.compareTo(b)
|
return@cmp a.compareTo(b)
|
||||||
|
@ -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<ChunkTile?>,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<ChunkTile?>,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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<ChunkTile?>,
|
||||||
|
private val seen: BooleanArray,
|
||||||
|
rootx: Int,
|
||||||
|
rooty: Int
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Tile positions which have at least one face free of neighbours
|
||||||
|
*/
|
||||||
|
val exposed = ArrayList<TileExposure>()
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user