diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt index dfb60953..67b6badd 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/Main.kt @@ -190,7 +190,7 @@ fun main() { client.gl.box2dRenderer.drawAABB = false client.gl.box2dRenderer.drawJoints = false - ent.spawn() + //ent.spawn() client.input.addScrollCallback { _, x, y -> if (y > 0.0) { @@ -207,11 +207,11 @@ fun main() { //client.camera.pos.x = ent.position.x.toFloat() //client.camera.pos.y = ent.position.y.toFloat() - client.camera.pos.x += if (client.input.KEY_LEFT_DOWN) -client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f - client.camera.pos.x += if (client.input.KEY_RIGHT_DOWN) client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f + client.camera.pos.x += if (client.input.KEY_LEFT_DOWN || client.input.KEY_A_DOWN) -client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f + client.camera.pos.x += if (client.input.KEY_RIGHT_DOWN || client.input.KEY_D_DOWN) client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f - client.camera.pos.y += if (client.input.KEY_UP_DOWN) client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f - client.camera.pos.y += if (client.input.KEY_DOWN_DOWN) -client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f + client.camera.pos.y += if (client.input.KEY_UP_DOWN || client.input.KEY_W_DOWN) client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f + client.camera.pos.y += if (client.input.KEY_DOWN_DOWN || client.input.KEY_S_DOWN) -client.frameRenderTime.toFloat() * 32f / client.settings.scale else 0f //println(client.camera.velocity.toDoubleVector() * client.frameRenderTime * 0.1) diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt index 3e4fb485..b93856f9 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientChunk.kt @@ -19,7 +19,7 @@ import java.io.Closeable */ const val Z_LEVEL_BACKGROUND = 60000 -class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk(world, pos), Closeable, ILayeredRenderer { +class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk(world, pos), Closeable { val state: GLStateTracker get() = world.client.gl private inner class TileLayerRenderer(private val layerChangeset: () -> Int, private val isBackground: Boolean) : AutoCloseable { @@ -221,63 +221,69 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk Unit, Int>>() + /** + * Хранит состояние отрисовки этого чанка + * + * Должен быть использован только один раз, после выкинут, иначе поведение + * кода невозможно будет предсказать + */ + inner class BakedLayeredRenderer constructor(val origin: ChunkPos = pos) : ILayeredRenderer { + private val layerQueue = ArrayDeque Unit, Int>>() - override fun renderLayerFromStack(zPos: Int, stack: Matrix4fStack): Int { - if (layerQueue.isEmpty()) - return -1 + init { + for ((baked, zLevel) in backgroundRenderer.bakedMeshes) { + layerQueue.add(baked::renderStacked to (zLevel + Z_LEVEL_BACKGROUND)) + } - stack.push().translateWithMultiplication(x = pos.x * CHUNK_SIZEf, y = pos.y * CHUNK_SIZEf) - var pair = layerQueue.last() + for ((baked, zLevel) in foregroundRenderer.bakedMeshes) { + layerQueue.add(baked::renderStacked to zLevel) + } - while (pair.second >= zPos) { - pair.first.invoke(stack) + for (renderer in entityRenderers.values) { + layerQueue.add(lambda@{ it: Matrix4fStack -> + val relative = renderer.renderPos - posVector2d + it.push().translateWithMultiplication(relative.x.toFloat(), relative.y.toFloat()) + renderer.render(it) + it.pop() + return@lambda + } to renderer.layer) + } - layerQueue.removeLast() + layerQueue.sortBy { + return@sortBy it.second + } + } + override fun renderLayerFromStack(zPos: Int, stack: Matrix4fStack): Int { + if (layerQueue.isEmpty()) + return -1 + + stack.push().translateWithMultiplication(x = origin.x * CHUNK_SIZEf, y = origin.y * CHUNK_SIZEf) + var pair = layerQueue.last() + + while (pair.second >= zPos) { + pair.first.invoke(stack) + + layerQueue.removeLast() + + if (layerQueue.isEmpty()) { + stack.pop() + return -1 + } + + pair = layerQueue.last() + } + + stack.pop() + return layerQueue.last().second + } + + override fun bottomMostZLevel(): Int { if (layerQueue.isEmpty()) { - stack.pop() return -1 } - pair = layerQueue.last() - } - - stack.pop() - return layerQueue.last().second - } - - override fun bottomMostZLevel(): Int { - if (layerQueue.isEmpty()) { - return -1 - } - - return layerQueue.last().second - } - - override fun prepareForLayeredRender() { - layerQueue.clear() - - for ((baked, zLevel) in backgroundRenderer.bakedMeshes) { - layerQueue.add(baked::renderStacked to (zLevel + Z_LEVEL_BACKGROUND)) - } - - for ((baked, zLevel) in foregroundRenderer.bakedMeshes) { - layerQueue.add(baked::renderStacked to zLevel) - } - - for (renderer in entityRenderers.values) { - layerQueue.add(lambda@{ it: Matrix4fStack -> - val relative = renderer.renderPos - posVector2d - it.push().translateWithMultiplication(relative.x.toFloat(), relative.y.toFloat()) - renderer.render(it) - it.pop() - return@lambda - } to renderer.layer) - } - - layerQueue.sortBy { - return@sortBy it.second + return layerQueue.last().second } } diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt index 9b69d509..31366f69 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/ClientWorld.kt @@ -3,15 +3,21 @@ package ru.dbotthepony.kstarbound.client import org.lwjgl.opengl.GL46.* import ru.dbotthepony.kstarbound.PIXELS_IN_STARBOUND_UNITf import ru.dbotthepony.kstarbound.client.gl.VertexTransformers +import ru.dbotthepony.kstarbound.client.render.ILayeredRenderer import ru.dbotthepony.kstarbound.client.render.renderLayeredList import ru.dbotthepony.kstarbound.defs.ParallaxPrototype import ru.dbotthepony.kstarbound.math.encasingChunkPosAABB import ru.dbotthepony.kstarbound.util.DoubleEdgeProgression import ru.dbotthepony.kstarbound.world.* import ru.dbotthepony.kstarbound.world.entities.Entity +import ru.dbotthepony.kvector.matrix.Matrix4fStack import ru.dbotthepony.kvector.util2d.AABB -class ClientWorld(val client: StarboundClient, seed: Long = 0L) : World(seed) { +class ClientWorld( + val client: StarboundClient, + seed: Long, + widthInChunks: Int, +) : World(seed, widthInChunks) { init { physics.debugDraw = client.gl.box2dRenderer } @@ -94,20 +100,20 @@ class ClientWorld(val client: StarboundClient, seed: Long = 0L) : World() + val determineRenderers = ArrayList() - for (chunk in collect(size.encasingChunkPosAABB())) { - determineRenderers.add(chunk) - chunk.bake() + for (chunk in collectPositionAware(size.encasingChunkPosAABB())) { + determineRenderers.add(chunk.second.BakedLayeredRenderer(chunk.first)) + chunk.second.bake() } renderLayeredList(client.gl.matrixStack, determineRenderers) physics.debugDraw() - for (renderer in determineRenderers) { + /*for (renderer in determineRenderers) { renderer.renderDebug() - } + }*/ } override fun thinkInner(delta: Double) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt index 239a3c33..2e48ac17 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/StarboundClient.kt @@ -125,7 +125,7 @@ class StarboundClient : AutoCloseable { val gl = GLStateTracker() - var world: ClientWorld? = ClientWorld(this, 0L) + var world: ClientWorld? = ClientWorld(this, 0L, 0) fun ensureSameThread() = gl.ensureSameThread() diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/ILayeredRenderer.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/ILayeredRenderer.kt index 43ab43f3..94f1233e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/ILayeredRenderer.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/client/render/ILayeredRenderer.kt @@ -34,13 +34,6 @@ interface ILayeredRenderer { * если этот объект имеет самый дальний слой */ fun bottomMostZLevel(): Int - - /** - * Говорит о том, что вот-вот будет начата отрисовка в render pipeline - * и будут вызываться [renderLayerFromStack]. В данном методе должна построиться - * и отсортироваться стопка слоёв - */ - fun prepareForLayeredRender() } fun renderLayeredList(transform: Matrix4fStack, potentialRenderers: List): Int { @@ -48,7 +41,6 @@ fun renderLayeredList(transform: Matrix4fStack, potentialRenderers: List= 0) { diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkPos.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkPos.kt index 4e2b6938..ad09e92e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkPos.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/ChunkPos.kt @@ -6,6 +6,18 @@ import ru.dbotthepony.kvector.api.IStruct2i import ru.dbotthepony.kvector.vector.nint.Vector2i import kotlin.math.absoluteValue +private fun circulate(value: Int, bounds: Int): Int { + require(bounds > 0) { "Bounds must be positive ($bounds given)" } + + if (value >= bounds) { + return value % bounds + } else if (value < 0) { + return bounds + value % bounds + } + + return value +} + /** * Сетка чанков идёт как и сетка тайлов. * @@ -85,6 +97,16 @@ class ChunkPos(val x: Int, val y: Int) : Comparable { return y.compareTo(other.y) } + fun circular(xWrap: Int): ChunkPos { + val x = circulate(x, xWrap) + + if (x == this.x) { + return this + } + + return ChunkPos(x, y) + } + companion object { val ZERO = ChunkPos(0, 0) @@ -93,15 +115,29 @@ class ChunkPos(val x: Int, val y: Int) : Comparable { return ChunkPos(tileToChunkComponent(x), tileToChunkComponent(y)) } + fun fromTilePosition(input: IStruct2i, xWrap: Int): ChunkPos { + val (x, y) = input + return ChunkPos(circulate(tileToChunkComponent(x), xWrap), tileToChunkComponent(y)) + } + fun fromTilePosition(input: IStruct2d): ChunkPos { val (x, y) = input return fromTilePosition(x, y) } + fun fromTilePosition(input: IStruct2d, xWrap: Int): ChunkPos { + val (x, y) = input + return fromTilePosition(x, y, xWrap) + } + 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 { return ChunkPos( tileToChunkComponent(roundByAbsoluteValue(x)), @@ -109,6 +145,13 @@ class ChunkPos(val x: Int, val y: Int) : Comparable { ) } + fun fromTilePosition(x: Double, y: Double, xWrap: Int): ChunkPos { + return ChunkPos( + circulate(tileToChunkComponent(roundByAbsoluteValue(x)), xWrap), + tileToChunkComponent(roundByAbsoluteValue(y)) + ) + } + fun normalizeCoordinate(input: Int): Int { val band = input and CHUNK_SIZE_FF diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt index 6c684caa..77315c7e 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/World.kt @@ -33,11 +33,23 @@ data class WorldSweepResult( private const val EPSILON = 0.00001 -abstract class World, ChunkType : Chunk>(val seed: Long = 0L) { +abstract class World, ChunkType : Chunk>( + val seed: Long, + val widthInChunks: Int +) { protected val chunkMap = Object2ObjectAVLTreeMap cmp@{ a, b -> return@cmp a.compareTo(b) } + /** + * Является ли мир "сферическим" + * + * Данный флаг говорит о том, что [widthInChunks] имеет осмысленное значение, + * попытка получить чанк с X координатой меньше нуля или больше [widthInChunks] + * приведёт к замыканию на конец/начало мира соответственно + */ + val isCircular = widthInChunks > 0 + /** * Chunks, which have their collision mesh changed */ @@ -213,13 +225,28 @@ abstract class World, ChunkType : Chunk? { @@ -230,6 +257,9 @@ abstract class World, ChunkType : Chunk, ChunkType : Chunk() for (ent in orphanedEntities) { - val cPos = ChunkPos.fromTilePosition(ent.position) + val cPos = if (isCircular) ChunkPos.fromTilePosition(ent.position, widthInChunks) else ChunkPos.fromTilePosition(ent.position) if (cPos == pos) { orphanedInThisChunk.add(ent) @@ -330,6 +360,23 @@ abstract class World, ChunkType : Chunk> { + val output = ArrayList>() + + for (pos in boundingBox.chunkPositions) { + val chunk = get(pos) + + if (chunk != null) { + output.add(pos to chunk) + } + } + + return output + } + /** * Производит проверку на пересечение [worldaabb] с геометрией мира при попытке пройти в [_deltaMovement] */ diff --git a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/Entity.kt b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/Entity.kt index d9205f9d..78022ce7 100644 --- a/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/Entity.kt +++ b/src/main/kotlin/ru/dbotthepony/kstarbound/world/entities/Entity.kt @@ -95,10 +95,10 @@ abstract class Entity(override val world: World<*, *>) : IEntity { return } - val chunkPos = ChunkPos.fromTilePosition(position) + val chunkPos = if (world.isCircular) ChunkPos.fromTilePosition(position, world.widthInChunks) else ChunkPos.fromTilePosition(position) if (value != null && chunkPos != value.pos) { - throw IllegalStateException("Set proper position before setting chunk this Entity belongs to") + throw IllegalStateException("Set proper position before setting chunk this Entity belongs to (expected chunk $chunkPos, got chunk ${value.pos})") } val oldChunk = field