Slice and splice tiles, make tile layers be an abstraction, not reality

This commit is contained in:
DBotThePony 2023-09-03 13:43:36 +07:00
parent 4e4875b5d7
commit 808a938144
Signed by: DBot
GPG Key ID: DCC23B5715498507
16 changed files with 630 additions and 657 deletions

View File

@ -81,7 +81,7 @@ dependencies {
implementation("com.github.jnr:jnr-ffi:2.2.13")
implementation("ru.dbotthepony:kbox2d:2.4.1.6")
implementation("ru.dbotthepony:kvector:2.2.8")
implementation("ru.dbotthepony:kvector:2.2.9")
implementation("com.github.ben-manes.caffeine:caffeine:3.1.5")
}

View File

@ -13,7 +13,6 @@ import ru.dbotthepony.kstarbound.player.QuestDescriptor
import ru.dbotthepony.kstarbound.player.QuestInstance
import ru.dbotthepony.kstarbound.util.JVMTimeSource
import ru.dbotthepony.kstarbound.world.ChunkPos
import ru.dbotthepony.kstarbound.world.TileState
import ru.dbotthepony.kstarbound.world.entities.ItemEntity
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
import ru.dbotthepony.kvector.vector.Vector2d
@ -90,29 +89,7 @@ fun main() {
for (y in 0 .. 31) {
for (x in 0 .. 31) {
chunk.foreground.setTile(x, y, TileState.read(starbound.tilesByID::get, starbound.tileModifiersByID::get, reader))
chunk.background.setTile(x, y, TileState.read(starbound.tilesByID::get, starbound.tileModifiersByID::get, reader))
val liquid = reader.readUnsignedByte()
val liquidLevel = reader.readFloat()
val liquidPressure = reader.readFloat()
val liquidIsInfinite = reader.readBoolean()
val collisionMap = reader.readUnsignedByte()
val dungeonId = reader.readUnsignedShort()
val biome = reader.readUnsignedByte()
val envBiome = reader.readUnsignedByte()
val indestructible = reader.readBoolean()
val unknown = reader.readUnsignedByte()
val getLiquid = starbound.liquidByID[liquid]
if (getLiquid != null) {
val state = chunk.setLiquid(x, y, getLiquid.value)!!
state.isInfinite = liquidIsInfinite
state.pressure = liquidPressure
state.level = liquidLevel
}
chunk.getCell(x, y).read(starbound.tilesByID::get, starbound.tileModifiersByID::get, starbound.liquidByID::get, reader)
}
}

View File

@ -23,6 +23,7 @@ import ru.dbotthepony.kvector.vector.Vector2f
import ru.dbotthepony.kvector.vector.Vector3f
import java.io.Closeable
import java.util.LinkedList
import java.util.function.IntSupplier
/**
* Псевдо zPos у фоновых тайлов
@ -36,12 +37,14 @@ const val Z_LEVEL_LIQUID = 10000
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos), Closeable {
val state: GLStateTracker get() = world.client.gl
private inner class TileLayerRenderer(private val layerChangeset: () -> Int, private val isBackground: Boolean) : AutoCloseable {
private inner class TileLayerRenderer(private val layerChangeset: IntSupplier, private val view: () -> ITileChunk, private val isBackground: Boolean) : AutoCloseable {
private val layers = TileLayerList()
val bakedMeshes = LinkedList<Pair<ConfiguredStaticMesh, Int>>()
private var changeset = -1
fun bake(view: ITileChunk) {
fun bake() {
val view = view()
if (state.isSameThread()) {
for (mesh in bakedMeshes) {
mesh.first.close()
@ -67,7 +70,9 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
}
}
fun loadRenderers(view: ITileChunk) {
fun loadRenderers() {
val view = view()
for ((_, tile) in view.iterate()) {
val material = tile.material
val modifier = tile.modifier
@ -100,10 +105,10 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
}
}
fun autoBake(provider: () -> ITileChunk) {
if (changeset != layerChangeset.invoke()) {
this.bake(provider.invoke())
changeset = layerChangeset.invoke()
fun autoBake() {
if (changeset != layerChangeset.asInt) {
this.bake()
changeset = layerChangeset.asInt
}
}
@ -123,13 +128,13 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
}
}
fun autoBakeAndUpload(provider: () -> ITileChunk) {
autoBake(provider)
fun autoBakeAndUpload() {
autoBake()
autoUpload()
}
fun bakeAndRender(transform: Matrix4fStack, provider: () -> ITileChunk) {
autoBakeAndUpload(provider)
fun bakeAndRender(transform: Matrix4fStack) {
autoBakeAndUpload()
render(transform)
}
@ -143,16 +148,8 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
val debugCollisions get() = world.client.settings.debugCollisions
val posVector2d = Vector2d(x = pos.x * CHUNK_SIZEd, y = pos.y * CHUNK_SIZEd)
private val foregroundRenderer = TileLayerRenderer(foreground::changeset, isBackground = false)
private val backgroundRenderer = TileLayerRenderer(background::changeset, isBackground = true)
private fun getForegroundView(): ITileChunk {
return world.getForegroundView(pos)!!
}
private fun getBackgroundView(): ITileChunk {
return world.getBackgroundView(pos)!!
}
private val foregroundRenderer = TileLayerRenderer(::foregroundChangeset, { world.getView(pos).foregroundView }, isBackground = false)
private val backgroundRenderer = TileLayerRenderer(::backgroundChangeset, { world.getView(pos).backgroundView }, isBackground = true)
/**
* Принудительно подгружает в GLStateTracker все необходимые рендереры (ибо им нужны текстуры и прочее)
@ -160,8 +157,8 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
* Вызывается перед tesselateStatic()
*/
fun loadRenderers() {
foregroundRenderer.loadRenderers(getForegroundView())
backgroundRenderer.loadRenderers(getBackgroundView())
foregroundRenderer.loadRenderers()
backgroundRenderer.loadRenderers()
}
/**
@ -173,8 +170,8 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
*
*/
fun bake() {
backgroundRenderer.autoBake(this::getBackgroundView)
foregroundRenderer.autoBake(this::getForegroundView)
backgroundRenderer.autoBake()
foregroundRenderer.autoBake()
if (state.isSameThread())
upload()
@ -199,20 +196,20 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
for (x in this.x * SHADOW_GEOMETRY_SQUARE_SIZE until (this.x + 1) * SHADOW_GEOMETRY_SQUARE_SIZE) {
for (y in this.y * SHADOW_GEOMETRY_SQUARE_SIZE until (this.y + 1) * SHADOW_GEOMETRY_SQUARE_SIZE) {
if (foreground.getTile(x, y).material?.renderParameters?.lightTransparent == false) {
if (x == 0 || foreground.getTile(x - 1, y).material?.renderParameters?.lightTransparent != true) {
if (foregroundView.getTile(x, y).material?.renderParameters?.lightTransparent == false) {
if (x == 0 || foregroundView.getTile(x - 1, y).material?.renderParameters?.lightTransparent != true) {
line(builder, x.toFloat(), y + 1f, x.toFloat(), y.toFloat())
}
if (x == CHUNK_SIZE - 1 || foreground.getTile(x + 1, y).material?.renderParameters?.lightTransparent != true) {
if (x == CHUNK_SIZE - 1 || foregroundView.getTile(x + 1, y).material?.renderParameters?.lightTransparent != true) {
line(builder, x + 1f, y.toFloat(), x + 1f, y + 1f)
}
if (y == 0 || foreground.getTile(x, y - 1).material?.renderParameters?.lightTransparent != true) {
if (y == 0 || foregroundView.getTile(x, y - 1).material?.renderParameters?.lightTransparent != true) {
line(builder, x.toFloat(), y.toFloat(), x + 1f, y.toFloat())
}
if (y == CHUNK_SIZE - 1 || foreground.getTile(x, y + 1).material?.renderParameters?.lightTransparent != true) {
if (y == CHUNK_SIZE - 1 || foregroundView.getTile(x, y + 1).material?.renderParameters?.lightTransparent != true) {
line(builder, x + 1f, y + 1f, x.toFloat(), y + 1f)
}
}
@ -382,10 +379,10 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
for (x in 0 until CHUNK_SIZE) {
for (y in 0 until CHUNK_SIZE) {
val state = getLiquid(x, y)
val state = getCell(x, y)
if (state != null && !types.any { it === state.def }) {
types.add(state.def)
if (state.liquid.def != null && !types.any { it === state.liquid.def }) {
types.add(state.liquid.def!!)
}
}
}
@ -402,10 +399,10 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
for (x in 0 until CHUNK_SIZE) {
for (y in 0 until CHUNK_SIZE) {
val state = getLiquid(x, y)
val state = getCell(x, y)
if (state != null && state.def === type) {
builder.builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state.level)
if (state.liquid.def === type) {
builder.builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state.liquid.level)
}
}
}

View File

@ -12,8 +12,8 @@ import ru.dbotthepony.kstarbound.client.gl.vertex.GLAttributeList
import ru.dbotthepony.kstarbound.client.gl.vertex.*
import ru.dbotthepony.kstarbound.defs.tile.*
import ru.dbotthepony.kstarbound.world.ITileChunk
import ru.dbotthepony.kstarbound.world.ITileState
import ru.dbotthepony.kstarbound.world.TileColor
import ru.dbotthepony.kstarbound.world.TileState
import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2i
import kotlin.collections.HashMap
@ -174,13 +174,13 @@ private enum class TileRenderTesselateResult {
private fun vertexTextureBuilder() = VertexBuilder(GLAttributeList.TILE, GeometryType.QUADS)
private class TileEqualityTester(val definition: TileDefinition) : EqualityRuleTester {
override fun test(thisTile: TileState, otherTile: TileState): Boolean {
override fun test(thisTile: ITileState, otherTile: ITileState): Boolean {
return otherTile.material == definition && thisTile.hueShift == otherTile.hueShift
}
}
private class ModifierEqualityTester(val definition: MaterialModifier) : EqualityRuleTester {
override fun test(thisTile: TileState, otherTile: TileState): Boolean {
override fun test(thisTile: ITileState, otherTile: ITileState): Boolean {
return otherTile.modifier == definition
}
}
@ -201,7 +201,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
val bakedBackgroundProgramState = renderers.background(texture)
// private var notifiedDepth = false
private fun tesselateAt(self: TileState, piece: RenderPiece, getter: ITileChunk, builder: VertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO, isModifier: Boolean) {
private fun tesselateAt(self: ITileState, piece: RenderPiece, getter: ITileChunk, builder: VertexBuilder, pos: Vector2i, offset: Vector2i = Vector2i.ZERO, isModifier: Boolean) {
val fx = pos.x.toFloat()
val fy = pos.y.toFloat()
@ -243,7 +243,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
}
private fun tesselatePiece(
self: TileState,
self: ITileState,
matchPiece: RenderMatch,
getter: ITileChunk,
layers: TileLayerList,
@ -294,7 +294,7 @@ class TileRenderer(val renderers: TileRenderers, val def: IRenderableTile) {
*
* Тесселирует тайлы в нужный VertexBuilder с масштабом согласно константе [PIXELS_IN_STARBOUND_UNITf]
*/
fun tesselate(self: TileState, getter: ITileChunk, layers: TileLayerList, pos: Vector2i, background: Boolean = false, isModifier: Boolean = false) {
fun tesselate(self: ITileState, getter: ITileChunk, layers: TileLayerList, pos: Vector2i, background: Boolean = false, isModifier: Boolean = false) {
// если у нас нет renderTemplate
// то мы просто не можем его отрисовать
val template = def.renderTemplate.value ?: return

View File

@ -6,8 +6,8 @@ import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.util.WriteOnce
import ru.dbotthepony.kstarbound.world.ITileGetter
import ru.dbotthepony.kstarbound.world.TileState
import ru.dbotthepony.kstarbound.world.ITileChunk
import ru.dbotthepony.kstarbound.world.ITileState
import ru.dbotthepony.kvector.vector.Vector2i
@JsonFactory
@ -20,7 +20,7 @@ data class RenderPiece(
)
fun interface EqualityRuleTester {
fun test(thisTile: TileState, otherTile: TileState): Boolean
fun test(thisTile: ITileState, otherTile: ITileState): Boolean
}
@JsonFactory
@ -38,7 +38,7 @@ data class RenderRuleList(
val matchHue: Boolean = false,
val inverse: Boolean = false,
) {
private fun doTest(getter: ITileGetter, equalityTester: EqualityRuleTester, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
private fun doTest(getter: ITileChunk, equalityTester: EqualityRuleTester, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
return when (type) {
"EqualsSelf" -> equalityTester.test(getter.getTile(thisPos), getter.getTile(thisPos + offsetPos))
"Connects" -> getter.getTile(thisPos + offsetPos).material != null
@ -53,7 +53,7 @@ data class RenderRuleList(
}
}
fun test(getter: ITileGetter, equalityTester: EqualityRuleTester, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
fun test(getter: ITileChunk, equalityTester: EqualityRuleTester, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
if (inverse) {
return !doTest(getter, equalityTester, thisPos, offsetPos)
}
@ -67,7 +67,7 @@ data class RenderRuleList(
}
}
fun test(getter: ITileGetter, equalityTester: EqualityRuleTester, thisPos: Vector2i, offset: Vector2i): Boolean {
fun test(getter: ITileChunk, equalityTester: EqualityRuleTester, thisPos: Vector2i, offset: Vector2i): Boolean {
when (join) {
Combination.ALL -> {
for (entry in entries) {
@ -120,7 +120,7 @@ data class RenderMatch(
) {
var rule by WriteOnce<RenderRuleList>()
fun test(getter: ITileGetter, equalityTester: EqualityRuleTester, thisPos: Vector2i): Boolean {
fun test(getter: ITileChunk, equalityTester: EqualityRuleTester, thisPos: Vector2i): Boolean {
return rule.test(getter, equalityTester, thisPos, offset)
}
@ -156,7 +156,7 @@ data class RenderMatch(
*
* [equalityTester] требуется для проверки раенства между "этим" тайлом и другим
*/
fun test(tileAccess: ITileGetter, equalityTester: EqualityRuleTester, thisPos: Vector2i): Boolean {
fun test(tileAccess: ITileChunk, equalityTester: EqualityRuleTester, thisPos: Vector2i): Boolean {
for (matcher in matchAllPoints) {
if (!matcher.test(tileAccess, equalityTester, thisPos)) {
return false

View File

@ -0,0 +1,62 @@
package ru.dbotthepony.kstarbound.world
/**
* Предоставляет доступ к чанку и его соседям
*
* В основном для использования в местах, где нужен не мир, а определённый чанк мира,
* и при этом координаты проверяются относительно чанка и могут спокойно выйти за его пределы,
* с желанием получить тайл из соседнего чанка
*/
class CellView(
override val pos: ChunkPos,
val center: IChunk?,
val right: IChunk?,
val top: IChunk?,
val topRight: IChunk?,
val topLeft: IChunk?,
val left: IChunk?,
val bottom: IChunk?,
val bottomLeft: IChunk?,
val bottomRight: IChunk?,
) : IChunk {
val backgroundView = BackgroundView(this)
val foregroundView = ForegroundView(this)
override fun getCell(x: Int, y: Int): IChunkCell {
if (x in 0 ..CHUNK_SIZE_FF) {
if (y in 0 ..CHUNK_SIZE_FF) {
return center?.getCell(x, y) ?: IChunkCell.Companion
}
if (y < 0) {
return bottom?.getCell(x, y + CHUNK_SIZE) ?: IChunkCell.Companion
} else {
return top?.getCell(x, y - CHUNK_SIZE) ?: IChunkCell.Companion
}
}
if (x < 0) {
if (y in 0 ..CHUNK_SIZE_FF) {
return left?.getCell(x + CHUNK_SIZE, y) ?: IChunkCell.Companion
}
if (y < 0) {
return bottomLeft?.getCell(x + CHUNK_SIZE, y + CHUNK_SIZE) ?: IChunkCell.Companion
} else {
return topLeft?.getCell(x + CHUNK_SIZE, y - CHUNK_SIZE) ?: IChunkCell.Companion
}
} else {
if (y in 0 ..CHUNK_SIZE_FF) {
return right?.getCell(x - CHUNK_SIZE, y) ?: IChunkCell.Companion
}
if (y < 0) {
return bottomRight?.getCell(x - CHUNK_SIZE, y + CHUNK_SIZE) ?: IChunkCell.Companion
} else {
return topRight?.getCell(x - CHUNK_SIZE, y - CHUNK_SIZE) ?: IChunkCell.Companion
}
}
}
}

View File

@ -5,6 +5,8 @@ import ru.dbotthepony.kbox2d.api.FixtureDef
import ru.dbotthepony.kbox2d.collision.shapes.PolygonShape
import ru.dbotthepony.kbox2d.dynamics.B2Fixture
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.util.TwoDimensionalArray
import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kstarbound.world.phys.RectTileFlooderDepthFirst
@ -26,24 +28,22 @@ import kotlin.collections.HashSet
*
* Весь игровой мир будет измеряться в Starbound Unit'ах
*/
abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType, This>>(val world: WorldType, val pos: ChunkPos) {
/**
* Возвращает общий счётчик изменений чанка
*/
abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType, This>>(val world: WorldType, final override val pos: ChunkPos) : IChunk {
var changeset = 0
private set
/**
* Возвращает счётчик изменений чанка по тайлам
*/
var tileChangeset = 0
private set
/**
* Возвращает счётчик изменений чанка по жидкостям
*/
var liquidChangeset = 0
private set
var cellChangeset = 0
private set
var collisionChangeset = 0
private set
var foregroundChangeset = 0
private set
var backgroundChangeset = 0
private set
val left get() = pos.left
val right get() = pos.right
@ -66,173 +66,232 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
world.dirtyPhysicsChunks.add(this as This)
}
fun bakeCollisions() {
foreground.bakeCollisions()
protected val cells = Object2DArray(CHUNK_SIZE, CHUNK_SIZE, ::Cell)
val backgroundView = BackgroundView(this)
val foregroundView = ForegroundView(this)
override fun getCell(x: Int, y: Int): IChunkCell {
return cells[x, y]
}
inner class LiquidState(val def: LiquidDefinition) {
var pressure: Float = 0f
set(value) {
if (value != field) {
field = value
liquidChangeset++
changeset++
private val collisionCache = ArrayList<AABB>()
private val collisionCacheView = Collections.unmodifiableCollection(collisionCache)
inner class Cell(val x: Int, val y: Int) : IChunkCell {
inner class Tile(private val foreground: Boolean) : ITileState {
private fun change() {
changeset++
cellChangeset++
tileChangeset++
if (foreground) {
collisionChangeset++
foregroundChangeset++
markPhysicsDirty()
} else {
backgroundChangeset++
}
}
var level: Float = 1f
set(value) {
if (value != field) {
field = value
liquidChangeset++
changeset++
}
}
var isInfinite: Boolean = false
set(value) {
if (value != field) {
field = value
liquidChangeset++
changeset++
}
}
}
inner class TileLayer : IMutableTileChunk {
/**
* Возвращает счётчик изменений этого слоя
*/
var changeset = 0
private set
private val collisionCache = ArrayList<AABB>()
private val collisionCacheView = Collections.unmodifiableCollection(collisionCache)
private var collisionChangeset = -1
private val body = world.physics.createBody(BodyDef(
position = pos.firstBlock.toDoubleVector(),
userData = this
))
private val collisionChains = ArrayList<B2Fixture>()
fun bakeCollisions() {
if (collisionChangeset == changeset)
return
collisionChangeset = changeset
collisionCache.clear()
for (box in collisionChains) {
body.destroyFixture(box)
}
collisionChains.clear()
val xyAdd = Vector2d(pos.x * CHUNK_SIZEd, pos.y * CHUNK_SIZEd)
val seen = BooleanArray(CHUNK_SIZE * CHUNK_SIZE)
for (y in 0 .. CHUNK_SIZE_FF) {
for (x in 0..CHUNK_SIZE_FF) {
if (!seen[x or (y shl CHUNK_SHIFT)] && tiles[x, y].material != null) {
val depthFirst = RectTileFlooderDepthFirst(tiles, seen, x, y)
val sizeFirst = RectTileFlooderSizeFirst(tiles, seen, x, y)
val xSpanDepth = depthFirst.maxs.x - depthFirst.mins.x
val ySpanDepth = depthFirst.maxs.y - depthFirst.mins.y
val xSpanSize = sizeFirst.maxs.x - sizeFirst.mins.x
val ySpanSize = sizeFirst.maxs.y - sizeFirst.mins.y
val aabb: AABB
if (xSpanDepth * ySpanDepth > xSpanSize * ySpanSize) {
depthFirst.markSeen()
aabb = AABB(depthFirst.mins.toDoubleVector(), depthFirst.maxs.toDoubleVector() + Vector2d.POSITIVE_XY)
} else {
sizeFirst.markSeen()
aabb = AABB(sizeFirst.mins.toDoubleVector(), sizeFirst.maxs.toDoubleVector() + Vector2d.POSITIVE_XY)
}
collisionChains.add(body.createFixture(FixtureDef(
shape = PolygonShape().also { it.setAsBox(aabb.width / 2.0, aabb.height / 2.0, aabb.centre, 0.0) },
friction = 0.4,
)))
collisionCache.add(aabb + xyAdd)
override var material: TileDefinition? = null
set(value) {
if (field !== value) {
field = value
change()
}
}
}
override var modifier: MaterialModifier? = null
set(value) {
if (field !== value) {
field = value
change()
}
}
override var color: TileColor = TileColor.DEFAULT
set(value) {
if (field != value) {
field = value
change()
}
}
override var hueShift: Float = 0f
set(value) {
if (field != value) {
field = value.coerceIn(0f, 360f)
change()
}
}
override var modifierHueShift: Float = 0f
set(value) {
if (field != value) {
field = value.coerceIn(0f, 360f)
change()
}
}
override val isEmpty: Boolean
get() = false
}
/**
* Возвращает список AABB тайлов этого слоя
*
* Данный список напрямую указывает на внутреннее состояние и будет изменён при перестройке
* коллизии чанка, поэтому если необходим стабильный список, его необходимо скопировать
*/
fun collisionLayers(): Collection<AABB> {
if (collisionChangeset != changeset) {
bakeCollisions()
inner class Liquid : ILiquidState {
private fun change() {
changeset++
cellChangeset++
liquidChangeset++
}
return collisionCacheView
override var def: LiquidDefinition? = null
set(value) {
if (field !== value) {
field = value
change()
}
}
override var level: Float = 0f
set(value) {
if (field != value) {
field = value
change()
}
}
override var pressure: Float = 0f
set(value) {
if (field != value) {
field = value
change()
}
}
override var isInfinite: Boolean = false
set(value) {
if (field != value) {
field = value
change()
}
}
override val isEmpty: Boolean
get() = false
}
override val pos: ChunkPos
get() = this@Chunk.pos
inline val left get() = pos.left
inline val right get() = pos.right
inline val top get() = pos.top
inline val bottom get() = pos.bottom
inline val topLeft get() = pos.topLeft
inline val topRight get() = pos.topRight
inline val bottomLeft get() = pos.bottomLeft
inline val bottomRight get() = pos.bottomRight
/**
* Хранит тайлы как x + y * CHUNK_SIZE
*/
private val tiles: Object2DArray<TileState> = Object2DArray(CHUNK_SIZE, CHUNK_SIZE, TileState.EMPTY)
override fun getTile(x: Int, y: Int): TileState {
return tiles[x, y]
}
override fun setTile(x: Int, y: Int, value: TileState) {
tiles[x, y] = value
tileChangeset++
private fun change() {
changeset++
collisionChangeset++
this@Chunk.changeset++
markPhysicsDirty()
cellChangeset++
}
override fun randomLongFor(x: Int, y: Int): Long {
return super.randomLongFor(x, y) xor world.seed
override val foreground = Tile(true)
override val background = Tile(false)
override val liquid = Liquid()
override var dungeonId: Int = 0
set(value) {
if (field != value) {
field = value
change()
}
}
override var biome: Int = 0
set(value) {
if (field != value) {
field = value
change()
}
}
override var envBiome: Int = 0
set(value) {
if (field != value) {
field = value
change()
}
}
override var isIndestructible: Boolean = false
set(value) {
if (field != value) {
field = value
change()
}
}
override val isEmpty: Boolean
get() = false
}
private val body = world.physics.createBody(BodyDef(
position = pos.firstBlock.toDoubleVector(),
userData = this
))
private val collisionChains = ArrayList<B2Fixture>()
fun bakeCollisions() {
if (collisionChangeset == changeset)
return
collisionChangeset = changeset
collisionCache.clear()
for (box in collisionChains) {
body.destroyFixture(box)
}
collisionChains.clear()
val xyAdd = Vector2d(pos.x * CHUNK_SIZEd, pos.y * CHUNK_SIZEd)
val seen = BooleanArray(CHUNK_SIZE * CHUNK_SIZE)
for (y in 0 .. CHUNK_SIZE_FF) {
for (x in 0..CHUNK_SIZE_FF) {
if (!seen[x or (y shl CHUNK_SHIFT)] && cells[x, y].foreground.material != null) {
val depthFirst = RectTileFlooderDepthFirst(cells, seen, x, y)
val sizeFirst = RectTileFlooderSizeFirst(cells, seen, x, y)
val xSpanDepth = depthFirst.maxs.x - depthFirst.mins.x
val ySpanDepth = depthFirst.maxs.y - depthFirst.mins.y
val xSpanSize = sizeFirst.maxs.x - sizeFirst.mins.x
val ySpanSize = sizeFirst.maxs.y - sizeFirst.mins.y
val aabb: AABB
if (xSpanDepth * ySpanDepth > xSpanSize * ySpanSize) {
depthFirst.markSeen()
aabb = AABB(depthFirst.mins.toDoubleVector(), depthFirst.maxs.toDoubleVector() + Vector2d.POSITIVE_XY)
} else {
sizeFirst.markSeen()
aabb = AABB(sizeFirst.mins.toDoubleVector(), sizeFirst.maxs.toDoubleVector() + Vector2d.POSITIVE_XY)
}
collisionChains.add(body.createFixture(FixtureDef(
shape = PolygonShape().also { it.setAsBox(aabb.width / 2.0, aabb.height / 2.0, aabb.centre, 0.0) },
friction = 0.4,
)))
collisionCache.add(aabb + xyAdd)
}
}
}
}
val foreground = TileLayer()
val background = TileLayer()
/**
* Возвращает список AABB тайлов этого слоя
*
* Данный список напрямую указывает на внутреннее состояние и будет изменён при перестройке
* коллизии чанка, поэтому если необходим стабильный список, его необходимо скопировать
*/
fun collisionLayers(): Collection<AABB> {
bakeCollisions()
return collisionCacheView
}
protected val liquidStates: Object2DArray<LiquidState?> = Object2DArray.nulls(CHUNK_SIZE, CHUNK_SIZE)
fun getLiquid(x: Int, y: Int) = liquidStates[x, y]
fun setLiquid(x: Int, y: Int, value: LiquidDefinition?): LiquidState? {
if (value == null) {
liquidStates.set(x, y, null)
changeset++
liquidChangeset++
return null
}
val state = LiquidState(value)
liquidStates[x, y] = state
changeset++
liquidChangeset++
return state
override fun randomLongFor(x: Int, y: Int): Long {
return super.randomLongFor(x, y) xor world.seed
}
protected val entities = HashSet<Entity>()

View File

@ -10,69 +10,13 @@ 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 {
/**
* Возвращает тайл по ОТНОСИТЕЛЬНЫМ координатам внутри чанка
*/
fun getTile(x: Int, y: Int): TileState
/**
* Возвращает тайл по ОТНОСИТЕЛЬНЫМ координатам внутри чанка
*/
fun getTile(pos: IStruct2i) = getTile(pos.component1(), pos.component2())
}
interface ITileSetter : ITileMap {
fun setTile(x: Int, y: Int, value: TileState)
fun setTile(pos: IStruct2i, value: TileState) = setTile(pos.component1(), pos.component2(), value)
}
fun ITileGetter.iterate(fromX: Int = 0, fromY: Int = 0, toX: Int = fromX + CHUNK_SIZE, toY: Int = fromY + CHUNK_SIZE): Iterator<Pair<Vector2i, TileState>> {
return object : Iterator<Pair<Vector2i, TileState>> {
private var x = fromX
private var y = fromY
override fun hasNext(): Boolean {
return x < toX && y < toY
}
override fun next(): Pair<Vector2i, TileState> {
if (!hasNext())
throw NoSuchElementException()
val tile = getTile(x, y)
val pos = Vector2i(x, y)
x++
if (x >= toX) {
y++
x = 0
}
return pos to tile
}
}
}
/**
* Интерфейс предоставляет из себя описание класса, который имеет координаты чанка
*/
interface IChunkPositionable : ITileMap {
interface IChunk {
val pos: ChunkPos
// relative
fun getCell(x: Int, y: Int): IChunkCell
fun getCell(pos: IStruct2i) = getCell(pos.component1(), pos.component2())
/**
* Возвращает псевдослучайное Long для заданной позиции
*
@ -111,5 +55,77 @@ interface IChunkPositionable : ITileMap {
fun randomDoubleFor(pos: Vector2i) = randomDoubleFor(pos.x, pos.y)
}
interface ITileChunk : ITileGetter, IChunkPositionable
interface IMutableTileChunk : ITileChunk, ITileSetter
// for getting tiles directly, avoiding manual layer specification
interface ITileChunk : IChunk {
// relative
fun getTile(x: Int, y: Int): ITileState
fun getTile(pos: IStruct2i) = getTile(pos.component1(), pos.component2())
}
class ForegroundView(private val parent: IChunk) : ITileChunk, IChunk by parent {
override fun getTile(x: Int, y: Int): ITileState {
return parent.getCell(x, y).foreground
}
}
class BackgroundView(private val parent: IChunk) : ITileChunk, IChunk by parent {
override fun getTile(x: Int, y: Int): ITileState {
return parent.getCell(x, y).background
}
}
fun IChunk.iterate(fromX: Int = 0, fromY: Int = 0, toX: Int = fromX + CHUNK_SIZE, toY: Int = fromY + CHUNK_SIZE): Iterator<Pair<Vector2i, IChunkCell>> {
return object : Iterator<Pair<Vector2i, IChunkCell>> {
private var x = fromX
private var y = fromY
override fun hasNext(): Boolean {
return x < toX && y < toY
}
override fun next(): Pair<Vector2i, IChunkCell> {
if (!hasNext())
throw NoSuchElementException()
val tile = getCell(x, y)
val pos = Vector2i(x, y)
x++
if (x >= toX) {
y++
x = 0
}
return pos to tile
}
}
}
fun ITileChunk.iterate(fromX: Int = 0, fromY: Int = 0, toX: Int = fromX + CHUNK_SIZE, toY: Int = fromY + CHUNK_SIZE): Iterator<Pair<Vector2i, ITileState>> {
return object : Iterator<Pair<Vector2i, ITileState>> {
private var x = fromX
private var y = fromY
override fun hasNext(): Boolean {
return x < toX && y < toY
}
override fun next(): Pair<Vector2i, ITileState> {
if (!hasNext())
throw NoSuchElementException()
val tile = getTile(x, y)
val pos = Vector2i(x, y)
x++
if (x >= toX) {
y++
x = 0
}
return pos to tile
}
}
}

View File

@ -1,43 +0,0 @@
package ru.dbotthepony.kstarbound.world
import ru.dbotthepony.kstarbound.util.NotNullTwoDimensionalArray
import ru.dbotthepony.kvector.arrays.Object2DArray
/**
* Предоставляет доступ к чанку и его соседям
*
* Данный вариант отличается от [TileView] тем, что данный класс *копирует* список тайлов из всех чанков, которые ему известны
* в единый массив.
*
* Полезно в ситуациях, когда необходимо максимально быстрое получение данных о тайлах.
*/
class RigidTileView(
view: TileView,
) : ITileChunk {
constructor(
pos: ChunkPos,
center: ITileChunk?,
right: ITileChunk?,
top: ITileChunk?,
topRight: ITileChunk?,
topLeft: ITileChunk?,
left: ITileChunk?,
bottom: ITileChunk?,
bottomLeft: ITileChunk?,
bottomRight: ITileChunk?,
) : this(TileView(pos, center, right, top, topRight, topLeft, left, bottom, bottomLeft, bottomRight))
override val pos: ChunkPos = view.pos
private val memory: Object2DArray<TileState>
init {
memory = Object2DArray(CHUNK_SIZE * 3, CHUNK_SIZE * 3) { a, b -> view.getTile(a - CHUNK_SIZE, b - CHUNK_SIZE) }
}
override fun getTile(x: Int, y: Int): TileState {
return memory[x + CHUNK_SIZE, y + CHUNK_SIZE]
}
}

View File

@ -1,86 +0,0 @@
package ru.dbotthepony.kstarbound.world
import ru.dbotthepony.kstarbound.RegistryObject
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import java.io.DataInputStream
enum class TileColor {
DEFAULT,
RED,
BLUE,
GREEN,
YELLOW,
BROWN,
PURPLE,
BLACK,
WHITE;
companion object {
private val values = values()
fun of(index: Int): TileColor {
return values.getOrElse(index) { DEFAULT }
}
}
}
data class TileState(
val material: TileDefinition? = null,
val modifier: MaterialModifier? = null,
val color: TileColor = TileColor.DEFAULT,
val hueShift: Float = 0f,
val modifierHueShift: Float = 0f,
) {
private fun with(
material: TileDefinition? = this.material,
modifier: MaterialModifier? = this.modifier,
color: TileColor = this.color,
hueShift: Float = this.hueShift,
modifierHueShift: Float = this.modifierHueShift,
): TileState {
if (this.material === material && this.modifier === modifier && this.color == color && this.hueShift == hueShift && this.modifierHueShift == modifierHueShift) {
return this
} else {
return TileState(material, modifier, color, hueShift, modifierHueShift)
}
}
fun withHueShift(hueShift: Int): TileState {
if (hueShift < 0) {
return with(hueShift = 0f)
} else if (hueShift >= 255) {
return with(hueShift = 360f)
} else {
return with(hueShift = hueShift / 255f * 360f)
}
}
fun withModifierHueShift(hueShift: Int): TileState {
if (hueShift < 0) {
return with(modifierHueShift = 0f)
} else if (hueShift >= 255) {
return with(modifierHueShift = 360f)
} else {
return with(modifierHueShift = hueShift / 255f * 360f)
}
}
companion object {
val EMPTY = TileState()
fun read(
materialAccess: (Int) -> RegistryObject<TileDefinition>?,
modifierAccess: (Int) -> RegistryObject<MaterialModifier>?,
stream: DataInputStream
): TileState {
val mat = materialAccess(stream.readUnsignedShort())
val hue = stream.read()
val color = stream.read()
val mod = modifierAccess(stream.readUnsignedShort())
val modHue = stream.read()
return TileState(mat?.value, mod?.value, TileColor.of(color), hue / 255f * 360f, modHue / 255f * 360f)
}
}
}

View File

@ -1,78 +0,0 @@
package ru.dbotthepony.kstarbound.world
/**
* Предоставляет доступ к чанку и его соседям
*
* В основном для использования в местах, где нужен не мир, а определённый чанк мира,
* и при этом координаты проверяются относительно чанка и могут спокойно выйти за его пределы,
* с желанием получить тайл из соседнего чанка
*/
open class TileView(
override val pos: ChunkPos,
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 getTile(x: Int, y: Int): TileState {
if (x in 0 ..CHUNK_SIZE_FF) {
if (y in 0 ..CHUNK_SIZE_FF) {
return center?.getTile(x, y) ?: TileState.EMPTY
}
if (y < 0) {
return bottom?.getTile(x, y + CHUNK_SIZE) ?: TileState.EMPTY
} else {
return top?.getTile(x, y - CHUNK_SIZE) ?: TileState.EMPTY
}
}
if (x < 0) {
if (y in 0 ..CHUNK_SIZE_FF) {
return left?.getTile(x + CHUNK_SIZE, y) ?: TileState.EMPTY
}
if (y < 0) {
return bottomLeft?.getTile(x + CHUNK_SIZE, y + CHUNK_SIZE) ?: TileState.EMPTY
} else {
return topLeft?.getTile(x + CHUNK_SIZE, y - CHUNK_SIZE) ?: TileState.EMPTY
}
} else {
if (y in 0 ..CHUNK_SIZE_FF) {
return right?.getTile(x - CHUNK_SIZE, y) ?: TileState.EMPTY
}
if (y < 0) {
return bottomRight?.getTile(x - CHUNK_SIZE, y + CHUNK_SIZE) ?: TileState.EMPTY
} else {
return topRight?.getTile(x - CHUNK_SIZE, y - CHUNK_SIZE) ?: TileState.EMPTY
}
}
}
}
class MutableTileView(
pos: ChunkPos,
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(pos, center, right, top, topRight, topLeft, left, bottom, bottomLeft, bottomRight), IMutableTileChunk {
override fun setTile(x: Int, y: Int, value: TileState) {
TODO()
}
}

View File

@ -0,0 +1,181 @@
package ru.dbotthepony.kstarbound.world
import ru.dbotthepony.kstarbound.RegistryObject
import ru.dbotthepony.kstarbound.defs.tile.LiquidDefinition
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import java.io.DataInputStream
enum class TileColor {
DEFAULT,
RED,
BLUE,
GREEN,
YELLOW,
BROWN,
PURPLE,
BLACK,
WHITE;
companion object {
private val values = values()
fun of(index: Int): TileColor {
return values.getOrElse(index) { DEFAULT }
}
}
}
interface ITileState {
var material: TileDefinition?
var modifier: MaterialModifier?
var color: TileColor
var hueShift: Float
var modifierHueShift: Float
val isEmpty: Boolean
fun setHueShift(value: Int) {
if (value < 0) {
hueShift = 0f
} else if (value >= 255) {
hueShift = 360f
} else {
hueShift = value / 255f * 360f
}
}
fun setModHueShift(value: Int) {
if (value < 0) {
modifierHueShift = 0f
} else if (value >= 255) {
modifierHueShift = 360f
} else {
modifierHueShift = value / 255f * 360f
}
}
fun read(
materialAccess: (Int) -> RegistryObject<TileDefinition>?,
modifierAccess: (Int) -> RegistryObject<MaterialModifier>?,
stream: DataInputStream
) {
material = materialAccess(stream.readUnsignedShort())?.value
setHueShift(stream.read())
color = TileColor.of(stream.read())
modifier = modifierAccess(stream.readUnsignedShort())?.value
setModHueShift(stream.read())
}
companion object : ITileState {
override var material: TileDefinition?
get() = null
set(value) { throw UnsupportedOperationException("Empty tile state") }
override var modifier: MaterialModifier?
get() = null
set(value) { throw UnsupportedOperationException("Empty tile state") }
override var color: TileColor
get() = TileColor.DEFAULT
set(value) { throw UnsupportedOperationException("Empty tile state") }
override var hueShift: Float
get() = 0f
set(value) { throw UnsupportedOperationException("Empty tile state") }
override var modifierHueShift: Float
get() = 0f
set(value) { throw UnsupportedOperationException("Empty tile state") }
override val isEmpty: Boolean
get() = true
}
}
interface ILiquidState {
var def: LiquidDefinition?
var level: Float
var pressure: Float
var isInfinite: Boolean
val isEmpty: Boolean
fun read(
liquidAccess: (Int) -> RegistryObject<LiquidDefinition>?,
stream: DataInputStream
) {
def = liquidAccess(stream.readUnsignedByte())?.value
level = stream.readFloat()
pressure = stream.readFloat()
isInfinite = stream.readBoolean()
}
companion object : ILiquidState {
override var def: LiquidDefinition?
get() = null
set(value) { throw UnsupportedOperationException("Empty liquid state") }
override var level: Float
get() = 0f
set(value) { throw UnsupportedOperationException("Empty liquid state") }
override var pressure: Float
get() = 0f
set(value) { throw UnsupportedOperationException("Empty liquid state") }
override var isInfinite: Boolean
get() = false
set(value) { throw UnsupportedOperationException("Empty liquid state") }
override val isEmpty: Boolean
get() = true
}
}
interface IChunkCell {
val foreground: ITileState
val background: ITileState
val liquid: ILiquidState
var dungeonId: Int
var biome: Int
var envBiome: Int
var isIndestructible: Boolean
val isEmpty: Boolean
fun read(
materialAccess: (Int) -> RegistryObject<TileDefinition>?,
modifierAccess: (Int) -> RegistryObject<MaterialModifier>?,
liquidAccess: (Int) -> RegistryObject<LiquidDefinition>?,
stream: DataInputStream
) {
foreground.read(materialAccess, modifierAccess, stream)
background.read(materialAccess, modifierAccess, stream)
liquid.read(liquidAccess, stream)
stream.skipBytes(1) // collisionMap
dungeonId = stream.readUnsignedShort()
biome = stream.readUnsignedByte()
envBiome = stream.readUnsignedByte()
isIndestructible = stream.readBoolean()
stream.skipBytes(1) // unknown
}
companion object : IChunkCell {
override val foreground: ITileState
get() = ITileState.Companion
override val background: ITileState
get() = ITileState.Companion
override val liquid: ILiquidState
get() = ILiquidState.Companion
override var dungeonId: Int
get() = 0
set(value) { throw UnsupportedOperationException("Empty chunk cell") }
override var biome: Int
get() = 0
set(value) { throw UnsupportedOperationException("Empty chunk cell") }
override var envBiome: Int
get() = 0
set(value) { throw UnsupportedOperationException("Empty chunk cell") }
override var isIndestructible: Boolean
get() = false
set(value) { throw UnsupportedOperationException("Empty chunk cell") }
override val isEmpty: Boolean
get() = true
}
}

View File

@ -1,8 +1,8 @@
package ru.dbotthepony.kstarbound.world
import com.google.common.collect.ImmutableList
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
import ru.dbotthepony.kbox2d.api.ContactImpulse
import ru.dbotthepony.kbox2d.api.IContactFilter
import ru.dbotthepony.kbox2d.api.IContactListener
@ -29,8 +29,8 @@ import kotlin.math.sin
const val EARTH_FREEFALL_ACCELERATION = 9.8312 / METRES_IN_STARBOUND_UNIT
data class RayCastResult(
val traversedTiles: List<Pair<Vector2i, TileState>>,
val hitTile: Pair<Vector2i, TileState>?,
val traversedTiles: List<Pair<Vector2i, IChunkCell>>,
val hitTile: Pair<Vector2i, IChunkCell>?,
val fraction: Double
)
@ -88,7 +88,7 @@ enum class RayFilterResult {
}
fun interface TileRayFilter {
fun test(state: TileState, fraction: Double, position: Vector2i): RayFilterResult
fun test(state: IChunkCell, fraction: Double, position: Vector2i): RayFilterResult
}
/**
@ -99,23 +99,23 @@ val AnythingRayFilter = TileRayFilter { state, t, pos -> RayFilterResult.CONTINU
/**
* Попадает по первому не-пустому тайлу
*/
val NonSolidRayFilter = TileRayFilter { state, t, pos -> RayFilterResult.of(state.material != null) }
val NonSolidRayFilter = TileRayFilter { state, t, pos -> RayFilterResult.of(state.foreground.material != null) }
/**
* Попадает по первому пустому тайлу
*/
val SolidRayFilter = TileRayFilter { state, t, pos -> RayFilterResult.of(state.material == null) }
val SolidRayFilter = TileRayFilter { state, t, pos -> RayFilterResult.of(state.foreground.material == null) }
/**
* Попадает по первому тайлу который блокирует проход света
*/
val LineOfSightRayFilter = TileRayFilter { state, t, pos -> RayFilterResult.of(state.material?.renderParameters?.lightTransparent == false) }
val LineOfSightRayFilter = TileRayFilter { state, t, pos -> RayFilterResult.of(state.foreground.material?.renderParameters?.lightTransparent == false) }
abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, ChunkType>>(
val seed: Long,
val widthInChunks: Int
) {
protected val chunkMap = Object2ObjectOpenHashMap<ChunkPos, ChunkType>()
protected val chunkMap = Long2ObjectOpenHashMap<ChunkType>()
/**
* Является ли мир "сферическим"
@ -300,12 +300,12 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
*/
open operator fun get(pos: ChunkPos): ChunkType? {
if (!isCircular) {
return chunkMap[pos]
return chunkMap[pos.toLong()]
}
@Suppress("Name_Shadowing")
val pos = pos.circular(widthInChunks)
return chunkMap[pos]
return chunkMap[pos.toLong()]
}
open fun getInstantTuple(pos: ChunkPos): IWorldChunkTuple<This, ChunkType>? {
@ -319,7 +319,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
@Suppress("Name_Shadowing")
val pos = if (isCircular) pos.circular(widthInChunks) else pos
return chunkMap.computeIfAbsent(pos, Object2ObjectFunction {
return chunkMap.computeIfAbsent(pos.toLong(), Long2ObjectFunction {
val chunk = chunkFactory(pos)
val orphanedInThisChunk = ArrayList<Entity>()
@ -336,7 +336,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
ent.chunk = chunk
}
return@Object2ObjectFunction chunk
return@Long2ObjectFunction chunk
})
}
@ -360,12 +360,8 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
return lastChunk
}
fun getTile(pos: Vector2i): TileState? {
return get(ChunkPos.fromTilePosition(pos))?.foreground?.getTile(ChunkPos.normalizeCoordinate(pos.x), ChunkPos.normalizeCoordinate(pos.y))
}
fun getBackgroundTile(pos: Vector2i): TileState? {
return get(ChunkPos.fromTilePosition(pos))?.background?.getTile(ChunkPos.normalizeCoordinate(pos.x), ChunkPos.normalizeCoordinate(pos.y))
fun getCell(pos: Vector2i): IChunkCell? {
return get(ChunkPos.fromTilePosition(pos))?.getCell(ChunkPos.normalizeCoordinate(pos.x), ChunkPos.normalizeCoordinate(pos.y))
}
/**
@ -384,16 +380,16 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
val dir = rayEnd - rayStart
val inc = 0.5 / dir.length
val tiles = LinkedList<Pair<Vector2i, TileState>>()
val tiles = LinkedList<Pair<Vector2i, IChunkCell>>()
var prev = Vector2i(Int.MIN_VALUE, Int.MAX_VALUE)
var hitTile: Pair<Vector2i, TileState>? = null
var hitTile: Pair<Vector2i, IChunkCell>? = null
while (t < 1.0) {
val (x, y) = rayStart + dir * t
val tilePos = Vector2i(x.roundToInt(), y.roundToInt())
if (tilePos != prev) {
val tile = getTile(tilePos) ?: TileState.EMPTY
val tile = getCell(tilePos) ?: IChunkCell.Companion
when (filter.test(tile, t, tilePos)) {
RayFilterResult.HIT -> {
@ -449,7 +445,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
var currentIntensity = intensity
castRayNaive(position, direction, intensity) { state, t, pos ->
if (state.material?.renderParameters?.lightTransparent == false) {
if (state.foreground.material?.renderParameters?.lightTransparent == false) {
currentIntensity -= falloffByTile
} else {
currentIntensity -= falloffByTravel
@ -545,56 +541,23 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
return CachedGetter().rayLightCircleNaive(position, intensity, falloffByTile, falloffByTravel)
}
open fun getForegroundView(pos: ChunkPos): TileView {
fun getView(pos: ChunkPos): CellView {
val tuple = get(pos)?.let { InstantWorldChunkTuple(this as This, it) }
return TileView(
return CellView(
pos = pos,
center = tuple?.chunk?.foreground,
left = tuple?.left?.chunk?.foreground,
top = tuple?.top?.chunk?.foreground,
topLeft = tuple?.topLeft?.chunk?.foreground,
topRight = tuple?.topRight?.chunk?.foreground,
right = tuple?.right?.chunk?.foreground,
bottom = tuple?.bottom?.chunk?.foreground,
bottomLeft = tuple?.bottomLeft?.chunk?.foreground,
bottomRight = tuple?.bottomRight?.chunk?.foreground,
center = tuple?.chunk,
left = tuple?.left?.chunk,
top = tuple?.top?.chunk,
topLeft = tuple?.topLeft?.chunk,
topRight = tuple?.topRight?.chunk,
right = tuple?.right?.chunk,
bottom = tuple?.bottom?.chunk,
bottomLeft = tuple?.bottomLeft?.chunk,
bottomRight = tuple?.bottomRight?.chunk,
)
}
open fun getBackgroundView(pos: ChunkPos): TileView {
val tuple = get(pos)?.let { InstantWorldChunkTuple(this as This, it) }
return TileView(
pos = pos,
center = tuple?.chunk?.background,
left = tuple?.left?.chunk?.background,
top = tuple?.top?.chunk?.background,
topLeft = tuple?.topLeft?.chunk?.background,
topRight = tuple?.topRight?.chunk?.background,
right = tuple?.right?.chunk?.background,
bottom = tuple?.bottom?.chunk?.background,
bottomLeft = tuple?.bottomLeft?.chunk?.background,
bottomRight = tuple?.bottomRight?.chunk?.background,
)
}
open fun getRigidForegroundView(pos: ChunkPos): RigidTileView {
return RigidTileView(getForegroundView(pos))
}
open fun getRigidBackgroundView(pos: ChunkPos): RigidTileView {
return RigidTileView(getBackgroundView(pos))
}
fun getTile(pos: Vector2i): TileState? {
return get(ChunkPos.fromTilePosition(pos))?.foreground?.getTile(ChunkPos.normalizeCoordinate(pos.x), ChunkPos.normalizeCoordinate(pos.y))
}
fun getBackgroundTile(pos: Vector2i): TileState? {
return get(ChunkPos.fromTilePosition(pos))?.background?.getTile(ChunkPos.normalizeCoordinate(pos.x), ChunkPos.normalizeCoordinate(pos.y))
}
/**
* Возвращает все чанки, которые пересекаются с заданным [boundingBox]
*/
@ -634,7 +597,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
*/
fun isSpaceEmptyFromTiles(worldaabb: AABB): Boolean {
val collected = collect(worldaabb.encasingChunkPosAABB())
.map { it.foreground.collisionLayers() }
.map { it.collisionLayers() }
.flatten()
.sortedWith { o1, o2 ->
val a = o1.distance(worldaabb)
@ -666,7 +629,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
private fun floodLightInto(
lightmap: Int2DArray,
view: RigidTileView,
view: CellView,
thisIntensity: Int,
lightBlockerStrength: Int,
posX: Int,
@ -678,11 +641,11 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
return 1
}
val tile = view.getTile(worldPosX, worldPosY)
val tile = view.getCell(worldPosX, worldPosY)
val newIntensity: Int
if (tile.material?.renderParameters?.lightTransparent == false) {
if (tile.foreground.material?.renderParameters?.lightTransparent == false) {
newIntensity = thisIntensity - lightBlockerStrength
} else {
newIntensity = thisIntensity - 1
@ -750,7 +713,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
val lightmap = Int2DArray.allocate(lightIntensity * 2 + 1, lightIntensity * 2 + 1)
val view = getRigidForegroundView(ChunkPos.fromTilePosition(lightPosition))
val view = getView(ChunkPos.fromTilePosition(lightPosition))
floodLightInto(
lightmap,

View File

@ -2,12 +2,12 @@ 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.TileState
import ru.dbotthepony.kstarbound.world.IChunkCell
import ru.dbotthepony.kvector.arrays.Object2DArray
import ru.dbotthepony.kvector.vector.Vector2i
class RectTileFlooderDepthFirst(
private val tiles: Object2DArray<out TileState>,
private val tiles: Object2DArray<out IChunkCell>,
private val seen: BooleanArray,
rootx: Int,
rooty: Int
@ -24,7 +24,7 @@ class RectTileFlooderDepthFirst(
return false
}
return !seen[x or (y shl CHUNK_SHIFT)] && tiles[x, y].material != null
return !seen[x or (y shl CHUNK_SHIFT)] && tiles[x, y].foreground.material != null
}
init {

View File

@ -2,12 +2,12 @@ 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.TileState
import ru.dbotthepony.kstarbound.world.IChunkCell
import ru.dbotthepony.kvector.arrays.Object2DArray
import ru.dbotthepony.kvector.vector.Vector2i
class RectTileFlooderSizeFirst(
private val tiles: Object2DArray<out TileState>,
private val tiles: Object2DArray<out IChunkCell>,
private val seen: BooleanArray,
private val rootx: Int,
private val rooty: Int
@ -24,7 +24,7 @@ class RectTileFlooderSizeFirst(
return false
}
return !seen[x or (y shl CHUNK_SHIFT)] && tiles[x, y].material != null
return !seen[x or (y shl CHUNK_SHIFT)] && tiles[x, y].foreground.material != null
}
private var widthPositive = 0

View File

@ -1,75 +0,0 @@
package ru.dbotthepony.kstarbound.world.phys
import ru.dbotthepony.kstarbound.util.NotNullTwoDimensionalArray
import ru.dbotthepony.kstarbound.world.CHUNK_SHIFT
import ru.dbotthepony.kstarbound.world.CHUNK_SIZE_FF
import ru.dbotthepony.kstarbound.world.TileState
import ru.dbotthepony.kvector.vector.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: NotNullTwoDimensionalArray<out TileState>,
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, y].material != 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)
}
}