Chunk map, circular worlds second attempt
This commit is contained in:
parent
e436864e12
commit
0657ee8ef7
@ -65,7 +65,8 @@ fun main() {
|
||||
var parse = 0L
|
||||
|
||||
//for (chunkX in 17 .. 18) {
|
||||
for (chunkX in 14 .. 24) {
|
||||
//for (chunkX in 14 .. 24) {
|
||||
for (chunkX in 0 .. 100) {
|
||||
// for (chunkY in 21 .. 21) {
|
||||
for (chunkY in 18 .. 24) {
|
||||
var t = System.currentTimeMillis()
|
||||
@ -73,7 +74,7 @@ fun main() {
|
||||
find += System.currentTimeMillis() - t
|
||||
|
||||
if (data != null) {
|
||||
val chunk = client.world!!.computeIfAbsent(ChunkPos(chunkX, chunkY))
|
||||
val chunk = client.world!!.chunkMap.computeIfAbsent(ChunkPos(chunkX, chunkY))
|
||||
val inflater = Inflater()
|
||||
inflater.setInput(data)
|
||||
|
||||
@ -105,12 +106,15 @@ fun main() {
|
||||
val item = starbound.items.values.random()
|
||||
val rand = java.util.Random()
|
||||
|
||||
for (i in 0 .. 10) {
|
||||
client.world!!.physics.gravity = Vector2d.ZERO
|
||||
|
||||
for (i in 0 .. 0) {
|
||||
val item = ItemEntity(client.world!!, item.value)
|
||||
|
||||
item.position = Vector2d(600.0 + 16.0 + i, 721.0 + 48.0)
|
||||
item.position = Vector2d(7.0 + i, 685.0)
|
||||
item.spawn()
|
||||
item.movement.applyVelocity(Vector2d(rand.nextDouble() * 1000.0 - 500.0, rand.nextDouble() * 1000.0 - 500.0))
|
||||
//item.movement.applyVelocity(Vector2d(rand.nextDouble() * 1000.0 - 500.0, rand.nextDouble() * 1000.0 - 500.0))
|
||||
item.movement.applyVelocity(Vector2d(-1.0, 0.0))
|
||||
}
|
||||
|
||||
// println(Starbound.statusEffects["firecharge"])
|
||||
@ -144,13 +148,14 @@ fun main() {
|
||||
|
||||
//ent.position += Vector2d(y = 14.0, x = -10.0)
|
||||
ent.position = Vector2d(600.0 + 16.0, 721.0 + 48.0)
|
||||
client.camera.pos = Vector2f(578f, 695f)
|
||||
client.camera.pos = Vector2f(7f, 685f)
|
||||
|
||||
client.onDrawGUI {
|
||||
client.gl.font.render("${ent.position}", y = 100f, scale = 0.25f)
|
||||
client.gl.font.render("${ent.movement.velocity}", y = 120f, scale = 0.25f)
|
||||
client.gl.font.render("${client.camera.pos} ${client.settings.zoom}", y = 140f, scale = 0.25f)
|
||||
client.gl.font.render("${ChunkPos.fromTilePosition(client.camera.pos.toDoubleVector())}", y = 160f, scale = 0.25f)
|
||||
client.gl.font.render("Camera: ${ChunkPos.fromPosition(client.camera.pos.toDoubleVector())}", y = 160f, scale = 0.25f)
|
||||
client.gl.font.render("World: ${client.world!!.chunkMap.cellToGrid(client.camera.pos.toDoubleVector())}", y = 180f, scale = 0.25f)
|
||||
}
|
||||
|
||||
client.onPreDrawWorld {
|
||||
|
@ -18,6 +18,7 @@ import ru.dbotthepony.kstarbound.world.*
|
||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE
|
||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEd
|
||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEf
|
||||
import ru.dbotthepony.kstarbound.world.api.ITileAccess
|
||||
import ru.dbotthepony.kstarbound.world.api.ITileChunk
|
||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||
import ru.dbotthepony.kvector.arrays.Matrix4fStack
|
||||
@ -42,12 +43,13 @@ 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: IntSupplier, private val view: () -> ITileChunk, private val isBackground: Boolean) : AutoCloseable {
|
||||
private inner class TileLayerRenderer(private val view: () -> ITileChunk, private val isBackground: Boolean) : AutoCloseable {
|
||||
private val layers = TileLayerList()
|
||||
val bakedMeshes = LinkedList<Pair<ConfiguredStaticMesh, Int>>()
|
||||
private var changeset = -1
|
||||
var isDirty = true
|
||||
|
||||
fun bake() {
|
||||
isDirty = false
|
||||
val view = view()
|
||||
|
||||
if (state.isSameThread()) {
|
||||
@ -60,7 +62,7 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
||||
|
||||
layers.clear()
|
||||
|
||||
for ((pos, tile) in view.iterate()) {
|
||||
for ((pos, tile) in view.iterateTiles()) {
|
||||
val material = tile.material
|
||||
|
||||
if (material != null) {
|
||||
@ -78,7 +80,7 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
||||
fun loadRenderers() {
|
||||
val view = view()
|
||||
|
||||
for ((_, tile) in view.iterate()) {
|
||||
for ((_, tile) in view.iterateTiles()) {
|
||||
val material = tile.material
|
||||
val modifier = tile.modifier
|
||||
|
||||
@ -111,9 +113,8 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
||||
}
|
||||
|
||||
fun autoBake() {
|
||||
if (changeset != layerChangeset.asInt) {
|
||||
this.bake()
|
||||
changeset = layerChangeset.asInt
|
||||
if (isDirty) {
|
||||
bake()
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,8 +154,28 @@ 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(::foregroundChangeset, { world.getView(pos).foregroundView }, isBackground = false)
|
||||
private val backgroundRenderer = TileLayerRenderer(::backgroundChangeset, { world.getView(pos).backgroundView }, isBackground = true)
|
||||
private val foregroundRenderer = TileLayerRenderer({ world.getView(pos).foregroundView }, isBackground = false)
|
||||
private val backgroundRenderer = TileLayerRenderer({ world.getView(pos).backgroundView }, isBackground = true)
|
||||
|
||||
override fun foregroundChanges() {
|
||||
super.foregroundChanges()
|
||||
|
||||
foregroundRenderer.isDirty = true
|
||||
|
||||
forEachNeighbour {
|
||||
it.foregroundRenderer.isDirty = true
|
||||
}
|
||||
}
|
||||
|
||||
override fun backgroundChanges() {
|
||||
super.backgroundChanges()
|
||||
|
||||
backgroundRenderer.isDirty = true
|
||||
|
||||
forEachNeighbour {
|
||||
it.backgroundRenderer.isDirty = true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Принудительно подгружает в GLStateTracker все необходимые рендереры (ибо им нужны текстуры и прочее)
|
||||
|
@ -5,12 +5,15 @@ import ru.dbotthepony.kstarbound.math.encasingChunkPosAABB
|
||||
import ru.dbotthepony.kstarbound.world.*
|
||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||
import ru.dbotthepony.kvector.util2d.AABB
|
||||
import ru.dbotthepony.kvector.vector.Vector2i
|
||||
|
||||
class ClientWorld(
|
||||
val client: StarboundClient,
|
||||
seed: Long,
|
||||
widthInChunks: Int,
|
||||
) : World<ClientWorld, ClientChunk>(seed, widthInChunks) {
|
||||
size: Vector2i? = null,
|
||||
loopX: Boolean = false,
|
||||
loopY: Boolean = false
|
||||
) : World<ClientWorld, ClientChunk>(seed, size, loopX, loopY) {
|
||||
init {
|
||||
physics.debugDraw = client.gl.box2dRenderer
|
||||
}
|
||||
@ -195,8 +198,6 @@ class ClientWorld(
|
||||
//frame.close()
|
||||
//texture.close()
|
||||
|
||||
physics.debugDraw()
|
||||
|
||||
/*for (renderer in determineRenderers) {
|
||||
renderer.renderDebug()
|
||||
}*/
|
||||
|
@ -26,6 +26,7 @@ import ru.dbotthepony.kvector.util2d.AABB
|
||||
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||
import ru.dbotthepony.kvector.vector.Vector2d
|
||||
import ru.dbotthepony.kvector.vector.Vector2f
|
||||
import ru.dbotthepony.kvector.vector.Vector2i
|
||||
import ru.dbotthepony.kvector.vector.Vector3f
|
||||
import java.io.Closeable
|
||||
import java.nio.ByteBuffer
|
||||
@ -235,7 +236,7 @@ class StarboundClient(val starbound: Starbound) : Closeable {
|
||||
lightRenderer.resizeFramebuffer(viewportWidth, viewportHeight)
|
||||
}
|
||||
|
||||
var world: ClientWorld? = ClientWorld(this, 0L, 0)
|
||||
var world: ClientWorld? = ClientWorld(this, 0L, Vector2i(3000, 2000), true)
|
||||
|
||||
init {
|
||||
putDebugLog("Initialized OpenGL context")
|
||||
@ -345,6 +346,8 @@ class StarboundClient(val starbound: Starbound) : Closeable {
|
||||
|
||||
layers.render(gl.matrixStack)
|
||||
|
||||
world.physics.debugDraw()
|
||||
|
||||
for (lambda in onPostDrawWorld) {
|
||||
lambda.invoke()
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import ru.dbotthepony.kstarbound.client.gl.shader.GLTileProgram
|
||||
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.api.ITileAccess
|
||||
import ru.dbotthepony.kstarbound.world.api.ITileChunk
|
||||
import ru.dbotthepony.kstarbound.world.api.ITileState
|
||||
import ru.dbotthepony.kstarbound.world.api.TileColor
|
||||
|
@ -6,7 +6,7 @@ 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.api.ITileChunk
|
||||
import ru.dbotthepony.kstarbound.world.api.ITileAccess
|
||||
import ru.dbotthepony.kstarbound.world.api.ITileState
|
||||
import ru.dbotthepony.kvector.vector.Vector2i
|
||||
|
||||
@ -38,7 +38,7 @@ data class RenderRuleList(
|
||||
val matchHue: Boolean = false,
|
||||
val inverse: Boolean = false,
|
||||
) {
|
||||
private fun doTest(getter: ITileChunk, equalityTester: EqualityRuleTester, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
|
||||
private fun doTest(getter: ITileAccess, 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: ITileChunk, equalityTester: EqualityRuleTester, thisPos: Vector2i, offsetPos: Vector2i): Boolean {
|
||||
fun test(getter: ITileAccess, 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: ITileChunk, equalityTester: EqualityRuleTester, thisPos: Vector2i, offset: Vector2i): Boolean {
|
||||
fun test(getter: ITileAccess, 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: ITileChunk, equalityTester: EqualityRuleTester, thisPos: Vector2i): Boolean {
|
||||
fun test(getter: ITileAccess, equalityTester: EqualityRuleTester, thisPos: Vector2i): Boolean {
|
||||
return rule.test(getter, equalityTester, thisPos, offset)
|
||||
}
|
||||
|
||||
@ -156,7 +156,7 @@ data class RenderMatch(
|
||||
*
|
||||
* [equalityTester] требуется для проверки раенства между "этим" тайлом и другим
|
||||
*/
|
||||
fun test(tileAccess: ITileChunk, equalityTester: EqualityRuleTester, thisPos: Vector2i): Boolean {
|
||||
fun test(tileAccess: ITileAccess, equalityTester: EqualityRuleTester, thisPos: Vector2i): Boolean {
|
||||
for (matcher in matchAllPoints) {
|
||||
if (!matcher.test(tileAccess, equalityTester, thisPos)) {
|
||||
return false
|
||||
|
@ -14,8 +14,8 @@ fun AABB.encasingIntAABB(): AABBi {
|
||||
|
||||
fun AABB.encasingChunkPosAABB(): AABBi {
|
||||
return AABBi(
|
||||
Vector2i(ChunkPos.tileToChunkComponent(roundTowardsNegativeInfinity(mins.x)), ChunkPos.tileToChunkComponent(roundTowardsNegativeInfinity(mins.y))),
|
||||
Vector2i(ChunkPos.tileToChunkComponent(roundTowardsPositiveInfinity(maxs.x)), ChunkPos.tileToChunkComponent(roundTowardsPositiveInfinity(maxs.y))),
|
||||
Vector2i(ChunkPos.component(roundTowardsNegativeInfinity(mins.x)), ChunkPos.component(roundTowardsNegativeInfinity(mins.y))),
|
||||
Vector2i(ChunkPos.component(roundTowardsPositiveInfinity(maxs.x)), ChunkPos.component(roundTowardsPositiveInfinity(maxs.y))),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import ru.dbotthepony.kstarbound.world.api.BackgroundView
|
||||
import ru.dbotthepony.kstarbound.world.api.BackgroundChunkView
|
||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE
|
||||
import ru.dbotthepony.kstarbound.world.api.ForegroundView
|
||||
import ru.dbotthepony.kstarbound.world.api.ForegroundChunkView
|
||||
import ru.dbotthepony.kstarbound.world.api.IChunk
|
||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||
import ru.dbotthepony.kvector.arrays.Object2DArray
|
||||
@ -29,8 +29,8 @@ class CellView(
|
||||
val bottom: IChunk?,
|
||||
val bottomRight: IChunk?,
|
||||
) : IChunk {
|
||||
val backgroundView = BackgroundView(this)
|
||||
val foregroundView = ForegroundView(this)
|
||||
val backgroundView = BackgroundChunkView(this)
|
||||
val foregroundView = ForegroundChunkView(this)
|
||||
|
||||
override fun getCell(x: Int, y: Int): IChunkCell {
|
||||
val ix = x + CHUNK_SIZE
|
||||
|
@ -7,12 +7,12 @@ 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.world.api.BackgroundView
|
||||
import ru.dbotthepony.kstarbound.world.api.BackgroundChunkView
|
||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_BITS
|
||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE
|
||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_FF
|
||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEd
|
||||
import ru.dbotthepony.kstarbound.world.api.ForegroundView
|
||||
import ru.dbotthepony.kstarbound.world.api.ForegroundChunkView
|
||||
import ru.dbotthepony.kstarbound.world.api.IChunk
|
||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||
import ru.dbotthepony.kstarbound.world.api.ILiquidState
|
||||
@ -38,8 +38,7 @@ import kotlin.collections.HashSet
|
||||
*
|
||||
* Весь игровой мир будет измеряться в Starbound Unit'ах
|
||||
*/
|
||||
abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType, This>>(val world: WorldType, final override val pos: ChunkPos) :
|
||||
IChunk {
|
||||
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
|
||||
@ -56,14 +55,34 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
||||
var backgroundChangeset = 0
|
||||
private set
|
||||
|
||||
val left get() = pos.left
|
||||
val right get() = pos.right
|
||||
val top get() = pos.top
|
||||
val bottom get() = pos.bottom
|
||||
val topLeft get() = pos.topLeft
|
||||
val topRight get() = pos.topRight
|
||||
val bottomLeft get() = pos.bottomLeft
|
||||
val bottomRight get() = pos.bottomRight
|
||||
protected open fun foregroundChanges() {
|
||||
changeset++
|
||||
cellChangeset++
|
||||
tileChangeset++
|
||||
|
||||
collisionChangeset++
|
||||
foregroundChangeset++
|
||||
markPhysicsDirty()
|
||||
}
|
||||
|
||||
protected open fun backgroundChanges() {
|
||||
changeset++
|
||||
cellChangeset++
|
||||
tileChangeset++
|
||||
|
||||
backgroundChangeset++
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
val aabb = aabbBase + Vector2d(pos.x * CHUNK_SIZE.toDouble(), pos.y * CHUNK_SIZE.toDouble())
|
||||
|
||||
@ -79,8 +98,8 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
||||
|
||||
protected val cells = Object2DArray(CHUNK_SIZE, CHUNK_SIZE, ::Cell)
|
||||
|
||||
val backgroundView = BackgroundView(this)
|
||||
val foregroundView = ForegroundView(this)
|
||||
val backgroundView = BackgroundChunkView(this)
|
||||
val foregroundView = ForegroundChunkView(this)
|
||||
|
||||
override fun getCell(x: Int, y: Int): IChunkCell {
|
||||
return cells[x, y]
|
||||
@ -92,16 +111,10 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
||||
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()
|
||||
foregroundChanges()
|
||||
} else {
|
||||
backgroundChangeset++
|
||||
backgroundChanges()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,11 +2,9 @@ package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import ru.dbotthepony.kstarbound.math.roundByAbsoluteValue
|
||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_BITS
|
||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_FF
|
||||
import ru.dbotthepony.kvector.api.IStruct2d
|
||||
import ru.dbotthepony.kvector.api.IStruct2i
|
||||
import ru.dbotthepony.kvector.vector.Vector2i
|
||||
import kotlin.math.absoluteValue
|
||||
|
||||
private fun circulate(value: Int, bounds: Int): Int {
|
||||
require(bounds > 0) { "Bounds must be positive ($bounds given)" }
|
||||
@ -130,64 +128,48 @@ class ChunkPos(val x: Int, val y: Int) : Comparable<ChunkPos> {
|
||||
return x.toLong() or (y.toLong() shl 32)
|
||||
}
|
||||
|
||||
fun fromTilePosition(input: IStruct2i): ChunkPos {
|
||||
fun longFromPosition(x: Int, y: Int): Long {
|
||||
return toLong(component(x), component(y))
|
||||
}
|
||||
|
||||
fun fromPosition(input: IStruct2i): ChunkPos {
|
||||
val (x, y) = input
|
||||
return ChunkPos(tileToChunkComponent(x), tileToChunkComponent(y))
|
||||
return ChunkPos(component(x), component(y))
|
||||
}
|
||||
|
||||
fun fromTilePosition(input: IStruct2i, xWrap: Int): ChunkPos {
|
||||
fun fromPosition(input: IStruct2d): ChunkPos {
|
||||
val (x, y) = input
|
||||
return ChunkPos(circulate(tileToChunkComponent(x), xWrap), tileToChunkComponent(y))
|
||||
return fromPosition(x, y)
|
||||
}
|
||||
|
||||
fun fromTilePosition(input: IStruct2d): ChunkPos {
|
||||
val (x, y) = input
|
||||
return fromTilePosition(x, y)
|
||||
fun fromPosition(x: Int, y: Int): ChunkPos {
|
||||
return ChunkPos(component(x), component(y))
|
||||
}
|
||||
|
||||
fun fromTilePosition(input: IStruct2d, xWrap: Int): ChunkPos {
|
||||
val (x, y) = input
|
||||
return fromTilePosition(x, y, xWrap)
|
||||
fun fromPosition(x: Int, y: Int, xWrap: Int): ChunkPos {
|
||||
return ChunkPos(circulate(component(x), xWrap), component(y))
|
||||
}
|
||||
|
||||
fun fromTilePosition(x: Int, y: Int): ChunkPos {
|
||||
return ChunkPos(tileToChunkComponent(x), tileToChunkComponent(y))
|
||||
}
|
||||
|
||||
fun fromTilePosition(x: Int, y: Int, xWrap: Int): ChunkPos {
|
||||
return ChunkPos(circulate(tileToChunkComponent(x), xWrap), tileToChunkComponent(y))
|
||||
}
|
||||
|
||||
fun fromTilePosition(x: Double, y: Double): ChunkPos {
|
||||
fun fromPosition(x: Double, y: Double): ChunkPos {
|
||||
return ChunkPos(
|
||||
tileToChunkComponent(roundByAbsoluteValue(x)),
|
||||
tileToChunkComponent(roundByAbsoluteValue(y))
|
||||
component(roundByAbsoluteValue(x)),
|
||||
component(roundByAbsoluteValue(y))
|
||||
)
|
||||
}
|
||||
|
||||
fun fromTilePosition(x: Double, y: Double, xWrap: Int): ChunkPos {
|
||||
fun fromPosition(x: Double, y: Double, xWrap: Int): ChunkPos {
|
||||
return ChunkPos(
|
||||
circulate(tileToChunkComponent(roundByAbsoluteValue(x)), xWrap),
|
||||
tileToChunkComponent(roundByAbsoluteValue(y))
|
||||
circulate(component(roundByAbsoluteValue(x)), xWrap),
|
||||
component(roundByAbsoluteValue(y))
|
||||
)
|
||||
}
|
||||
|
||||
fun normalizeCoordinate(input: Int): Int {
|
||||
val band = input and CHUNK_SIZE_FF
|
||||
|
||||
if (band < 0) {
|
||||
return band + CHUNK_SIZE_FF
|
||||
fun component(value: Int): Int {
|
||||
if (value < 0) {
|
||||
return -((-value) shr CHUNK_SIZE_BITS) - 1
|
||||
}
|
||||
|
||||
return band
|
||||
}
|
||||
|
||||
fun tileToChunkComponent(comp: Int): Int {
|
||||
if (comp < 0) {
|
||||
return -(comp.absoluteValue shr CHUNK_SIZE_BITS) - 1
|
||||
}
|
||||
|
||||
return comp shr CHUNK_SIZE_BITS
|
||||
return value shr CHUNK_SIZE_BITS
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ package ru.dbotthepony.kstarbound.world
|
||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE
|
||||
import ru.dbotthepony.kstarbound.world.api.IChunk
|
||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||
import ru.dbotthepony.kstarbound.world.api.ITileChunk
|
||||
import ru.dbotthepony.kstarbound.world.api.ITileAccess
|
||||
import ru.dbotthepony.kstarbound.world.api.ITileState
|
||||
import ru.dbotthepony.kvector.vector.Vector2i
|
||||
|
||||
@ -35,7 +35,7 @@ fun IChunk.iterate(fromX: Int = 0, fromY: Int = 0, toX: Int = fromX + CHUNK_SIZE
|
||||
}
|
||||
}
|
||||
|
||||
fun ITileChunk.iterate(fromX: Int = 0, fromY: Int = 0, toX: Int = fromX + CHUNK_SIZE, toY: Int = fromY + CHUNK_SIZE): Iterator<Pair<Vector2i, ITileState>> {
|
||||
fun ITileAccess.iterateTiles(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
|
||||
|
229
src/main/kotlin/ru/dbotthepony/kstarbound/world/Raycasting.kt
Normal file
229
src/main/kotlin/ru/dbotthepony/kstarbound/world/Raycasting.kt
Normal file
@ -0,0 +1,229 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import ru.dbotthepony.kstarbound.METRES_IN_STARBOUND_UNIT
|
||||
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||
import ru.dbotthepony.kvector.arrays.Double2DArray
|
||||
import ru.dbotthepony.kvector.vector.Vector2d
|
||||
import ru.dbotthepony.kvector.vector.Vector2i
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.math.sin
|
||||
|
||||
|
||||
const val EARTH_FREEFALL_ACCELERATION = 9.8312 / METRES_IN_STARBOUND_UNIT
|
||||
|
||||
data class RayCastResult(
|
||||
val traversedTiles: List<Pair<Vector2i, IChunkCell>>,
|
||||
val hitTile: Pair<Vector2i, IChunkCell>?,
|
||||
val fraction: Double
|
||||
)
|
||||
|
||||
private fun makeDirFan(step: Double): List<Vector2d> {
|
||||
var i = 0.0
|
||||
val result = ImmutableList.builder<Vector2d>()
|
||||
|
||||
while (i < 360.0) {
|
||||
i += step
|
||||
result.add(Vector2d(cos(i / 180.0 * PI), sin(i / 180.0 * PI)))
|
||||
}
|
||||
|
||||
return result.build()
|
||||
}
|
||||
|
||||
private val potatoDirFan by lazy { makeDirFan(4.0) }
|
||||
private val veryRoughDirFan by lazy { makeDirFan(3.0) }
|
||||
private val roughDirFan by lazy { makeDirFan(2.0) }
|
||||
private val dirFan by lazy { makeDirFan(1.0) }
|
||||
private val preciseFan by lazy { makeDirFan(0.5) }
|
||||
private val veryPreciseFan by lazy { makeDirFan(0.25) }
|
||||
|
||||
private fun chooseLightRayFan(size: Double): List<Vector2d> {
|
||||
return when (size) {
|
||||
in 0.0 .. 8.0 -> potatoDirFan
|
||||
in 8.0 .. 16.0 -> veryRoughDirFan
|
||||
in 16.0 .. 24.0 -> roughDirFan
|
||||
in 24.0 .. 48.0 -> dirFan
|
||||
in 48.0 .. 96.0 -> preciseFan
|
||||
// in 32.0 .. 48.0 -> veryPreciseFan
|
||||
else -> veryPreciseFan
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [HIT] - луч попал по объекту и трассировка прекращается; объект записывается в коллекцию объектов, в которые попал луч.
|
||||
*
|
||||
* [HIT_SKIP] - луч попал по объекту и трассировка прекращается; объект не записывается в коллекцию объектов, в которые попал луч.
|
||||
*
|
||||
* [SKIP] - луч не попал по объекту, объект не записывается в коллекцию объектов, в которые попал луч.
|
||||
*
|
||||
* [CONTINUE] - луч не попал по объекту; объект записывается в коллекцию объектов, в которые попал луч.
|
||||
*/
|
||||
enum class RayFilterResult {
|
||||
HIT,
|
||||
HIT_SKIP,
|
||||
SKIP,
|
||||
CONTINUE;
|
||||
|
||||
companion object {
|
||||
fun of(boolean: Boolean): RayFilterResult {
|
||||
return if (boolean) HIT else CONTINUE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun interface TileRayFilter {
|
||||
fun test(state: IChunkCell, fraction: Double, position: Vector2i): RayFilterResult
|
||||
}
|
||||
|
||||
/**
|
||||
* Считает все тайлы неблокирующими
|
||||
*/
|
||||
val AnythingRayFilter = TileRayFilter { state, t, pos -> RayFilterResult.CONTINUE }
|
||||
|
||||
/**
|
||||
* Попадает по первому не-пустому тайлу
|
||||
*/
|
||||
val NonSolidRayFilter = TileRayFilter { state, t, pos -> RayFilterResult.of(state.foreground.material != null) }
|
||||
|
||||
/**
|
||||
* Попадает по первому пустому тайлу
|
||||
*/
|
||||
val SolidRayFilter = TileRayFilter { state, t, pos -> RayFilterResult.of(state.foreground.material == null) }
|
||||
|
||||
/**
|
||||
* Попадает по первому тайлу который блокирует проход света
|
||||
*/
|
||||
val LineOfSightRayFilter = TileRayFilter { state, t, pos -> RayFilterResult.of(state.foreground.material?.renderParameters?.lightTransparent == false) }
|
||||
|
||||
/**
|
||||
* Бросает луч напротив тайлов мира с заданными позициями и фильтром
|
||||
*/
|
||||
fun ICellAccess.castRayNaive(
|
||||
rayStart: Vector2d,
|
||||
rayEnd: Vector2d,
|
||||
filter: TileRayFilter = AnythingRayFilter
|
||||
): RayCastResult {
|
||||
if (rayStart == rayEnd) {
|
||||
return RayCastResult(listOf(), null, 1.0)
|
||||
}
|
||||
|
||||
var t = 0.0
|
||||
val dir = rayEnd - rayStart
|
||||
val inc = 0.5 / dir.length
|
||||
|
||||
val tiles = LinkedList<Pair<Vector2i, IChunkCell>>()
|
||||
var prev = Vector2i(Int.MIN_VALUE, Int.MAX_VALUE)
|
||||
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 = getCell(tilePos)
|
||||
|
||||
when (filter.test(tile, t, tilePos)) {
|
||||
RayFilterResult.HIT -> {
|
||||
hitTile = tilePos to tile
|
||||
tiles.add(hitTile)
|
||||
break
|
||||
}
|
||||
|
||||
RayFilterResult.HIT_SKIP -> {
|
||||
hitTile = tilePos to tile
|
||||
break
|
||||
}
|
||||
|
||||
RayFilterResult.SKIP -> {}
|
||||
RayFilterResult.CONTINUE -> tiles.add(tilePos to tile)
|
||||
}
|
||||
|
||||
prev = tilePos
|
||||
}
|
||||
|
||||
t += inc
|
||||
}
|
||||
|
||||
return RayCastResult(tiles, hitTile, t)
|
||||
}
|
||||
|
||||
/**
|
||||
* Бросает луч напротив тайлов мира с заданной позицией, направлением и фильтром
|
||||
*/
|
||||
fun ICellAccess.castRayNaive(
|
||||
rayPosition: Vector2d,
|
||||
direction: Vector2d,
|
||||
length: Double,
|
||||
filter: TileRayFilter = AnythingRayFilter
|
||||
): RayCastResult {
|
||||
return castRayNaive(rayPosition, rayPosition + direction.unitVector * length, filter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Выпускает луч света с заданной силой (определяет длину луча и способность проходить сквозь тайлы), позицией и направлением.
|
||||
*
|
||||
* Позволяет указать отдельно [falloffByTile] потерю силы света при прохождении через тайлы.
|
||||
*/
|
||||
fun ICellAccess.rayLightNaive(
|
||||
position: Vector2d,
|
||||
direction: Vector2d,
|
||||
intensity: Double,
|
||||
falloffByTile: Double = 2.0,
|
||||
falloffByTravel: Double = 1.0,
|
||||
): List<Pair<Double, Vector2i>> {
|
||||
val result = ArrayList<Pair<Double, Vector2i>>()
|
||||
|
||||
var currentIntensity = intensity
|
||||
|
||||
castRayNaive(position, direction, intensity) { state, t, pos ->
|
||||
if (state.foreground.material?.renderParameters?.lightTransparent == false) {
|
||||
currentIntensity -= falloffByTile
|
||||
} else {
|
||||
currentIntensity -= falloffByTravel
|
||||
}
|
||||
|
||||
//result.add(currentIntensity to pos)
|
||||
|
||||
if (currentIntensity <= 0.0) {
|
||||
return@castRayNaive RayFilterResult.HIT_SKIP
|
||||
} else {
|
||||
return@castRayNaive RayFilterResult.SKIP
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Трассирует лучи света вокруг себя с заданной позицией, интенсивностью,
|
||||
* падением интенсивности за проход сквозь тайл [falloffByTile] и
|
||||
* падением интенсивности за проход по пустому месту [falloffByTravel].
|
||||
*/
|
||||
fun ICellAccess.rayLightCircleNaive(
|
||||
position: Vector2d,
|
||||
intensity: Double,
|
||||
falloffByTile: Double = 2.0,
|
||||
falloffByTravel: Double = 1.0,
|
||||
): Double2DArray {
|
||||
val combinedResult = Double2DArray.allocate(intensity.roundToInt() * 2, intensity.roundToInt() * 2)
|
||||
val baselineX = position.x.roundToInt() - intensity.roundToInt()
|
||||
val baselineY = position.y.roundToInt() - intensity.roundToInt()
|
||||
|
||||
val dirs = chooseLightRayFan(intensity)
|
||||
val mul = 1.0 / dirs.size
|
||||
|
||||
for (dir in dirs) {
|
||||
val result2 = rayLightNaive(position, dir, intensity, falloffByTile, falloffByTravel)
|
||||
|
||||
for (pair in result2) {
|
||||
combinedResult[pair.second.y - baselineY, pair.second.x - baselineX] += pair.first * mul
|
||||
}
|
||||
}
|
||||
|
||||
return combinedResult
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
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) } }
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.world
|
||||
|
||||
import com.google.common.collect.ImmutableList
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||
import ru.dbotthepony.kbox2d.api.ContactImpulse
|
||||
import ru.dbotthepony.kbox2d.api.IContactFilter
|
||||
import ru.dbotthepony.kbox2d.api.IContactListener
|
||||
@ -10,128 +9,212 @@ import ru.dbotthepony.kbox2d.api.Manifold
|
||||
import ru.dbotthepony.kbox2d.dynamics.B2Fixture
|
||||
import ru.dbotthepony.kbox2d.dynamics.B2World
|
||||
import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
|
||||
import ru.dbotthepony.kstarbound.METRES_IN_STARBOUND_UNIT
|
||||
import ru.dbotthepony.kstarbound.math.*
|
||||
import ru.dbotthepony.kstarbound.util.Timer
|
||||
import ru.dbotthepony.kstarbound.world.api.BackgroundAccessView
|
||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE
|
||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_BITS
|
||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_MASK
|
||||
import ru.dbotthepony.kstarbound.world.api.ForegroundAccessView
|
||||
import ru.dbotthepony.kstarbound.world.api.ICellAccess
|
||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||
import ru.dbotthepony.kvector.arrays.Double2DArray
|
||||
import ru.dbotthepony.kvector.api.IStruct2d
|
||||
import ru.dbotthepony.kvector.api.IStruct2i
|
||||
import ru.dbotthepony.kvector.arrays.Int2DArray
|
||||
import ru.dbotthepony.kvector.arrays.Object2DArray
|
||||
import ru.dbotthepony.kvector.util2d.AABB
|
||||
import ru.dbotthepony.kvector.util2d.AABBi
|
||||
import ru.dbotthepony.kvector.vector.Vector2d
|
||||
import ru.dbotthepony.kvector.vector.Vector2i
|
||||
import java.util.LinkedList
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.math.sin
|
||||
|
||||
const val EARTH_FREEFALL_ACCELERATION = 9.8312 / METRES_IN_STARBOUND_UNIT
|
||||
|
||||
data class RayCastResult(
|
||||
val traversedTiles: List<Pair<Vector2i, IChunkCell>>,
|
||||
val hitTile: Pair<Vector2i, IChunkCell>?,
|
||||
val fraction: Double
|
||||
)
|
||||
|
||||
private fun makeDirFan(step: Double): List<Vector2d> {
|
||||
var i = 0.0
|
||||
val result = ImmutableList.builder<Vector2d>()
|
||||
|
||||
while (i < 360.0) {
|
||||
i += step
|
||||
result.add(Vector2d(cos(i / 180.0 * PI), sin(i / 180.0 * PI)))
|
||||
}
|
||||
|
||||
return result.build()
|
||||
}
|
||||
|
||||
private val potatoDirFan by lazy { makeDirFan(4.0) }
|
||||
private val veryRoughDirFan by lazy { makeDirFan(3.0) }
|
||||
private val roughDirFan by lazy { makeDirFan(2.0) }
|
||||
private val dirFan by lazy { makeDirFan(1.0) }
|
||||
private val preciseFan by lazy { makeDirFan(0.5) }
|
||||
private val veryPreciseFan by lazy { makeDirFan(0.25) }
|
||||
|
||||
private fun chooseLightRayFan(size: Double): List<Vector2d> {
|
||||
return when (size) {
|
||||
in 0.0 .. 8.0 -> potatoDirFan
|
||||
in 8.0 .. 16.0 -> veryRoughDirFan
|
||||
in 16.0 .. 24.0 -> roughDirFan
|
||||
in 24.0 .. 48.0 -> dirFan
|
||||
in 48.0 .. 96.0 -> preciseFan
|
||||
// in 32.0 .. 48.0 -> veryPreciseFan
|
||||
else -> veryPreciseFan
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* [HIT] - луч попал по объекту и трассировка прекращается; объект записывается в коллекцию объектов, в которые попал луч.
|
||||
*
|
||||
* [HIT_SKIP] - луч попал по объекту и трассировка прекращается; объект не записывается в коллекцию объектов, в которые попал луч.
|
||||
*
|
||||
* [SKIP] - луч не попал по объекту, объект не записывается в коллекцию объектов, в которые попал луч.
|
||||
*
|
||||
* [CONTINUE] - луч не попал по объекту; объект записывается в коллекцию объектов, в которые попал луч.
|
||||
*/
|
||||
enum class RayFilterResult {
|
||||
HIT,
|
||||
HIT_SKIP,
|
||||
SKIP,
|
||||
CONTINUE;
|
||||
|
||||
companion object {
|
||||
fun of(boolean: Boolean): RayFilterResult {
|
||||
return if (boolean) HIT else CONTINUE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun interface TileRayFilter {
|
||||
fun test(state: IChunkCell, fraction: Double, position: Vector2i): RayFilterResult
|
||||
}
|
||||
|
||||
/**
|
||||
* Считает все тайлы неблокирующими
|
||||
*/
|
||||
val AnythingRayFilter = TileRayFilter { state, t, pos -> RayFilterResult.CONTINUE }
|
||||
|
||||
/**
|
||||
* Попадает по первому не-пустому тайлу
|
||||
*/
|
||||
val NonSolidRayFilter = TileRayFilter { state, t, pos -> RayFilterResult.of(state.foreground.material != null) }
|
||||
|
||||
/**
|
||||
* Попадает по первому пустому тайлу
|
||||
*/
|
||||
val SolidRayFilter = TileRayFilter { state, t, pos -> RayFilterResult.of(state.foreground.material == null) }
|
||||
|
||||
/**
|
||||
* Попадает по первому тайлу который блокирует проход света
|
||||
*/
|
||||
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
|
||||
val size: Vector2i?,
|
||||
val loopX: Boolean,
|
||||
val loopY: Boolean
|
||||
) {
|
||||
protected val chunkMap = Long2ObjectOpenHashMap<ChunkType>()
|
||||
abstract class AbstractCoordinatesWrapper {
|
||||
abstract fun cell(value: Int): Int
|
||||
abstract fun cell(value: Double): Double
|
||||
abstract fun cell(value: Float): Float
|
||||
abstract fun chunk(value: Int): Int
|
||||
abstract fun chunk(value: Double): Double
|
||||
|
||||
/**
|
||||
* Является ли мир "сферическим"
|
||||
*
|
||||
* Данный флаг говорит о том, что [widthInChunks] имеет осмысленное значение,
|
||||
* попытка получить чанк с X координатой меньше нуля или больше [widthInChunks]
|
||||
* приведёт к замыканию на конец/начало мира соответственно
|
||||
*/
|
||||
val isCircular = widthInChunks > 0
|
||||
protected fun positiveModulo(a: Int, b: Int): Int {
|
||||
val result = a % b
|
||||
return if (result < 0) result + b else result
|
||||
}
|
||||
|
||||
protected fun positiveModulo(a: Double, b: Int): Double {
|
||||
val result = a % b
|
||||
return if (result < 0.0) result + b else result
|
||||
}
|
||||
|
||||
protected fun positiveModulo(a: Float, b: Int): Float {
|
||||
val result = a % b
|
||||
return if (result < 0f) result + b else result
|
||||
}
|
||||
}
|
||||
|
||||
object PassthroughWrapper : AbstractCoordinatesWrapper() {
|
||||
override fun cell(value: Int): Int = value
|
||||
override fun cell(value: Double): Double = value
|
||||
override fun cell(value: Float): Float = value
|
||||
override fun chunk(value: Int): Int = value
|
||||
override fun chunk(value: Double): Double = value
|
||||
}
|
||||
|
||||
class CoordinatesWrapper(private val cell: Int, private val chunk: Int) : AbstractCoordinatesWrapper() {
|
||||
override fun cell(value: Int): Int {
|
||||
return positiveModulo(value, cell)
|
||||
}
|
||||
|
||||
override fun cell(value: Double): Double {
|
||||
return positiveModulo(value, cell)
|
||||
}
|
||||
|
||||
override fun cell(value: Float): Float {
|
||||
return positiveModulo(value, cell)
|
||||
}
|
||||
|
||||
override fun chunk(value: Int): Int {
|
||||
return positiveModulo(value, chunk)
|
||||
}
|
||||
|
||||
override fun chunk(value: Double): Double {
|
||||
return positiveModulo(value, chunk)
|
||||
}
|
||||
}
|
||||
|
||||
class CoordinatesClamper(private val cell: Int, private val chunk: Int) : AbstractCoordinatesWrapper() {
|
||||
override fun cell(value: Int): Int {
|
||||
return value.coerceIn(0, cell - 1)
|
||||
}
|
||||
|
||||
override fun cell(value: Double): Double {
|
||||
return value.coerceIn(0.0, cell - 1.0)
|
||||
}
|
||||
|
||||
override fun cell(value: Float): Float {
|
||||
return value.coerceIn(0f, cell - 1f)
|
||||
}
|
||||
|
||||
override fun chunk(value: Int): Int {
|
||||
return value.coerceIn(0, chunk - 1)
|
||||
}
|
||||
|
||||
override fun chunk(value: Double): Double {
|
||||
return value.coerceIn(0.0, chunk - 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
abstract inner class ChunkMap : ICellAccess {
|
||||
abstract val x: AbstractCoordinatesWrapper
|
||||
abstract val y: AbstractCoordinatesWrapper
|
||||
|
||||
val backgroundView = BackgroundAccessView(this)
|
||||
val foregroundView = ForegroundAccessView(this)
|
||||
|
||||
abstract operator fun get(x: Int, y: Int): ChunkType?
|
||||
operator fun get(pos: ChunkPos) = get(pos.x, pos.y)
|
||||
|
||||
override fun getCell(x: Int, y: Int): IChunkCell {
|
||||
return get(ChunkPos.component(x), ChunkPos.component(y))?.getCell(x and CHUNK_SIZE_MASK, y and CHUNK_SIZE_MASK) ?: IChunkCell.Companion
|
||||
}
|
||||
|
||||
abstract operator fun set(x: Int, y: Int, chunk: ChunkType)
|
||||
abstract fun remove(x: Int, y: Int)
|
||||
|
||||
fun cellToGrid(x: Int, y: Int) = ChunkPos.fromPosition(this.x.cell(x), this.y.cell(y))
|
||||
fun cellToGrid(x: Double, y: Double) = ChunkPos.fromPosition(this.x.cell(x), this.y.cell(y))
|
||||
fun cellToGrid(position: IStruct2i) = cellToGrid(position.component1(), position.component2())
|
||||
fun cellToGrid(position: IStruct2d) = cellToGrid(position.component1(), position.component2())
|
||||
|
||||
fun computeIfAbsent(pos: ChunkPos) = computeIfAbsent(pos.x, pos.y)
|
||||
|
||||
fun computeIfAbsent(x: Int, y: Int): ChunkType {
|
||||
val existing = get(x, y)
|
||||
|
||||
if (existing != null)
|
||||
return existing
|
||||
|
||||
val pos = ChunkPos(this.x.chunk(x), this.y.chunk(y))
|
||||
val chunk = chunkFactory(pos)
|
||||
val orphanedInThisChunk = ArrayList<Entity>()
|
||||
|
||||
for (ent in orphanedEntities) {
|
||||
val (ex, ey) = ent.position
|
||||
|
||||
if (ChunkPos.fromPosition(this.x.cell(ex), this.y.cell(ey)) == pos) {
|
||||
orphanedInThisChunk.add(ent)
|
||||
}
|
||||
}
|
||||
|
||||
for (ent in orphanedInThisChunk) {
|
||||
ent.chunk = chunk
|
||||
}
|
||||
|
||||
set(x, y, chunk)
|
||||
return chunk
|
||||
}
|
||||
}
|
||||
|
||||
inner class InfiniteChunkMap : ChunkMap() {
|
||||
private val map = Long2ObjectOpenHashMap<ChunkType>()
|
||||
|
||||
override fun get(x: Int, y: Int): ChunkType? {
|
||||
return map[ChunkPos.toLong(x, y)]
|
||||
}
|
||||
|
||||
override fun set(x: Int, y: Int, chunk: ChunkType) {
|
||||
map[ChunkPos.toLong(x, y)] = chunk
|
||||
}
|
||||
|
||||
override fun remove(x: Int, y: Int) {
|
||||
map.remove(ChunkPos.toLong(x, y))
|
||||
}
|
||||
|
||||
override val x = PassthroughWrapper
|
||||
override val y = PassthroughWrapper
|
||||
}
|
||||
|
||||
inner class RectChunkMap : ChunkMap() {
|
||||
val width = size!!.x
|
||||
val height = size!!.y
|
||||
|
||||
val widthInChunks = if (width and CHUNK_SIZE_MASK == 0) width / CHUNK_SIZE else width / CHUNK_SIZE + 1
|
||||
val heightInChunks = if (height and CHUNK_SIZE_MASK == 0) height / CHUNK_SIZE else height / CHUNK_SIZE + 1
|
||||
|
||||
override val x: AbstractCoordinatesWrapper = if (loopX) CoordinatesWrapper(width, widthInChunks) else CoordinatesClamper(width, widthInChunks)
|
||||
override val y: AbstractCoordinatesWrapper = if (loopY) CoordinatesWrapper(height, heightInChunks) else CoordinatesClamper(height, heightInChunks)
|
||||
|
||||
private val map = Object2DArray.nulls<ChunkType>(widthInChunks, heightInChunks)
|
||||
|
||||
override fun get(x: Int, y: Int): ChunkType? {
|
||||
return map[this.x.chunk(x), this.y.chunk(y)]
|
||||
}
|
||||
|
||||
override fun getCell(x: Int, y: Int): IChunkCell {
|
||||
if (x < 0 || y < 0) return IChunkCell.Companion
|
||||
return get(x ushr CHUNK_SIZE_BITS, y ushr CHUNK_SIZE_BITS)?.getCell(x and CHUNK_SIZE_MASK, y and CHUNK_SIZE_MASK) ?: IChunkCell.Companion
|
||||
}
|
||||
|
||||
override fun set(x: Int, y: Int, chunk: ChunkType) {
|
||||
map[this.x.chunk(x), this.y.chunk(y)] = chunk
|
||||
}
|
||||
|
||||
override fun remove(x: Int, y: Int) {
|
||||
map[this.x.chunk(x), this.y.chunk(y)] = null
|
||||
}
|
||||
}
|
||||
|
||||
val chunkMap: ChunkMap = if (size != null) RectChunkMap() else InfiniteChunkMap()
|
||||
|
||||
/**
|
||||
* Chunks, which have their collision mesh changed
|
||||
*/
|
||||
val dirtyPhysicsChunks = HashSet<ChunkType>()
|
||||
val dirtyPhysicsChunks = ObjectOpenHashSet<ChunkType>()
|
||||
|
||||
val physics = B2World(Vector2d(0.0, -EARTH_FREEFALL_ACCELERATION))
|
||||
|
||||
@ -293,270 +376,20 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
*/
|
||||
var gravity by physics::gravity
|
||||
|
||||
protected abstract fun chunkFactory(
|
||||
pos: ChunkPos,
|
||||
): ChunkType
|
||||
|
||||
/**
|
||||
* Возвращает чанк на указанной позиции если он существует
|
||||
*/
|
||||
open operator fun get(pos: ChunkPos): ChunkType? {
|
||||
if (!isCircular) {
|
||||
return chunkMap[pos.toLong()]
|
||||
}
|
||||
|
||||
@Suppress("Name_Shadowing")
|
||||
val pos = pos.circular(widthInChunks)
|
||||
return chunkMap[pos.toLong()]
|
||||
}
|
||||
|
||||
open fun getInstantTuple(pos: ChunkPos): IWorldChunkTuple<This, ChunkType>? {
|
||||
return this[pos]?.let { InstantWorldChunkTuple(this as This, it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает чанк на заданной позиции, создаёт его если он не существует
|
||||
*/
|
||||
open fun computeIfAbsent(pos: ChunkPos): ChunkType {
|
||||
@Suppress("Name_Shadowing")
|
||||
val pos = if (isCircular) pos.circular(widthInChunks) else pos
|
||||
|
||||
return chunkMap.computeIfAbsent(pos.toLong(), Long2ObjectFunction {
|
||||
val chunk = chunkFactory(pos)
|
||||
|
||||
val orphanedInThisChunk = ArrayList<Entity>()
|
||||
|
||||
for (ent in orphanedEntities) {
|
||||
val cPos = if (isCircular) ChunkPos.fromTilePosition(ent.position, widthInChunks) else ChunkPos.fromTilePosition(ent.position)
|
||||
|
||||
if (cPos == pos) {
|
||||
orphanedInThisChunk.add(ent)
|
||||
}
|
||||
}
|
||||
|
||||
for (ent in orphanedInThisChunk) {
|
||||
ent.chunk = chunk
|
||||
}
|
||||
|
||||
return@Long2ObjectFunction chunk
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Позволяет получать чанки/тайлы с минимальным кешем. Если один чанк считывается очень большое число раз,
|
||||
* то использование этого класса сильно ускорит работу.
|
||||
*
|
||||
* Так же реализует raycasting методы.
|
||||
*/
|
||||
inner class CachedGetter {
|
||||
private var lastChunk: ChunkType? = null
|
||||
private var lastPos: ChunkPos? = null
|
||||
|
||||
operator fun get(pos: ChunkPos): ChunkType? {
|
||||
if (lastPos == pos) {
|
||||
return lastChunk
|
||||
}
|
||||
|
||||
lastChunk = this@World[pos]
|
||||
lastPos = pos
|
||||
return lastChunk
|
||||
}
|
||||
|
||||
fun getCell(pos: Vector2i): IChunkCell? {
|
||||
return get(ChunkPos.fromTilePosition(pos))?.getCell(ChunkPos.normalizeCoordinate(pos.x), ChunkPos.normalizeCoordinate(pos.y))
|
||||
}
|
||||
|
||||
/**
|
||||
* Бросает луч напротив тайлов мира с заданными позициями и фильтром
|
||||
*/
|
||||
fun castRayNaive(
|
||||
rayStart: Vector2d,
|
||||
rayEnd: Vector2d,
|
||||
filter: TileRayFilter = AnythingRayFilter
|
||||
): RayCastResult {
|
||||
if (rayStart == rayEnd) {
|
||||
return RayCastResult(listOf(), null, 1.0)
|
||||
}
|
||||
|
||||
var t = 0.0
|
||||
val dir = rayEnd - rayStart
|
||||
val inc = 0.5 / dir.length
|
||||
|
||||
val tiles = LinkedList<Pair<Vector2i, IChunkCell>>()
|
||||
var prev = Vector2i(Int.MIN_VALUE, Int.MAX_VALUE)
|
||||
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 = getCell(tilePos) ?: IChunkCell.Companion
|
||||
|
||||
when (filter.test(tile, t, tilePos)) {
|
||||
RayFilterResult.HIT -> {
|
||||
hitTile = tilePos to tile
|
||||
tiles.add(hitTile)
|
||||
break
|
||||
}
|
||||
|
||||
RayFilterResult.HIT_SKIP -> {
|
||||
hitTile = tilePos to tile
|
||||
break
|
||||
}
|
||||
|
||||
RayFilterResult.SKIP -> {}
|
||||
RayFilterResult.CONTINUE -> tiles.add(tilePos to tile)
|
||||
}
|
||||
|
||||
prev = tilePos
|
||||
}
|
||||
|
||||
t += inc
|
||||
}
|
||||
|
||||
return RayCastResult(tiles, hitTile, t)
|
||||
}
|
||||
|
||||
/**
|
||||
* Бросает луч напротив тайлов мира с заданной позицией, направлением и фильтром
|
||||
*/
|
||||
fun castRayNaive(
|
||||
rayPosition: Vector2d,
|
||||
direction: Vector2d,
|
||||
length: Double,
|
||||
filter: TileRayFilter = AnythingRayFilter
|
||||
): RayCastResult {
|
||||
return castRayNaive(rayPosition, rayPosition + direction.unitVector * length, filter)
|
||||
}
|
||||
|
||||
/**
|
||||
* Выпускает луч света с заданной силой (определяет длину луча и способность проходить сквозь тайлы), позицией и направлением.
|
||||
*
|
||||
* Позволяет указать отдельно [falloffByTile] потерю силы света при прохождении через тайлы.
|
||||
*/
|
||||
fun rayLightNaive(
|
||||
position: Vector2d,
|
||||
direction: Vector2d,
|
||||
intensity: Double,
|
||||
falloffByTile: Double = 2.0,
|
||||
falloffByTravel: Double = 1.0,
|
||||
): List<Pair<Double, Vector2i>> {
|
||||
val result = ArrayList<Pair<Double, Vector2i>>()
|
||||
|
||||
var currentIntensity = intensity
|
||||
|
||||
castRayNaive(position, direction, intensity) { state, t, pos ->
|
||||
if (state.foreground.material?.renderParameters?.lightTransparent == false) {
|
||||
currentIntensity -= falloffByTile
|
||||
} else {
|
||||
currentIntensity -= falloffByTravel
|
||||
}
|
||||
|
||||
//result.add(currentIntensity to pos)
|
||||
|
||||
if (currentIntensity <= 0.0) {
|
||||
return@castRayNaive RayFilterResult.HIT_SKIP
|
||||
} else {
|
||||
return@castRayNaive RayFilterResult.SKIP
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Трассирует лучи света вокруг себя с заданной позицией, интенсивностью,
|
||||
* падением интенсивности за проход сквозь тайл [falloffByTile] и
|
||||
* падением интенсивности за проход по пустому месту [falloffByTravel].
|
||||
*/
|
||||
fun rayLightCircleNaive(
|
||||
position: Vector2d,
|
||||
intensity: Double,
|
||||
falloffByTile: Double = 2.0,
|
||||
falloffByTravel: Double = 1.0,
|
||||
): Double2DArray {
|
||||
val combinedResult = Double2DArray.allocate(intensity.roundToInt() * 2, intensity.roundToInt() * 2)
|
||||
val baselineX = position.x.roundToInt() - intensity.roundToInt()
|
||||
val baselineY = position.y.roundToInt() - intensity.roundToInt()
|
||||
|
||||
val dirs = chooseLightRayFan(intensity)
|
||||
val mul = 1.0 / dirs.size
|
||||
|
||||
for (dir in dirs) {
|
||||
val result2 = rayLightNaive(position, dir, intensity, falloffByTile, falloffByTravel)
|
||||
|
||||
for (pair in result2) {
|
||||
combinedResult[pair.second.y - baselineY, pair.second.x - baselineX] += pair.first * mul
|
||||
}
|
||||
}
|
||||
|
||||
return combinedResult
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see CachedGetter.castRayNaive
|
||||
*/
|
||||
fun castRayNaive(
|
||||
rayStart: Vector2d,
|
||||
rayEnd: Vector2d,
|
||||
filter: TileRayFilter = AnythingRayFilter
|
||||
): RayCastResult {
|
||||
return CachedGetter().castRayNaive(rayStart, rayEnd, filter)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see CachedGetter.castRayNaive
|
||||
*/
|
||||
fun castRayNaive(
|
||||
rayPosition: Vector2d,
|
||||
direction: Vector2d,
|
||||
length: Double,
|
||||
filter: TileRayFilter = AnythingRayFilter
|
||||
): RayCastResult {
|
||||
return CachedGetter().castRayNaive(rayPosition, direction, length, filter)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see CachedGetter.rayLightNaive
|
||||
*/
|
||||
fun rayLightNaive(
|
||||
position: Vector2d,
|
||||
direction: Vector2d,
|
||||
intensity: Double,
|
||||
falloffByTile: Double = 2.0,
|
||||
falloffByTravel: Double = 1.0,
|
||||
): List<Pair<Double, Vector2i>> {
|
||||
return CachedGetter().rayLightNaive(position, direction, intensity, falloffByTile, falloffByTravel)
|
||||
}
|
||||
|
||||
/**
|
||||
* @see CachedGetter.rayLightCircleNaive
|
||||
*/
|
||||
fun rayLightCircleNaive(
|
||||
position: Vector2d,
|
||||
intensity: Double,
|
||||
falloffByTile: Double = 2.0,
|
||||
falloffByTravel: Double = 2.0,
|
||||
): Double2DArray {
|
||||
return CachedGetter().rayLightCircleNaive(position, intensity, falloffByTile, falloffByTravel)
|
||||
}
|
||||
protected abstract fun chunkFactory(pos: ChunkPos): ChunkType
|
||||
|
||||
fun getView(pos: ChunkPos): CellView {
|
||||
val tuple = get(pos)?.let { InstantWorldChunkTuple(this as This, it) }
|
||||
|
||||
return CellView(
|
||||
pos = pos,
|
||||
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,
|
||||
center = chunkMap[pos],
|
||||
left = chunkMap[pos.left],
|
||||
top = chunkMap[pos.top],
|
||||
topLeft = chunkMap[pos.topLeft],
|
||||
topRight = chunkMap[pos.topRight],
|
||||
right = chunkMap[pos.right],
|
||||
bottom = chunkMap[pos.bottom],
|
||||
bottomLeft = chunkMap[pos.bottomLeft],
|
||||
bottomRight = chunkMap[pos.bottomRight],
|
||||
)
|
||||
}
|
||||
|
||||
@ -567,9 +400,9 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
val output = ArrayList<ChunkType>()
|
||||
|
||||
for (pos in boundingBox.chunkPositions) {
|
||||
val chunk = get(pos)
|
||||
val chunk = chunkMap[pos]
|
||||
|
||||
if (chunk != null) {
|
||||
if (chunk != null && chunk !in output) {
|
||||
output.add(chunk)
|
||||
}
|
||||
}
|
||||
@ -584,9 +417,9 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
val output = ArrayList<Pair<ChunkPos, ChunkType>>()
|
||||
|
||||
for (pos in boundingBox.chunkPositions) {
|
||||
val chunk = get(pos)
|
||||
val chunk = chunkMap[pos]
|
||||
|
||||
if (chunk != null) {
|
||||
if (chunk != null && !output.any { it.second === chunk }) {
|
||||
output.add(pos to chunk)
|
||||
}
|
||||
}
|
||||
@ -715,7 +548,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
|
||||
val lightmap = Int2DArray.allocate(lightIntensity * 2 + 1, lightIntensity * 2 + 1)
|
||||
|
||||
val view = getView(ChunkPos.fromTilePosition(lightPosition))
|
||||
val view = getView(ChunkPos.fromPosition(lightPosition))
|
||||
|
||||
floodLightInto(
|
||||
lightmap,
|
||||
|
@ -1,6 +1,7 @@
|
||||
package ru.dbotthepony.kstarbound.world.api
|
||||
|
||||
const val CHUNK_SIZE_BITS = 5
|
||||
const val CHUNK_SIZE_MASK = 1 or 2 or 4 or 8 or 16
|
||||
const val CHUNK_SIZE = 1 shl CHUNK_SIZE_BITS // 32
|
||||
const val CHUNK_SIZE_FF = CHUNK_SIZE - 1
|
||||
const val CHUNK_SIZEf = CHUNK_SIZE.toFloat()
|
||||
|
@ -0,0 +1,9 @@
|
||||
package ru.dbotthepony.kstarbound.world.api
|
||||
|
||||
import ru.dbotthepony.kvector.api.IStruct2i
|
||||
|
||||
interface ICellAccess {
|
||||
// relative
|
||||
fun getCell(x: Int, y: Int): IChunkCell
|
||||
fun getCell(pos: IStruct2i) = getCell(pos.component1(), pos.component2())
|
||||
}
|
@ -4,13 +4,9 @@ import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||
import ru.dbotthepony.kvector.api.IStruct2i
|
||||
import ru.dbotthepony.kvector.vector.Vector2i
|
||||
|
||||
interface IChunk {
|
||||
interface IChunk : ICellAccess {
|
||||
val pos: ChunkPos
|
||||
|
||||
// relative
|
||||
fun getCell(x: Int, y: Int): IChunkCell
|
||||
fun getCell(pos: IStruct2i) = getCell(pos.component1(), pos.component2())
|
||||
|
||||
/**
|
||||
* Возвращает псевдослучайное Long для заданной позиции
|
||||
*
|
||||
@ -47,4 +43,4 @@ interface IChunk {
|
||||
* Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции
|
||||
*/
|
||||
fun randomDoubleFor(pos: Vector2i) = randomDoubleFor(pos.x, pos.y)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
package ru.dbotthepony.kstarbound.world.api
|
||||
|
||||
import ru.dbotthepony.kvector.api.IStruct2i
|
||||
|
||||
// for getting tiles directly, avoiding manual layer specification
|
||||
interface ITileAccess : ICellAccess {
|
||||
// relative
|
||||
fun getTile(x: Int, y: Int): ITileState
|
||||
fun getTile(pos: IStruct2i) = getTile(pos.component1(), pos.component2())
|
||||
}
|
||||
|
||||
interface ITileChunk : IChunk, ITileAccess
|
||||
|
||||
class ForegroundChunkView(private val parent: IChunk) : ITileChunk, IChunk by parent {
|
||||
override fun getTile(x: Int, y: Int): ITileState {
|
||||
return parent.getCell(x, y).foreground
|
||||
}
|
||||
}
|
||||
|
||||
class BackgroundChunkView(private val parent: IChunk) : ITileChunk, IChunk by parent {
|
||||
override fun getTile(x: Int, y: Int): ITileState {
|
||||
return parent.getCell(x, y).background
|
||||
}
|
||||
}
|
||||
|
||||
class ForegroundAccessView(private val parent: ICellAccess) : ITileAccess, ICellAccess by parent {
|
||||
override fun getTile(x: Int, y: Int): ITileState {
|
||||
return parent.getCell(x, y).foreground
|
||||
}
|
||||
}
|
||||
|
||||
class BackgroundAccessView(private val parent: ICellAccess) : ITileAccess, ICellAccess by parent {
|
||||
override fun getTile(x: Int, y: Int): ITileState {
|
||||
return parent.getCell(x, y).background
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
package ru.dbotthepony.kstarbound.world.api
|
||||
|
||||
import ru.dbotthepony.kvector.api.IStruct2i
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ package ru.dbotthepony.kstarbound.world.entities
|
||||
|
||||
import ru.dbotthepony.kstarbound.defs.DamageType
|
||||
import ru.dbotthepony.kstarbound.world.Chunk
|
||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
||||
import ru.dbotthepony.kstarbound.world.World
|
||||
import ru.dbotthepony.kvector.vector.Vector2d
|
||||
|
||||
@ -95,7 +94,7 @@ abstract class Entity(override val world: World<*, *>) : IEntity {
|
||||
return
|
||||
}
|
||||
|
||||
val chunkPos = if (world.isCircular) ChunkPos.fromTilePosition(position, world.widthInChunks) else ChunkPos.fromTilePosition(position)
|
||||
val chunkPos = world.chunkMap.cellToGrid(position)
|
||||
|
||||
if (value != null && chunkPos != value.pos) {
|
||||
throw IllegalStateException("Set proper position before setting chunk this Entity belongs to (expected chunk $chunkPos, got chunk ${value.pos})")
|
||||
@ -121,16 +120,16 @@ abstract class Entity(override val world: World<*, *>) : IEntity {
|
||||
return
|
||||
|
||||
val old = field
|
||||
field = value
|
||||
field = Vector2d(world.chunkMap.x.cell(value.x), world.chunkMap.x.cell(value.y))
|
||||
|
||||
movement.notifyPositionChanged()
|
||||
|
||||
if (isSpawned && !isRemoved) {
|
||||
val oldChunkPos = ChunkPos.fromTilePosition(old)
|
||||
val newChunkPos = ChunkPos.fromTilePosition(value)
|
||||
val oldChunkPos = world.chunkMap.cellToGrid(old)
|
||||
val newChunkPos = world.chunkMap.cellToGrid(field)
|
||||
|
||||
if (oldChunkPos != newChunkPos) {
|
||||
chunk = world[newChunkPos]
|
||||
chunk = world.chunkMap[newChunkPos]
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -155,7 +154,7 @@ abstract class Entity(override val world: World<*, *>) : IEntity {
|
||||
|
||||
isSpawned = true
|
||||
world.entities.add(this)
|
||||
chunk = world[ChunkPos.fromTilePosition(position)]
|
||||
chunk = world.chunkMap[world.chunkMap.cellToGrid(position)]
|
||||
|
||||
if (chunk == null) {
|
||||
world.orphanedEntities.add(this)
|
||||
|
@ -51,21 +51,21 @@ object MathTests {
|
||||
@Test
|
||||
@DisplayName("ChunkPos class tests")
|
||||
fun chunkPosTests() {
|
||||
check(ChunkPos.fromTilePosition(0, 0) == ChunkPos(0, 0))
|
||||
check(ChunkPos.fromTilePosition(1, 0) == ChunkPos(0, 0))
|
||||
check(ChunkPos.fromTilePosition(0, 1) == ChunkPos(0, 0))
|
||||
check(ChunkPos.fromTilePosition(1, 1) == ChunkPos(0, 0))
|
||||
check(ChunkPos.fromTilePosition(-1, 1) == ChunkPos(-1, 0))
|
||||
check(ChunkPos.fromTilePosition(-1, -1) == ChunkPos(-1, -1))
|
||||
check(ChunkPos.fromPosition(0, 0) == ChunkPos(0, 0))
|
||||
check(ChunkPos.fromPosition(1, 0) == ChunkPos(0, 0))
|
||||
check(ChunkPos.fromPosition(0, 1) == ChunkPos(0, 0))
|
||||
check(ChunkPos.fromPosition(1, 1) == ChunkPos(0, 0))
|
||||
check(ChunkPos.fromPosition(-1, 1) == ChunkPos(-1, 0))
|
||||
check(ChunkPos.fromPosition(-1, -1) == ChunkPos(-1, -1))
|
||||
|
||||
check(ChunkPos.fromTilePosition(CHUNK_SIZE_FF, 0) == ChunkPos(0, 0))
|
||||
check(ChunkPos.fromTilePosition(CHUNK_SIZE, 0) == ChunkPos(1, 0))
|
||||
check(ChunkPos.fromTilePosition(0, CHUNK_SIZE_FF) == ChunkPos(0, 0))
|
||||
check(ChunkPos.fromTilePosition(0, CHUNK_SIZE) == ChunkPos(0, 1))
|
||||
check(ChunkPos.fromPosition(CHUNK_SIZE_FF, 0) == ChunkPos(0, 0))
|
||||
check(ChunkPos.fromPosition(CHUNK_SIZE, 0) == ChunkPos(1, 0))
|
||||
check(ChunkPos.fromPosition(0, CHUNK_SIZE_FF) == ChunkPos(0, 0))
|
||||
check(ChunkPos.fromPosition(0, CHUNK_SIZE) == ChunkPos(0, 1))
|
||||
|
||||
check(ChunkPos.fromTilePosition(-CHUNK_SIZE_FF, 0) == ChunkPos(-1, 0)) { ChunkPos.fromTilePosition(-CHUNK_SIZE_FF, 0).toString() }
|
||||
check(ChunkPos.fromTilePosition(-CHUNK_SIZE, 0) == ChunkPos(-2, 0)) { ChunkPos.fromTilePosition(-CHUNK_SIZE, 0) }
|
||||
check(ChunkPos.fromTilePosition(0, -CHUNK_SIZE_FF) == ChunkPos(0, -1)) { ChunkPos.fromTilePosition(0, -CHUNK_SIZE_FF) }
|
||||
check(ChunkPos.fromTilePosition(0, -CHUNK_SIZE) == ChunkPos(0, -2)) { ChunkPos.fromTilePosition(0, -CHUNK_SIZE) }
|
||||
check(ChunkPos.fromPosition(-CHUNK_SIZE_FF, 0) == ChunkPos(-1, 0)) { ChunkPos.fromPosition(-CHUNK_SIZE_FF, 0).toString() }
|
||||
check(ChunkPos.fromPosition(-CHUNK_SIZE, 0) == ChunkPos(-2, 0)) { ChunkPos.fromPosition(-CHUNK_SIZE, 0) }
|
||||
check(ChunkPos.fromPosition(0, -CHUNK_SIZE_FF) == ChunkPos(0, -1)) { ChunkPos.fromPosition(0, -CHUNK_SIZE_FF) }
|
||||
check(ChunkPos.fromPosition(0, -CHUNK_SIZE) == ChunkPos(0, -2)) { ChunkPos.fromPosition(0, -CHUNK_SIZE) }
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user