Circular world test
This commit is contained in:
parent
51a43d70be
commit
2653d043a9
@ -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)
|
||||
|
||||
|
@ -19,7 +19,7 @@ import java.io.Closeable
|
||||
*/
|
||||
const val Z_LEVEL_BACKGROUND = 60000
|
||||
|
||||
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos), Closeable, ILayeredRenderer {
|
||||
class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, ClientChunk>(world, pos), Closeable {
|
||||
val state: GLStateTracker get() = world.client.gl
|
||||
|
||||
private inner class TileLayerRenderer(private val layerChangeset: () -> Int, private val isBackground: Boolean) : AutoCloseable {
|
||||
@ -221,63 +221,69 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos) : Chunk<ClientWorld, Client
|
||||
}
|
||||
}
|
||||
|
||||
private val layerQueue = ArrayDeque<Pair<(Matrix4fStack) -> Unit, Int>>()
|
||||
/**
|
||||
* Хранит состояние отрисовки этого чанка
|
||||
*
|
||||
* Должен быть использован только один раз, после выкинут, иначе поведение
|
||||
* кода невозможно будет предсказать
|
||||
*/
|
||||
inner class BakedLayeredRenderer constructor(val origin: ChunkPos = pos) : ILayeredRenderer {
|
||||
private val layerQueue = ArrayDeque<Pair<(Matrix4fStack) -> 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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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<ClientWorld, ClientChunk>(seed) {
|
||||
class ClientWorld(
|
||||
val client: StarboundClient,
|
||||
seed: Long,
|
||||
widthInChunks: Int,
|
||||
) : World<ClientWorld, ClientChunk>(seed, widthInChunks) {
|
||||
init {
|
||||
physics.debugDraw = client.gl.box2dRenderer
|
||||
}
|
||||
@ -94,20 +100,20 @@ class ClientWorld(val client: StarboundClient, seed: Long = 0L) : World<ClientWo
|
||||
client.gl.matrixStack.pop()
|
||||
}
|
||||
|
||||
val determineRenderers = ArrayList<ClientChunk>()
|
||||
val determineRenderers = ArrayList<ILayeredRenderer>()
|
||||
|
||||
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) {
|
||||
|
@ -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()
|
||||
|
||||
|
@ -34,13 +34,6 @@ interface ILayeredRenderer {
|
||||
* если этот объект имеет самый дальний слой
|
||||
*/
|
||||
fun bottomMostZLevel(): Int
|
||||
|
||||
/**
|
||||
* Говорит о том, что вот-вот будет начата отрисовка в render pipeline
|
||||
* и будут вызываться [renderLayerFromStack]. В данном методе должна построиться
|
||||
* и отсортироваться стопка слоёв
|
||||
*/
|
||||
fun prepareForLayeredRender()
|
||||
}
|
||||
|
||||
fun renderLayeredList(transform: Matrix4fStack, potentialRenderers: List<ILayeredRenderer>): Int {
|
||||
@ -48,7 +41,6 @@ fun renderLayeredList(transform: Matrix4fStack, potentialRenderers: List<ILayere
|
||||
var bottomMost = -1
|
||||
|
||||
for (render in potentialRenderers) {
|
||||
render.prepareForLayeredRender()
|
||||
val zLevel = render.bottomMostZLevel()
|
||||
|
||||
if (zLevel >= 0) {
|
||||
|
@ -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<ChunkPos> {
|
||||
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<ChunkPos> {
|
||||
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<ChunkPos> {
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
|
@ -33,11 +33,23 @@ data class WorldSweepResult(
|
||||
|
||||
private const val EPSILON = 0.00001
|
||||
|
||||
abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, ChunkType>>(val seed: Long = 0L) {
|
||||
abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, ChunkType>>(
|
||||
val seed: Long,
|
||||
val widthInChunks: Int
|
||||
) {
|
||||
protected val chunkMap = Object2ObjectAVLTreeMap<ChunkPos, ChunkType> 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<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
* Возвращает чанк на указанной позиции если он существует
|
||||
*/
|
||||
open operator fun get(pos: ChunkPos): ChunkType? {
|
||||
if (!isCircular) {
|
||||
val lastAccessedChunk = lastAccessedChunk
|
||||
|
||||
if (lastAccessedChunk?.pos == pos) {
|
||||
return lastAccessedChunk
|
||||
}
|
||||
|
||||
return chunkMap[pos]
|
||||
}
|
||||
|
||||
@Suppress("Name_Shadowing")
|
||||
val pos = pos.circular(widthInChunks)
|
||||
|
||||
val lastAccessedChunk = lastAccessedChunk
|
||||
|
||||
if (lastAccessedChunk?.pos == pos) {
|
||||
return lastAccessedChunk
|
||||
}
|
||||
|
||||
return chunkMap[pos]
|
||||
val load = chunkMap[pos]
|
||||
this.lastAccessedChunk = load
|
||||
return load
|
||||
}
|
||||
|
||||
open fun getInstantTuple(pos: ChunkPos): IWorldChunkTuple<This, ChunkType>? {
|
||||
@ -230,6 +257,9 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
* Возвращает чанк на заданной позиции, создаёт его если он не существует
|
||||
*/
|
||||
open fun computeIfAbsent(pos: ChunkPos): ChunkType {
|
||||
@Suppress("Name_Shadowing")
|
||||
val pos = pos.circular(widthInChunks)
|
||||
|
||||
val _lastAccessedChunk = lastAccessedChunk
|
||||
|
||||
if (_lastAccessedChunk?.pos == pos) {
|
||||
@ -244,7 +274,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
val orphanedInThisChunk = ArrayList<Entity>()
|
||||
|
||||
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<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
||||
return output
|
||||
}
|
||||
|
||||
/**
|
||||
* Возвращает все чанки, которые пересекаются с заданным [boundingBox]
|
||||
*/
|
||||
open fun collectPositionAware(boundingBox: AABBi): List<Pair<ChunkPos, ChunkType>> {
|
||||
val output = ArrayList<Pair<ChunkPos, ChunkType>>()
|
||||
|
||||
for (pos in boundingBox.chunkPositions) {
|
||||
val chunk = get(pos)
|
||||
|
||||
if (chunk != null) {
|
||||
output.add(pos to chunk)
|
||||
}
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
/**
|
||||
* Производит проверку на пересечение [worldaabb] с геометрией мира при попытке пройти в [_deltaMovement]
|
||||
*/
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user