Chunk map, circular worlds second attempt
This commit is contained in:
parent
e436864e12
commit
0657ee8ef7
@ -65,7 +65,8 @@ fun main() {
|
|||||||
var parse = 0L
|
var parse = 0L
|
||||||
|
|
||||||
//for (chunkX in 17 .. 18) {
|
//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 21 .. 21) {
|
||||||
for (chunkY in 18 .. 24) {
|
for (chunkY in 18 .. 24) {
|
||||||
var t = System.currentTimeMillis()
|
var t = System.currentTimeMillis()
|
||||||
@ -73,7 +74,7 @@ fun main() {
|
|||||||
find += System.currentTimeMillis() - t
|
find += System.currentTimeMillis() - t
|
||||||
|
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
val chunk = client.world!!.computeIfAbsent(ChunkPos(chunkX, chunkY))
|
val chunk = client.world!!.chunkMap.computeIfAbsent(ChunkPos(chunkX, chunkY))
|
||||||
val inflater = Inflater()
|
val inflater = Inflater()
|
||||||
inflater.setInput(data)
|
inflater.setInput(data)
|
||||||
|
|
||||||
@ -105,12 +106,15 @@ fun main() {
|
|||||||
val item = starbound.items.values.random()
|
val item = starbound.items.values.random()
|
||||||
val rand = java.util.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)
|
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.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"])
|
// println(Starbound.statusEffects["firecharge"])
|
||||||
@ -144,13 +148,14 @@ fun main() {
|
|||||||
|
|
||||||
//ent.position += Vector2d(y = 14.0, x = -10.0)
|
//ent.position += Vector2d(y = 14.0, x = -10.0)
|
||||||
ent.position = Vector2d(600.0 + 16.0, 721.0 + 48.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.onDrawGUI {
|
||||||
client.gl.font.render("${ent.position}", y = 100f, scale = 0.25f)
|
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("${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("${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 {
|
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_SIZE
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEd
|
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEd
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEf
|
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.api.ITileChunk
|
||||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||||
import ru.dbotthepony.kvector.arrays.Matrix4fStack
|
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 {
|
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos), Closeable {
|
||||||
val state: GLStateTracker get() = world.client.gl
|
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()
|
private val layers = TileLayerList()
|
||||||
val bakedMeshes = LinkedList<Pair<ConfiguredStaticMesh, Int>>()
|
val bakedMeshes = LinkedList<Pair<ConfiguredStaticMesh, Int>>()
|
||||||
private var changeset = -1
|
var isDirty = true
|
||||||
|
|
||||||
fun bake() {
|
fun bake() {
|
||||||
|
isDirty = false
|
||||||
val view = view()
|
val view = view()
|
||||||
|
|
||||||
if (state.isSameThread()) {
|
if (state.isSameThread()) {
|
||||||
@ -60,7 +62,7 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
|||||||
|
|
||||||
layers.clear()
|
layers.clear()
|
||||||
|
|
||||||
for ((pos, tile) in view.iterate()) {
|
for ((pos, tile) in view.iterateTiles()) {
|
||||||
val material = tile.material
|
val material = tile.material
|
||||||
|
|
||||||
if (material != null) {
|
if (material != null) {
|
||||||
@ -78,7 +80,7 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
|||||||
fun loadRenderers() {
|
fun loadRenderers() {
|
||||||
val view = view()
|
val view = view()
|
||||||
|
|
||||||
for ((_, tile) in view.iterate()) {
|
for ((_, tile) in view.iterateTiles()) {
|
||||||
val material = tile.material
|
val material = tile.material
|
||||||
val modifier = tile.modifier
|
val modifier = tile.modifier
|
||||||
|
|
||||||
@ -111,9 +113,8 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun autoBake() {
|
fun autoBake() {
|
||||||
if (changeset != layerChangeset.asInt) {
|
if (isDirty) {
|
||||||
this.bake()
|
bake()
|
||||||
changeset = layerChangeset.asInt
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,8 +154,28 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
|||||||
val debugCollisions get() = world.client.settings.debugCollisions
|
val debugCollisions get() = world.client.settings.debugCollisions
|
||||||
val posVector2d = Vector2d(x = pos.x * CHUNK_SIZEd, y = pos.y * CHUNK_SIZEd)
|
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 foregroundRenderer = TileLayerRenderer({ world.getView(pos).foregroundView }, isBackground = false)
|
||||||
private val backgroundRenderer = TileLayerRenderer(::backgroundChangeset, { world.getView(pos).backgroundView }, isBackground = true)
|
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 все необходимые рендереры (ибо им нужны текстуры и прочее)
|
* Принудительно подгружает в GLStateTracker все необходимые рендереры (ибо им нужны текстуры и прочее)
|
||||||
|
@ -5,12 +5,15 @@ import ru.dbotthepony.kstarbound.math.encasingChunkPosAABB
|
|||||||
import ru.dbotthepony.kstarbound.world.*
|
import ru.dbotthepony.kstarbound.world.*
|
||||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
import ru.dbotthepony.kstarbound.world.entities.Entity
|
||||||
import ru.dbotthepony.kvector.util2d.AABB
|
import ru.dbotthepony.kvector.util2d.AABB
|
||||||
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
|
|
||||||
class ClientWorld(
|
class ClientWorld(
|
||||||
val client: StarboundClient,
|
val client: StarboundClient,
|
||||||
seed: Long,
|
seed: Long,
|
||||||
widthInChunks: Int,
|
size: Vector2i? = null,
|
||||||
) : World<ClientWorld, ClientChunk>(seed, widthInChunks) {
|
loopX: Boolean = false,
|
||||||
|
loopY: Boolean = false
|
||||||
|
) : World<ClientWorld, ClientChunk>(seed, size, loopX, loopY) {
|
||||||
init {
|
init {
|
||||||
physics.debugDraw = client.gl.box2dRenderer
|
physics.debugDraw = client.gl.box2dRenderer
|
||||||
}
|
}
|
||||||
@ -195,8 +198,6 @@ class ClientWorld(
|
|||||||
//frame.close()
|
//frame.close()
|
||||||
//texture.close()
|
//texture.close()
|
||||||
|
|
||||||
physics.debugDraw()
|
|
||||||
|
|
||||||
/*for (renderer in determineRenderers) {
|
/*for (renderer in determineRenderers) {
|
||||||
renderer.renderDebug()
|
renderer.renderDebug()
|
||||||
}*/
|
}*/
|
||||||
|
@ -26,6 +26,7 @@ import ru.dbotthepony.kvector.util2d.AABB
|
|||||||
import ru.dbotthepony.kvector.vector.RGBAColor
|
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||||
import ru.dbotthepony.kvector.vector.Vector2d
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
import ru.dbotthepony.kvector.vector.Vector2f
|
import ru.dbotthepony.kvector.vector.Vector2f
|
||||||
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
import ru.dbotthepony.kvector.vector.Vector3f
|
import ru.dbotthepony.kvector.vector.Vector3f
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
@ -235,7 +236,7 @@ class StarboundClient(val starbound: Starbound) : Closeable {
|
|||||||
lightRenderer.resizeFramebuffer(viewportWidth, viewportHeight)
|
lightRenderer.resizeFramebuffer(viewportWidth, viewportHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
var world: ClientWorld? = ClientWorld(this, 0L, 0)
|
var world: ClientWorld? = ClientWorld(this, 0L, Vector2i(3000, 2000), true)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
putDebugLog("Initialized OpenGL context")
|
putDebugLog("Initialized OpenGL context")
|
||||||
@ -345,6 +346,8 @@ class StarboundClient(val starbound: Starbound) : Closeable {
|
|||||||
|
|
||||||
layers.render(gl.matrixStack)
|
layers.render(gl.matrixStack)
|
||||||
|
|
||||||
|
world.physics.debugDraw()
|
||||||
|
|
||||||
for (lambda in onPostDrawWorld) {
|
for (lambda in onPostDrawWorld) {
|
||||||
lambda.invoke()
|
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.GLAttributeList
|
||||||
import ru.dbotthepony.kstarbound.client.gl.vertex.*
|
import ru.dbotthepony.kstarbound.client.gl.vertex.*
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.*
|
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.ITileChunk
|
||||||
import ru.dbotthepony.kstarbound.world.api.ITileState
|
import ru.dbotthepony.kstarbound.world.api.ITileState
|
||||||
import ru.dbotthepony.kstarbound.world.api.TileColor
|
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 org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.io.json.builder.JsonFactory
|
||||||
import ru.dbotthepony.kstarbound.util.WriteOnce
|
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.kstarbound.world.api.ITileState
|
||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ data class RenderRuleList(
|
|||||||
val matchHue: Boolean = false,
|
val matchHue: Boolean = false,
|
||||||
val inverse: 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) {
|
return when (type) {
|
||||||
"EqualsSelf" -> equalityTester.test(getter.getTile(thisPos), getter.getTile(thisPos + offsetPos))
|
"EqualsSelf" -> equalityTester.test(getter.getTile(thisPos), getter.getTile(thisPos + offsetPos))
|
||||||
"Connects" -> getter.getTile(thisPos + offsetPos).material != null
|
"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) {
|
if (inverse) {
|
||||||
return !doTest(getter, equalityTester, thisPos, offsetPos)
|
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) {
|
when (join) {
|
||||||
Combination.ALL -> {
|
Combination.ALL -> {
|
||||||
for (entry in entries) {
|
for (entry in entries) {
|
||||||
@ -120,7 +120,7 @@ data class RenderMatch(
|
|||||||
) {
|
) {
|
||||||
var rule by WriteOnce<RenderRuleList>()
|
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)
|
return rule.test(getter, equalityTester, thisPos, offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,7 +156,7 @@ data class RenderMatch(
|
|||||||
*
|
*
|
||||||
* [equalityTester] требуется для проверки раенства между "этим" тайлом и другим
|
* [equalityTester] требуется для проверки раенства между "этим" тайлом и другим
|
||||||
*/
|
*/
|
||||||
fun test(tileAccess: ITileChunk, equalityTester: EqualityRuleTester, thisPos: Vector2i): Boolean {
|
fun test(tileAccess: ITileAccess, equalityTester: EqualityRuleTester, thisPos: Vector2i): Boolean {
|
||||||
for (matcher in matchAllPoints) {
|
for (matcher in matchAllPoints) {
|
||||||
if (!matcher.test(tileAccess, equalityTester, thisPos)) {
|
if (!matcher.test(tileAccess, equalityTester, thisPos)) {
|
||||||
return false
|
return false
|
||||||
|
@ -14,8 +14,8 @@ fun AABB.encasingIntAABB(): AABBi {
|
|||||||
|
|
||||||
fun AABB.encasingChunkPosAABB(): AABBi {
|
fun AABB.encasingChunkPosAABB(): AABBi {
|
||||||
return AABBi(
|
return AABBi(
|
||||||
Vector2i(ChunkPos.tileToChunkComponent(roundTowardsNegativeInfinity(mins.x)), ChunkPos.tileToChunkComponent(roundTowardsNegativeInfinity(mins.y))),
|
Vector2i(ChunkPos.component(roundTowardsNegativeInfinity(mins.x)), ChunkPos.component(roundTowardsNegativeInfinity(mins.y))),
|
||||||
Vector2i(ChunkPos.tileToChunkComponent(roundTowardsPositiveInfinity(maxs.x)), ChunkPos.tileToChunkComponent(roundTowardsPositiveInfinity(maxs.y))),
|
Vector2i(ChunkPos.component(roundTowardsPositiveInfinity(maxs.x)), ChunkPos.component(roundTowardsPositiveInfinity(maxs.y))),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package ru.dbotthepony.kstarbound.world
|
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.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.IChunk
|
||||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||||
import ru.dbotthepony.kvector.arrays.Object2DArray
|
import ru.dbotthepony.kvector.arrays.Object2DArray
|
||||||
@ -29,8 +29,8 @@ class CellView(
|
|||||||
val bottom: IChunk?,
|
val bottom: IChunk?,
|
||||||
val bottomRight: IChunk?,
|
val bottomRight: IChunk?,
|
||||||
) : IChunk {
|
) : IChunk {
|
||||||
val backgroundView = BackgroundView(this)
|
val backgroundView = BackgroundChunkView(this)
|
||||||
val foregroundView = ForegroundView(this)
|
val foregroundView = ForegroundChunkView(this)
|
||||||
|
|
||||||
override fun getCell(x: Int, y: Int): IChunkCell {
|
override fun getCell(x: Int, y: Int): IChunkCell {
|
||||||
val ix = x + CHUNK_SIZE
|
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.LiquidDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
|
import ru.dbotthepony.kstarbound.defs.tile.MaterialModifier
|
||||||
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
|
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_BITS
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE
|
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_FF
|
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_FF
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZEd
|
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.IChunk
|
||||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||||
import ru.dbotthepony.kstarbound.world.api.ILiquidState
|
import ru.dbotthepony.kstarbound.world.api.ILiquidState
|
||||||
@ -38,8 +38,7 @@ import kotlin.collections.HashSet
|
|||||||
*
|
*
|
||||||
* Весь игровой мир будет измеряться в Starbound Unit'ах
|
* Весь игровой мир будет измеряться в Starbound Unit'ах
|
||||||
*/
|
*/
|
||||||
abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType, This>>(val world: WorldType, final override val pos: ChunkPos) :
|
abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType, This>>(val world: WorldType, final override val pos: ChunkPos) : IChunk {
|
||||||
IChunk {
|
|
||||||
var changeset = 0
|
var changeset = 0
|
||||||
private set
|
private set
|
||||||
var tileChangeset = 0
|
var tileChangeset = 0
|
||||||
@ -56,14 +55,34 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
var backgroundChangeset = 0
|
var backgroundChangeset = 0
|
||||||
private set
|
private set
|
||||||
|
|
||||||
val left get() = pos.left
|
protected open fun foregroundChanges() {
|
||||||
val right get() = pos.right
|
changeset++
|
||||||
val top get() = pos.top
|
cellChangeset++
|
||||||
val bottom get() = pos.bottom
|
tileChangeset++
|
||||||
val topLeft get() = pos.topLeft
|
|
||||||
val topRight get() = pos.topRight
|
collisionChangeset++
|
||||||
val bottomLeft get() = pos.bottomLeft
|
foregroundChangeset++
|
||||||
val bottomRight get() = pos.bottomRight
|
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())
|
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)
|
protected val cells = Object2DArray(CHUNK_SIZE, CHUNK_SIZE, ::Cell)
|
||||||
|
|
||||||
val backgroundView = BackgroundView(this)
|
val backgroundView = BackgroundChunkView(this)
|
||||||
val foregroundView = ForegroundView(this)
|
val foregroundView = ForegroundChunkView(this)
|
||||||
|
|
||||||
override fun getCell(x: Int, y: Int): IChunkCell {
|
override fun getCell(x: Int, y: Int): IChunkCell {
|
||||||
return cells[x, y]
|
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 Cell(val x: Int, val y: Int) : IChunkCell {
|
||||||
inner class Tile(private val foreground: Boolean) : ITileState {
|
inner class Tile(private val foreground: Boolean) : ITileState {
|
||||||
private fun change() {
|
private fun change() {
|
||||||
changeset++
|
|
||||||
cellChangeset++
|
|
||||||
tileChangeset++
|
|
||||||
|
|
||||||
if (foreground) {
|
if (foreground) {
|
||||||
collisionChangeset++
|
foregroundChanges()
|
||||||
foregroundChangeset++
|
|
||||||
markPhysicsDirty()
|
|
||||||
} else {
|
} else {
|
||||||
backgroundChangeset++
|
backgroundChanges()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,11 +2,9 @@ package ru.dbotthepony.kstarbound.world
|
|||||||
|
|
||||||
import ru.dbotthepony.kstarbound.math.roundByAbsoluteValue
|
import ru.dbotthepony.kstarbound.math.roundByAbsoluteValue
|
||||||
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_BITS
|
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.IStruct2d
|
||||||
import ru.dbotthepony.kvector.api.IStruct2i
|
import ru.dbotthepony.kvector.api.IStruct2i
|
||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
import kotlin.math.absoluteValue
|
|
||||||
|
|
||||||
private fun circulate(value: Int, bounds: Int): Int {
|
private fun circulate(value: Int, bounds: Int): Int {
|
||||||
require(bounds > 0) { "Bounds must be positive ($bounds given)" }
|
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)
|
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
|
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
|
val (x, y) = input
|
||||||
return ChunkPos(circulate(tileToChunkComponent(x), xWrap), tileToChunkComponent(y))
|
return fromPosition(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromTilePosition(input: IStruct2d): ChunkPos {
|
fun fromPosition(x: Int, y: Int): ChunkPos {
|
||||||
val (x, y) = input
|
return ChunkPos(component(x), component(y))
|
||||||
return fromTilePosition(x, y)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromTilePosition(input: IStruct2d, xWrap: Int): ChunkPos {
|
fun fromPosition(x: Int, y: Int, xWrap: Int): ChunkPos {
|
||||||
val (x, y) = input
|
return ChunkPos(circulate(component(x), xWrap), component(y))
|
||||||
return fromTilePosition(x, y, xWrap)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromTilePosition(x: Int, y: Int): ChunkPos {
|
fun fromPosition(x: Double, y: Double): 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 {
|
|
||||||
return ChunkPos(
|
return ChunkPos(
|
||||||
tileToChunkComponent(roundByAbsoluteValue(x)),
|
component(roundByAbsoluteValue(x)),
|
||||||
tileToChunkComponent(roundByAbsoluteValue(y))
|
component(roundByAbsoluteValue(y))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromTilePosition(x: Double, y: Double, xWrap: Int): ChunkPos {
|
fun fromPosition(x: Double, y: Double, xWrap: Int): ChunkPos {
|
||||||
return ChunkPos(
|
return ChunkPos(
|
||||||
circulate(tileToChunkComponent(roundByAbsoluteValue(x)), xWrap),
|
circulate(component(roundByAbsoluteValue(x)), xWrap),
|
||||||
tileToChunkComponent(roundByAbsoluteValue(y))
|
component(roundByAbsoluteValue(y))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun normalizeCoordinate(input: Int): Int {
|
fun component(value: Int): Int {
|
||||||
val band = input and CHUNK_SIZE_FF
|
if (value < 0) {
|
||||||
|
return -((-value) shr CHUNK_SIZE_BITS) - 1
|
||||||
if (band < 0) {
|
|
||||||
return band + CHUNK_SIZE_FF
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return band
|
return value shr CHUNK_SIZE_BITS
|
||||||
}
|
|
||||||
|
|
||||||
fun tileToChunkComponent(comp: Int): Int {
|
|
||||||
if (comp < 0) {
|
|
||||||
return -(comp.absoluteValue shr CHUNK_SIZE_BITS) - 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return comp 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.CHUNK_SIZE
|
||||||
import ru.dbotthepony.kstarbound.world.api.IChunk
|
import ru.dbotthepony.kstarbound.world.api.IChunk
|
||||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
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.kstarbound.world.api.ITileState
|
||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
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>> {
|
return object : Iterator<Pair<Vector2i, ITileState>> {
|
||||||
private var x = fromX
|
private var x = fromX
|
||||||
private var y = fromY
|
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
|
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.longs.Long2ObjectOpenHashMap
|
||||||
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||||
import ru.dbotthepony.kbox2d.api.ContactImpulse
|
import ru.dbotthepony.kbox2d.api.ContactImpulse
|
||||||
import ru.dbotthepony.kbox2d.api.IContactFilter
|
import ru.dbotthepony.kbox2d.api.IContactFilter
|
||||||
import ru.dbotthepony.kbox2d.api.IContactListener
|
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.B2Fixture
|
||||||
import ru.dbotthepony.kbox2d.dynamics.B2World
|
import ru.dbotthepony.kbox2d.dynamics.B2World
|
||||||
import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
|
import ru.dbotthepony.kbox2d.dynamics.contact.AbstractContact
|
||||||
import ru.dbotthepony.kstarbound.METRES_IN_STARBOUND_UNIT
|
|
||||||
import ru.dbotthepony.kstarbound.math.*
|
import ru.dbotthepony.kstarbound.math.*
|
||||||
import ru.dbotthepony.kstarbound.util.Timer
|
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
|
||||||
|
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.api.IChunkCell
|
||||||
import ru.dbotthepony.kstarbound.world.entities.Entity
|
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.Int2DArray
|
||||||
|
import ru.dbotthepony.kvector.arrays.Object2DArray
|
||||||
import ru.dbotthepony.kvector.util2d.AABB
|
import ru.dbotthepony.kvector.util2d.AABB
|
||||||
import ru.dbotthepony.kvector.util2d.AABBi
|
import ru.dbotthepony.kvector.util2d.AABBi
|
||||||
import ru.dbotthepony.kvector.vector.Vector2d
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
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>>(
|
abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, ChunkType>>(
|
||||||
val seed: Long,
|
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
|
||||||
|
|
||||||
/**
|
protected fun positiveModulo(a: Int, b: Int): Int {
|
||||||
* Является ли мир "сферическим"
|
val result = a % b
|
||||||
*
|
return if (result < 0) result + b else result
|
||||||
* Данный флаг говорит о том, что [widthInChunks] имеет осмысленное значение,
|
}
|
||||||
* попытка получить чанк с X координатой меньше нуля или больше [widthInChunks]
|
|
||||||
* приведёт к замыканию на конец/начало мира соответственно
|
protected fun positiveModulo(a: Double, b: Int): Double {
|
||||||
*/
|
val result = a % b
|
||||||
val isCircular = widthInChunks > 0
|
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
|
* Chunks, which have their collision mesh changed
|
||||||
*/
|
*/
|
||||||
val dirtyPhysicsChunks = HashSet<ChunkType>()
|
val dirtyPhysicsChunks = ObjectOpenHashSet<ChunkType>()
|
||||||
|
|
||||||
val physics = B2World(Vector2d(0.0, -EARTH_FREEFALL_ACCELERATION))
|
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
|
var gravity by physics::gravity
|
||||||
|
|
||||||
protected abstract fun chunkFactory(
|
protected abstract fun chunkFactory(pos: ChunkPos): ChunkType
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getView(pos: ChunkPos): CellView {
|
fun getView(pos: ChunkPos): CellView {
|
||||||
val tuple = get(pos)?.let { InstantWorldChunkTuple(this as This, it) }
|
|
||||||
|
|
||||||
return CellView(
|
return CellView(
|
||||||
pos = pos,
|
pos = pos,
|
||||||
center = tuple?.chunk,
|
center = chunkMap[pos],
|
||||||
left = tuple?.left?.chunk,
|
left = chunkMap[pos.left],
|
||||||
top = tuple?.top?.chunk,
|
top = chunkMap[pos.top],
|
||||||
topLeft = tuple?.topLeft?.chunk,
|
topLeft = chunkMap[pos.topLeft],
|
||||||
topRight = tuple?.topRight?.chunk,
|
topRight = chunkMap[pos.topRight],
|
||||||
right = tuple?.right?.chunk,
|
right = chunkMap[pos.right],
|
||||||
bottom = tuple?.bottom?.chunk,
|
bottom = chunkMap[pos.bottom],
|
||||||
bottomLeft = tuple?.bottomLeft?.chunk,
|
bottomLeft = chunkMap[pos.bottomLeft],
|
||||||
bottomRight = tuple?.bottomRight?.chunk,
|
bottomRight = chunkMap[pos.bottomRight],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -567,9 +400,9 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
val output = ArrayList<ChunkType>()
|
val output = ArrayList<ChunkType>()
|
||||||
|
|
||||||
for (pos in boundingBox.chunkPositions) {
|
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)
|
output.add(chunk)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -584,9 +417,9 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
val output = ArrayList<Pair<ChunkPos, ChunkType>>()
|
val output = ArrayList<Pair<ChunkPos, ChunkType>>()
|
||||||
|
|
||||||
for (pos in boundingBox.chunkPositions) {
|
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)
|
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 lightmap = Int2DArray.allocate(lightIntensity * 2 + 1, lightIntensity * 2 + 1)
|
||||||
|
|
||||||
val view = getView(ChunkPos.fromTilePosition(lightPosition))
|
val view = getView(ChunkPos.fromPosition(lightPosition))
|
||||||
|
|
||||||
floodLightInto(
|
floodLightInto(
|
||||||
lightmap,
|
lightmap,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.world.api
|
package ru.dbotthepony.kstarbound.world.api
|
||||||
|
|
||||||
const val CHUNK_SIZE_BITS = 5
|
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 = 1 shl CHUNK_SIZE_BITS // 32
|
||||||
const val CHUNK_SIZE_FF = CHUNK_SIZE - 1
|
const val CHUNK_SIZE_FF = CHUNK_SIZE - 1
|
||||||
const val CHUNK_SIZEf = CHUNK_SIZE.toFloat()
|
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.api.IStruct2i
|
||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
|
|
||||||
interface IChunk {
|
interface IChunk : ICellAccess {
|
||||||
val pos: ChunkPos
|
val pos: ChunkPos
|
||||||
|
|
||||||
// relative
|
|
||||||
fun getCell(x: Int, y: Int): IChunkCell
|
|
||||||
fun getCell(pos: IStruct2i) = getCell(pos.component1(), pos.component2())
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Возвращает псевдослучайное Long для заданной позиции
|
* Возвращает псевдослучайное Long для заданной позиции
|
||||||
*
|
*
|
||||||
@ -47,4 +43,4 @@ interface IChunk {
|
|||||||
* Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции
|
* Для использования в рендерах и прочих вещах, которым нужно стабильное число на основе своей позиции
|
||||||
*/
|
*/
|
||||||
fun randomDoubleFor(pos: Vector2i) = randomDoubleFor(pos.x, pos.y)
|
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.defs.DamageType
|
||||||
import ru.dbotthepony.kstarbound.world.Chunk
|
import ru.dbotthepony.kstarbound.world.Chunk
|
||||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
import ru.dbotthepony.kvector.vector.Vector2d
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
|
|
||||||
@ -95,7 +94,7 @@ abstract class Entity(override val world: World<*, *>) : IEntity {
|
|||||||
return
|
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) {
|
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})")
|
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
|
return
|
||||||
|
|
||||||
val old = field
|
val old = field
|
||||||
field = value
|
field = Vector2d(world.chunkMap.x.cell(value.x), world.chunkMap.x.cell(value.y))
|
||||||
|
|
||||||
movement.notifyPositionChanged()
|
movement.notifyPositionChanged()
|
||||||
|
|
||||||
if (isSpawned && !isRemoved) {
|
if (isSpawned && !isRemoved) {
|
||||||
val oldChunkPos = ChunkPos.fromTilePosition(old)
|
val oldChunkPos = world.chunkMap.cellToGrid(old)
|
||||||
val newChunkPos = ChunkPos.fromTilePosition(value)
|
val newChunkPos = world.chunkMap.cellToGrid(field)
|
||||||
|
|
||||||
if (oldChunkPos != newChunkPos) {
|
if (oldChunkPos != newChunkPos) {
|
||||||
chunk = world[newChunkPos]
|
chunk = world.chunkMap[newChunkPos]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,7 +154,7 @@ abstract class Entity(override val world: World<*, *>) : IEntity {
|
|||||||
|
|
||||||
isSpawned = true
|
isSpawned = true
|
||||||
world.entities.add(this)
|
world.entities.add(this)
|
||||||
chunk = world[ChunkPos.fromTilePosition(position)]
|
chunk = world.chunkMap[world.chunkMap.cellToGrid(position)]
|
||||||
|
|
||||||
if (chunk == null) {
|
if (chunk == null) {
|
||||||
world.orphanedEntities.add(this)
|
world.orphanedEntities.add(this)
|
||||||
|
@ -51,21 +51,21 @@ object MathTests {
|
|||||||
@Test
|
@Test
|
||||||
@DisplayName("ChunkPos class tests")
|
@DisplayName("ChunkPos class tests")
|
||||||
fun chunkPosTests() {
|
fun chunkPosTests() {
|
||||||
check(ChunkPos.fromTilePosition(0, 0) == ChunkPos(0, 0))
|
check(ChunkPos.fromPosition(0, 0) == ChunkPos(0, 0))
|
||||||
check(ChunkPos.fromTilePosition(1, 0) == ChunkPos(0, 0))
|
check(ChunkPos.fromPosition(1, 0) == ChunkPos(0, 0))
|
||||||
check(ChunkPos.fromTilePosition(0, 1) == ChunkPos(0, 0))
|
check(ChunkPos.fromPosition(0, 1) == ChunkPos(0, 0))
|
||||||
check(ChunkPos.fromTilePosition(1, 1) == ChunkPos(0, 0))
|
check(ChunkPos.fromPosition(1, 1) == ChunkPos(0, 0))
|
||||||
check(ChunkPos.fromTilePosition(-1, 1) == ChunkPos(-1, 0))
|
check(ChunkPos.fromPosition(-1, 1) == ChunkPos(-1, 0))
|
||||||
check(ChunkPos.fromTilePosition(-1, -1) == ChunkPos(-1, -1))
|
check(ChunkPos.fromPosition(-1, -1) == ChunkPos(-1, -1))
|
||||||
|
|
||||||
check(ChunkPos.fromTilePosition(CHUNK_SIZE_FF, 0) == ChunkPos(0, 0))
|
check(ChunkPos.fromPosition(CHUNK_SIZE_FF, 0) == ChunkPos(0, 0))
|
||||||
check(ChunkPos.fromTilePosition(CHUNK_SIZE, 0) == ChunkPos(1, 0))
|
check(ChunkPos.fromPosition(CHUNK_SIZE, 0) == ChunkPos(1, 0))
|
||||||
check(ChunkPos.fromTilePosition(0, CHUNK_SIZE_FF) == ChunkPos(0, 0))
|
check(ChunkPos.fromPosition(0, CHUNK_SIZE_FF) == ChunkPos(0, 0))
|
||||||
check(ChunkPos.fromTilePosition(0, CHUNK_SIZE) == ChunkPos(0, 1))
|
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.fromPosition(-CHUNK_SIZE_FF, 0) == ChunkPos(-1, 0)) { ChunkPos.fromPosition(-CHUNK_SIZE_FF, 0).toString() }
|
||||||
check(ChunkPos.fromTilePosition(-CHUNK_SIZE, 0) == ChunkPos(-2, 0)) { ChunkPos.fromTilePosition(-CHUNK_SIZE, 0) }
|
check(ChunkPos.fromPosition(-CHUNK_SIZE, 0) == ChunkPos(-2, 0)) { ChunkPos.fromPosition(-CHUNK_SIZE, 0) }
|
||||||
check(ChunkPos.fromTilePosition(0, -CHUNK_SIZE_FF) == ChunkPos(0, -1)) { ChunkPos.fromTilePosition(0, -CHUNK_SIZE_FF) }
|
check(ChunkPos.fromPosition(0, -CHUNK_SIZE_FF) == ChunkPos(0, -1)) { ChunkPos.fromPosition(0, -CHUNK_SIZE_FF) }
|
||||||
check(ChunkPos.fromTilePosition(0, -CHUNK_SIZE) == ChunkPos(0, -2)) { ChunkPos.fromTilePosition(0, -CHUNK_SIZE) }
|
check(ChunkPos.fromPosition(0, -CHUNK_SIZE) == ChunkPos(0, -2)) { ChunkPos.fromPosition(0, -CHUNK_SIZE) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user