Move classes around
This commit is contained in:
parent
af97c80cdd
commit
aa9d379d41
src/main/kotlin/ru/dbotthepony/kstarbound
@ -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<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) {
|
||||
init {
|
||||
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.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)
|
||||
|
||||
/**
|
||||
* Возвращает итератор пар <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 {
|
||||
if (point.x > 0.0) {
|
||||
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.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<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
|
||||
|
||||
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<This : World<This, ChunkType>, ChunkType : Chunk<This, ChunkType>>(val seed: Long = 0L) {
|
||||
protected val chunkMap = Object2ObjectAVLTreeMap<ChunkPos, ChunkType> cmp@{ a, 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