KStarbound/src/main/kotlin/ru/dbotthepony/kstarbound/world/Chunk.kt

273 lines
8.0 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package ru.dbotthepony.kstarbound.world
import it.unimi.dsi.fastutil.objects.ObjectArrayList
import ru.dbotthepony.kommons.arrays.Boolean2DArray
import ru.dbotthepony.kommons.arrays.Object2DArray
import ru.dbotthepony.kstarbound.math.AABB
import ru.dbotthepony.kstarbound.math.AABBi
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.math.vector.Vector2i
import ru.dbotthepony.kstarbound.network.LegacyNetworkCellState
import ru.dbotthepony.kstarbound.server.world.ServerChunk
import ru.dbotthepony.kstarbound.world.api.AbstractCell
import ru.dbotthepony.kstarbound.world.api.ICellAccess
import ru.dbotthepony.kstarbound.world.api.ImmutableCell
import ru.dbotthepony.kstarbound.world.api.OffsetCellAccess
import ru.dbotthepony.kstarbound.world.api.TileView
import ru.dbotthepony.kstarbound.world.physics.CollisionPoly
import ru.dbotthepony.kstarbound.world.physics.CollisionType
import ru.dbotthepony.kstarbound.world.physics.getBlockPlatforms
import ru.dbotthepony.kstarbound.world.physics.getBlocksMarchingSquares
import java.util.BitSet
import java.util.concurrent.CopyOnWriteArraySet
import kotlin.math.max
import kotlin.math.min
/**
* Чанк мира
*
* Хранит в себе тайлы и ентити внутри себя
*
* Считается, что один тайл имеет форму квадрата и сторона квадрата примерно равна полуметру,
* что будет называться Starbound Unit
*
* Весь игровой мир будет измеряться в Starbound Unit'ах
*/
abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType, This>>(
val world: WorldType,
val pos: ChunkPos,
) : ICellAccess {
var changeset = 0
private set
var tileChangeset = 0
private set
var liquidChangeset = 0
private set
var cellChangeset = 0
private set
var foregroundChangeset = 0
private set
var backgroundChangeset = 0
private set
abstract val state: ChunkState
val width = (world.geometry.size.x - pos.tileX).coerceAtMost(CHUNK_SIZE)
val height = (world.geometry.size.y - pos.tileY).coerceAtMost(CHUNK_SIZE)
// local cells' tile access
val localBackgroundView = TileView.Background(this)
val localForegroundView = TileView.Foreground(this)
// relative world cells access (accessing 0, 0 will lookup cell in world, relative to this chunk)
val worldView = OffsetCellAccess(world, pos.x * CHUNK_SIZE, pos.y * CHUNK_SIZE)
val worldBackgroundView = TileView.Background(worldView)
val worldForegroundView = TileView.Foreground(worldView)
val aabb = AABBi(pos.tile, pos.tile + Vector2i(width, height))
val aabbd = aabb.toDoubleAABB()
protected val cells = lazy {
Object2DArray(width, height, AbstractCell.NULL)
}
protected val backgroundHealth = HashMap<Vector2i, TileHealth.Tile>()
protected val foregroundHealth = HashMap<Vector2i, TileHealth.Tile>()
protected val collisionCacheDirty = Boolean2DArray.allocate(width, height)
protected val collisionCache by lazy {
Object2DArray(width, height) { _, _ -> ObjectArrayList<CollisionPoly>(0) }
}
init {
for (x in 0 until width)
for (y in 0 until height)
collisionCacheDirty[x, y] = true
}
private var hasDirtyCollisions = false
// bulk mark collision dirty of neighbour chunks as well as ours
protected fun signalChunkContentsUpdated() {
val signalPositions = ObjectArrayList<Vector2i>(24)
for (x in 1 .. 2) {
for (y in 1 .. 2) {
signalPositions.add(pos.tile + Vector2i(width + x, height + y))
signalPositions.add(pos.tile + Vector2i(width, height + y))
signalPositions.add(pos.tile + Vector2i(width + x, height))
signalPositions.add(pos.tile + Vector2i(-x, -y))
signalPositions.add(pos.tile + Vector2i(0, -y))
signalPositions.add(pos.tile + Vector2i(-x, 0))
}
}
for (pos in signalPositions) {
val actualCellPosition = world.geometry.wrap(pos)
val chunk = world.chunkMap[world.geometry.chunkFromCell(actualCellPosition)] ?: continue
chunk.hasDirtyCollisions = true
chunk.collisionCacheDirty[actualCellPosition.x - chunk.pos.tileX, actualCellPosition.y - chunk.pos.tileY] = true
}
hasDirtyCollisions = true
backgroundHealth.clear()
foregroundHealth.clear()
for (x in 0 until width) {
for (y in 0 until height) {
collisionCacheDirty[x, y] = true
}
}
}
private val collisionsLock = Any()
fun getCollisions(x: Int, y: Int, target: MutableCollection<CollisionPoly>) {
if (hasDirtyCollisions) {
synchronized(collisionsLock) {
if (hasDirtyCollisions) {
var minX = width
var minY = height
var maxX = 0
var maxY = 0
for (x in 0 until width) {
for (y in 0 until height) {
if (collisionCacheDirty[x, y]) {
minX = min(minX, x)
minY = min(minY, y)
maxX = max(maxX, x)
maxY = max(maxY, y)
}
}
}
for (x in minX .. maxX) {
for (y in minY .. maxY) {
if (collisionCacheDirty[x, y]) {
collisionCache[x, y].clear()
getBlocksMarchingSquares(pos.tileX + x, pos.tileY + y, world.foreground, CollisionType.DYNAMIC, collisionCache[x, y])
getBlockPlatforms(pos.tileX + x, pos.tileY + y, world.foreground, CollisionType.PLATFORM, collisionCache[x, y])
collisionCacheDirty[x, y] = false
}
}
}
}
}
hasDirtyCollisions = false
}
target.addAll(collisionCache[x, y])
}
fun loadCells(source: Object2DArray<out AbstractCell>) {
val ours = cells.value
for (x in 0 until width) {
for (y in 0 until height) {
ours[x, y] = source[x, y].immutable()
}
}
signalChunkContentsUpdated()
}
fun copyCells(): Object2DArray<ImmutableCell> {
if (cells.isInitialized()) {
val cells = cells.value
return Object2DArray(width, height) { x, y -> cells[x, y] }
} else {
return Object2DArray(width, height, AbstractCell.NULL)
}
}
override fun getCell(x: Int, y: Int): AbstractCell {
return if (cells.isInitialized()) cells.value[x, y] else AbstractCell.NULL
}
final override fun setCell(x: Int, y: Int, cell: AbstractCell): Boolean {
val old = if (cells.isInitialized()) cells.value[x, y] else AbstractCell.NULL
val new = cell.immutable()
if (old != new) {
cells.value[x, y] = new
hasDirtyCollisions = true
collisionCacheDirty[x, y] = true
for (xoff in -2 .. 2) {
for (yoff in -2 .. 2) {
val actualCellPosition = world.geometry.wrap(pos.tile + Vector2i(x + xoff, y + yoff))
val chunk = world.chunkMap[world.geometry.chunkFromCell(actualCellPosition)] ?: continue
chunk.hasDirtyCollisions = true
chunk.collisionCacheDirty[actualCellPosition.x - chunk.pos.tileX, actualCellPosition.y - chunk.pos.tileY] = true
}
}
if (old.foreground != new.foreground) {
foregroundHealth.remove(Vector2i(x, y))
foregroundChanges(x, y, new)
}
if (old.background != new.background) {
backgroundHealth.remove(Vector2i(x, y))
backgroundChanges(x, y, new)
}
if (old.liquid != new.liquid) {
liquidChanges(x, y, new)
}
cellChanges(x, y, new)
}
return true
}
protected open fun foregroundChanges(x: Int, y: Int, cell: ImmutableCell) {
tileChangeset++
foregroundChangeset++
}
protected open fun backgroundChanges(x: Int, y: Int, cell: ImmutableCell) {
tileChangeset++
backgroundChangeset++
}
protected open fun liquidChanges(x: Int, y: Int, cell: ImmutableCell) {
liquidChangeset++
}
protected open fun cellChanges(x: Int, y: Int, cell: ImmutableCell) {
changeset++
cellChangeset++
}
protected inline fun forEachNeighbour(block: (This) -> Unit) {
world.chunkMap[pos.left]?.let(block)
world.chunkMap[pos.right]?.let(block)
world.chunkMap[pos.top]?.let(block)
world.chunkMap[pos.bottom]?.let(block)
world.chunkMap[pos.topLeft]?.let(block)
world.chunkMap[pos.topRight]?.let(block)
world.chunkMap[pos.bottomLeft]?.let(block)
world.chunkMap[pos.bottomRight]?.let(block)
}
override fun toString(): String {
return "${this::class.simpleName}(pos=$pos, world=$world)"
}
open fun remove() {
}
open fun tick(delta: Double) {
}
}