Undo dynamic chunk dimensions, add render regions instead for solid grid rendering
This commit is contained in:
parent
94fe3662ad
commit
538f8a9b72
@ -12,7 +12,6 @@ import ru.dbotthepony.kstarbound.player.Avatar
|
|||||||
import ru.dbotthepony.kstarbound.player.QuestDescriptor
|
import ru.dbotthepony.kstarbound.player.QuestDescriptor
|
||||||
import ru.dbotthepony.kstarbound.player.QuestInstance
|
import ru.dbotthepony.kstarbound.player.QuestInstance
|
||||||
import ru.dbotthepony.kstarbound.util.JVMTimeSource
|
import ru.dbotthepony.kstarbound.util.JVMTimeSource
|
||||||
import ru.dbotthepony.kstarbound.world.ChunkPos
|
|
||||||
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
import ru.dbotthepony.kstarbound.world.api.IChunkCell
|
||||||
import ru.dbotthepony.kstarbound.world.entities.ItemEntity
|
import ru.dbotthepony.kstarbound.world.entities.ItemEntity
|
||||||
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
|
import ru.dbotthepony.kstarbound.world.entities.PlayerEntity
|
||||||
@ -90,7 +89,7 @@ fun main() {
|
|||||||
|
|
||||||
for (y in 0 .. 31) {
|
for (y in 0 .. 31) {
|
||||||
for (x in 0 .. 31) {
|
for (x in 0 .. 31) {
|
||||||
val cell = client.world!!.chunkMap.getCellDirect(chunkX * 32 + x, chunkY * 32 + y)
|
val cell = client.world!!.getCellDirect(chunkX * 32 + x, chunkY * 32 + y)
|
||||||
|
|
||||||
if (cell == null) {
|
if (cell == null) {
|
||||||
IChunkCell.skip(reader)
|
IChunkCell.skip(reader)
|
||||||
@ -160,7 +159,7 @@ fun main() {
|
|||||||
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("Camera: ${client.camera.pos} ${client.settings.zoom}", y = 140f, scale = 0.25f)
|
client.gl.font.render("Camera: ${client.camera.pos} ${client.settings.zoom}", y = 140f, scale = 0.25f)
|
||||||
client.gl.font.render("World chunk: ${client.world!!.chunkMap.cellToChunk(client.camera.pos.toDoubleVector())}", y = 160f, scale = 0.25f)
|
client.gl.font.render("World chunk: ${client.world!!.chunkFromCell(client.camera.pos.toDoubleVector())}", y = 160f, scale = 0.25f)
|
||||||
}
|
}
|
||||||
|
|
||||||
client.onPreDrawWorld {
|
client.onPreDrawWorld {
|
||||||
|
@ -26,7 +26,7 @@ import java.util.LinkedList
|
|||||||
const val Z_LEVEL_BACKGROUND = 60000
|
const val Z_LEVEL_BACKGROUND = 60000
|
||||||
const val Z_LEVEL_LIQUID = 10000
|
const val Z_LEVEL_LIQUID = 10000
|
||||||
|
|
||||||
class ClientChunk(world: ClientWorld, pos: ChunkPos, width: Int, height: Int) : Chunk<ClientWorld, ClientChunk>(world, pos, width, height), 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 view: ITileAccess, private val isBackground: Boolean) : AutoCloseable {
|
private inner class TileLayerRenderer(private val view: ITileAccess, private val isBackground: Boolean) : AutoCloseable {
|
||||||
@ -48,8 +48,9 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos, width: Int, height: Int) :
|
|||||||
|
|
||||||
layers.clear()
|
layers.clear()
|
||||||
|
|
||||||
for (x in 0 until width) {
|
for (x in 0 until CHUNK_SIZE) {
|
||||||
for (y in 0 until height) {
|
for (y in 0 until CHUNK_SIZE) {
|
||||||
|
if (!world.inBounds(x, y)) continue
|
||||||
val tile = view.getTile(x, y) ?: continue
|
val tile = view.getTile(x, y) ?: continue
|
||||||
val material = tile.material
|
val material = tile.material
|
||||||
|
|
||||||
@ -97,31 +98,31 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos, width: Int, height: Int) :
|
|||||||
override fun foregroundChanges() {
|
override fun foregroundChanges() {
|
||||||
super.foregroundChanges()
|
super.foregroundChanges()
|
||||||
|
|
||||||
foregroundRenderer.isDirty = true
|
world.forEachRenderRegion(pos) {
|
||||||
|
it.foreground.isDirty = true
|
||||||
|
}
|
||||||
|
|
||||||
forEachNeighbour {
|
forEachNeighbour {
|
||||||
it.foregroundRenderer.isDirty = true
|
world.forEachRenderRegion(it.pos) {
|
||||||
|
it.foreground.isDirty = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun backgroundChanges() {
|
override fun backgroundChanges() {
|
||||||
super.backgroundChanges()
|
super.backgroundChanges()
|
||||||
|
|
||||||
backgroundRenderer.isDirty = true
|
world.forEachRenderRegion(pos) {
|
||||||
|
it.background.isDirty = true
|
||||||
|
}
|
||||||
|
|
||||||
forEachNeighbour {
|
forEachNeighbour {
|
||||||
it.backgroundRenderer.isDirty = true
|
world.forEachRenderRegion(it.pos) {
|
||||||
|
it.background.isDirty = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Тесселирует "статичную" геометрию в builders (к примеру тайлы), с проверкой, изменилось ли что либо,
|
|
||||||
* и загружает её в видеопамять.
|
|
||||||
*
|
|
||||||
* Может быть вызван вне рендер потока (ибо в любом случае он требует некой "стаитичности" данных в чанке)
|
|
||||||
* но только если до этого был вызыван loadRenderers() и геометрия чанка не поменялась
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
fun bake() {
|
fun bake() {
|
||||||
backgroundRenderer.bake()
|
backgroundRenderer.bake()
|
||||||
foregroundRenderer.bake()
|
foregroundRenderer.bake()
|
||||||
@ -130,9 +131,6 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos, width: Int, height: Int) :
|
|||||||
upload()
|
upload()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Загружает в видеопамять всю геометрию напрямую, если есть что загружать
|
|
||||||
*/
|
|
||||||
fun upload() {
|
fun upload() {
|
||||||
backgroundRenderer.upload()
|
backgroundRenderer.upload()
|
||||||
foregroundRenderer.upload()
|
foregroundRenderer.upload()
|
||||||
@ -146,8 +144,8 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos, width: Int, height: Int) :
|
|||||||
liquidTypes.clear()
|
liquidTypes.clear()
|
||||||
liquidTypesVer = liquidChangeset
|
liquidTypesVer = liquidChangeset
|
||||||
|
|
||||||
for (x in 0 until width) {
|
for (x in 0 until CHUNK_SIZE) {
|
||||||
for (y in 0 until height) {
|
for (y in 0 until CHUNK_SIZE) {
|
||||||
getCell(x, y).liquid.def?.let { liquidTypes.add(it) }
|
getCell(x, y).liquid.def?.let { liquidTypes.add(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -199,8 +197,8 @@ class ClientChunk(world: ClientWorld, pos: ChunkPos, width: Int, height: Int) :
|
|||||||
for (type in types) {
|
for (type in types) {
|
||||||
builder.builder.begin()
|
builder.builder.begin()
|
||||||
|
|
||||||
for (x in 0 until width) {
|
for (x in 0 until CHUNK_SIZE) {
|
||||||
for (y in 0 until height) {
|
for (y in 0 until CHUNK_SIZE) {
|
||||||
val state = getCell(x, y)
|
val state = getCell(x, y)
|
||||||
|
|
||||||
if (state.liquid.def === type) {
|
if (state.liquid.def === type) {
|
||||||
|
@ -1,14 +1,26 @@
|
|||||||
package ru.dbotthepony.kstarbound.client
|
package ru.dbotthepony.kstarbound.client
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectFunction
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap
|
||||||
|
import ru.dbotthepony.kstarbound.client.render.ConfiguredStaticMesh
|
||||||
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
|
||||||
import ru.dbotthepony.kstarbound.math.encasingIntAABB
|
import ru.dbotthepony.kstarbound.client.render.TileLayerList
|
||||||
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
import ru.dbotthepony.kstarbound.math.roundTowardsNegativeInfinity
|
||||||
|
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
||||||
import ru.dbotthepony.kstarbound.world.*
|
import ru.dbotthepony.kstarbound.world.*
|
||||||
|
import ru.dbotthepony.kstarbound.world.api.ITileAccess
|
||||||
|
import ru.dbotthepony.kstarbound.world.api.OffsetCellAccess
|
||||||
|
import ru.dbotthepony.kstarbound.world.api.TileView
|
||||||
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.Vector2d
|
||||||
import ru.dbotthepony.kvector.vector.Vector2f
|
import ru.dbotthepony.kvector.vector.Vector2f
|
||||||
import ru.dbotthepony.kvector.vector.Vector2i
|
import ru.dbotthepony.kvector.vector.Vector2i
|
||||||
import kotlin.math.roundToInt
|
import java.util.function.Function
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
import kotlin.math.PI
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
class ClientWorld(
|
class ClientWorld(
|
||||||
val client: StarboundClient,
|
val client: StarboundClient,
|
||||||
@ -21,31 +33,186 @@ class ClientWorld(
|
|||||||
physics.debugDraw = client.gl.box2dRenderer
|
physics.debugDraw = client.gl.box2dRenderer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun determineChunkSize(cells: Int): Int {
|
||||||
|
for (i in 16 downTo 1) {
|
||||||
|
if (cells % i == 0) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw RuntimeException("unreachable code")
|
||||||
|
}
|
||||||
|
|
||||||
|
val renderRegionWidth = if (size == null) 16 else determineChunkSize(size.x)
|
||||||
|
val renderRegionHeight = if (size == null) 16 else determineChunkSize(size.y)
|
||||||
|
|
||||||
|
inner class RenderRegion(val x: Int, val y: Int) {
|
||||||
|
inner class Layer(private val view: ITileAccess, private val isBackground: Boolean) {
|
||||||
|
private val state get() = client.gl
|
||||||
|
private val layers = TileLayerList()
|
||||||
|
val bakedMeshes = ArrayList<Pair<ConfiguredStaticMesh, Int>>()
|
||||||
|
var isDirty = true
|
||||||
|
|
||||||
|
fun bake() {
|
||||||
|
if (!isDirty) return
|
||||||
|
isDirty = false
|
||||||
|
|
||||||
|
if (state.isSameThread()) {
|
||||||
|
for (mesh in bakedMeshes) {
|
||||||
|
mesh.first.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bakedMeshes.clear()
|
||||||
|
|
||||||
|
layers.clear()
|
||||||
|
|
||||||
|
for (x in 0 until renderRegionWidth) {
|
||||||
|
for (y in 0 until renderRegionHeight) {
|
||||||
|
if (!inBounds(x, y)) continue
|
||||||
|
|
||||||
|
val tile = view.getTile(x, y) ?: continue
|
||||||
|
val material = tile.material
|
||||||
|
|
||||||
|
if (material != null) {
|
||||||
|
client.tileRenderers.getTileRenderer(material.materialName).tesselate(tile, view, layers, Vector2i(x, y), background = isBackground)
|
||||||
|
}
|
||||||
|
|
||||||
|
val modifier = tile.modifier
|
||||||
|
|
||||||
|
if (modifier != null) {
|
||||||
|
client.tileRenderers.getModifierRenderer(modifier.modName).tesselate(tile, view, layers, Vector2i(x, y), background = isBackground, isModifier = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (layers.isNotEmpty) {
|
||||||
|
for (mesh in bakedMeshes) {
|
||||||
|
mesh.first.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
bakedMeshes.clear()
|
||||||
|
|
||||||
|
for ((baked, builder, zLevel) in layers.buildSortedLayerList()) {
|
||||||
|
bakedMeshes.add(ConfiguredStaticMesh(baked, builder) to zLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
layers.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val view = OffsetCellAccess(this@ClientWorld, x * renderRegionWidth, y * renderRegionHeight)
|
||||||
|
|
||||||
|
val background = Layer(TileView.Background(view), true)
|
||||||
|
val foreground = Layer(TileView.Foreground(view), false)
|
||||||
|
|
||||||
|
fun addLayers(layers: LayeredRenderer, renderOrigin: Vector2f) {
|
||||||
|
background.bake()
|
||||||
|
foreground.bake()
|
||||||
|
|
||||||
|
for ((baked, zLevel) in background.bakedMeshes) {
|
||||||
|
layers.add(zLevel + Z_LEVEL_BACKGROUND) {
|
||||||
|
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
||||||
|
baked.renderStacked(it)
|
||||||
|
it.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((baked, zLevel) in foreground.bakedMeshes) {
|
||||||
|
layers.add(zLevel) {
|
||||||
|
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
||||||
|
baked.renderStacked(it)
|
||||||
|
it.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*for (renderer in entityRenderers.values) {
|
||||||
|
layers.add(renderer.layer) {
|
||||||
|
val relative = renderer.renderPos - posVector2d
|
||||||
|
it.push().last().translateWithMultiplication(renderOrigin.x + relative.x.toFloat(), renderOrigin.y + relative.y.toFloat())
|
||||||
|
renderer.render(it)
|
||||||
|
it.pop()
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*val types = getLiquidTypes()
|
||||||
|
|
||||||
|
if (types.isNotEmpty()) {
|
||||||
|
layers.add(Z_LEVEL_LIQUID) {
|
||||||
|
it.push().last().translateWithMultiplication(renderOrigin.x, renderOrigin.y)
|
||||||
|
|
||||||
|
val program = state.programs.liquid
|
||||||
|
|
||||||
|
program.use()
|
||||||
|
program.transform = it.last()
|
||||||
|
|
||||||
|
val builder = program.builder
|
||||||
|
|
||||||
|
for (type in types) {
|
||||||
|
builder.builder.begin()
|
||||||
|
|
||||||
|
for (x in 0 until CHUNK_SIZE) {
|
||||||
|
for (y in 0 until CHUNK_SIZE) {
|
||||||
|
val state = getCell(x, y)
|
||||||
|
|
||||||
|
if (state.liquid.def === type) {
|
||||||
|
builder.builder.quad(x.toFloat(), y.toFloat(), x + 1f, y + state.liquid.level)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
program.baselineColor = type.color
|
||||||
|
|
||||||
|
builder.upload()
|
||||||
|
builder.draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
it.pop()
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val renderRegions = Long2ObjectOpenHashMap<RenderRegion>()
|
||||||
|
|
||||||
|
inline fun forEachRenderRegion(pos: ChunkPos, action: (RenderRegion) -> Unit) {
|
||||||
|
var (ix, iy) = pos.tile
|
||||||
|
ix /= renderRegionWidth
|
||||||
|
iy /= renderRegionHeight
|
||||||
|
|
||||||
|
for (x in ix .. ix + CHUNK_SIZE / renderRegionWidth) {
|
||||||
|
for (y in iy .. iy + CHUNK_SIZE / renderRegionWidth) {
|
||||||
|
renderRegions[x.toLong() shl 32 or y.toLong()]?.let(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun chunkFactory(pos: ChunkPos): ClientChunk {
|
override fun chunkFactory(pos: ChunkPos): ClientChunk {
|
||||||
return ClientChunk(this, pos, chunkWidth, chunkHeight)
|
return ClientChunk(this, pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addLayers(
|
fun addLayers(
|
||||||
size: AABB,
|
size: AABB,
|
||||||
layers: LayeredRenderer
|
layers: LayeredRenderer
|
||||||
) {
|
) {
|
||||||
val rx = roundTowardsNegativeInfinity(size.mins.x) / chunkWidth - 1
|
val rx = roundTowardsNegativeInfinity(size.mins.x) / renderRegionWidth - 1
|
||||||
val ry = roundTowardsNegativeInfinity(size.mins.y) / chunkHeight - 1
|
val ry = roundTowardsNegativeInfinity(size.mins.y) / renderRegionHeight - 1
|
||||||
|
|
||||||
val dx = roundTowardsNegativeInfinity(size.maxs.x - size.mins.x) / chunkWidth + 2
|
val dx = roundTowardsPositiveInfinity(size.maxs.x - size.mins.x) / renderRegionWidth + 2
|
||||||
val dy = roundTowardsNegativeInfinity(size.maxs.y - size.mins.y) / chunkWidth + 2
|
val dy = roundTowardsPositiveInfinity(size.maxs.y - size.mins.y) / renderRegionHeight + 2
|
||||||
|
|
||||||
for (x in rx .. rx + dx) {
|
for (x in rx .. rx + dx) {
|
||||||
for (y in ry .. ry + dy) {
|
for (y in ry .. ry + dy) {
|
||||||
val chunk = chunkMap[x, y] ?: continue
|
val renderer = renderRegions.computeIfAbsent(x.toLong() shl 32 or y.toLong(), Long2ObjectFunction {
|
||||||
val renderer = chunk.Renderer(Vector2f(x * chunkWidth.toFloat(), y * chunkHeight.toFloat()))
|
RenderRegion((it ushr 32).toInt(), it.toInt())
|
||||||
|
})
|
||||||
|
|
||||||
renderer.addLayers(layers)
|
renderer.addLayers(layers, Vector2f(x * renderRegionWidth.toFloat(), y * renderRegionHeight.toFloat()))
|
||||||
chunk.bake()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val pos = client.screenToWorld(client.mouseCoordinatesF)
|
val pos = client.screenToWorld(client.mouseCoordinatesF).toDoubleVector()
|
||||||
|
|
||||||
/*layers.add(-999999) {
|
/*layers.add(-999999) {
|
||||||
val lightsize = 16
|
val lightsize = 16
|
||||||
@ -65,29 +232,30 @@ class ClientWorld(
|
|||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
/*
|
|
||||||
val rayFan = ArrayList<Vector2d>()
|
|
||||||
|
|
||||||
for (i in 0 .. 359) {
|
layers.add(-999999) {
|
||||||
rayFan.add(Vector2d(cos(i / 180.0 * PI), sin(i / 180.0 * PI)))
|
val rayFan = ArrayList<Vector2d>()
|
||||||
}
|
|
||||||
|
|
||||||
for (ray in rayFan) {
|
for (i in 0 .. 359) {
|
||||||
val trace = castRayNaive(pos, ray, 16.0)
|
rayFan.add(Vector2d(cos(i / 180.0 * PI), sin(i / 180.0 * PI)))
|
||||||
|
}
|
||||||
|
|
||||||
client.gl.quadWireframe {
|
for (ray in rayFan) {
|
||||||
for ((tpos, tile) in trace.traversedTiles) {
|
val trace = castRayNaive(pos, ray, 16.0)
|
||||||
if (tile.material != null)
|
|
||||||
it.quad(
|
client.gl.quadWireframe {
|
||||||
tpos.x.toFloat(),
|
for ((tpos, tile) in trace.traversedTiles) {
|
||||||
tpos.y.toFloat(),
|
if (tile.foreground.material != null)
|
||||||
tpos.x + 1f,
|
it.quad(
|
||||||
tpos.y + 1f
|
tpos.x.toFloat(),
|
||||||
)
|
tpos.y.toFloat(),
|
||||||
|
tpos.x + 1f,
|
||||||
|
tpos.y + 1f
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
//rayLightCircleNaive(pos, 48.0, falloffByTravel = 1.0, falloffByTile = 3.0)
|
//rayLightCircleNaive(pos, 48.0, falloffByTravel = 1.0, falloffByTile = 3.0)
|
||||||
|
|
||||||
|
@ -227,7 +227,7 @@ class StarboundClient(val starbound: Starbound) : Closeable {
|
|||||||
|
|
||||||
val tileRenderers = TileRenderers(this)
|
val tileRenderers = TileRenderers(this)
|
||||||
|
|
||||||
var world: ClientWorld? = ClientWorld(this, 0L, null, true)
|
var world: ClientWorld? = ClientWorld(this, 0L, Vector2i(3000, 2000), true)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
putDebugLog("Initialized OpenGL context")
|
putDebugLog("Initialized OpenGL context")
|
||||||
|
@ -19,8 +19,8 @@ import ru.dbotthepony.kvector.vector.Vector2i
|
|||||||
import kotlin.collections.HashMap
|
import kotlin.collections.HashMap
|
||||||
|
|
||||||
data class TileLayer(
|
data class TileLayer(
|
||||||
val bakedProgramState: ConfiguredShaderProgram<GLTileProgram>,
|
val program: ConfiguredShaderProgram<GLTileProgram>,
|
||||||
val vertexBuilder: VertexBuilder,
|
val vertices: VertexBuilder,
|
||||||
val zPos: Int
|
val zPos: Int
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ class TileLayerList {
|
|||||||
fun computeIfAbsent(program: ConfiguredShaderProgram<GLTileProgram>, zLevel: Int, compute: () -> VertexBuilder): VertexBuilder {
|
fun computeIfAbsent(program: ConfiguredShaderProgram<GLTileProgram>, zLevel: Int, compute: () -> VertexBuilder): VertexBuilder {
|
||||||
return layers.computeIfAbsent(program) { Int2ObjectAVLTreeMap() }.computeIfAbsent(zLevel, Int2ObjectFunction {
|
return layers.computeIfAbsent(program) { Int2ObjectAVLTreeMap() }.computeIfAbsent(zLevel, Int2ObjectFunction {
|
||||||
return@Int2ObjectFunction TileLayer(program, compute.invoke(), zLevel)
|
return@Int2ObjectFunction TileLayer(program, compute.invoke(), zLevel)
|
||||||
}).vertexBuilder
|
}).vertices
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildSortedLayerList(): List<TileLayer> {
|
fun buildSortedLayerList(): List<TileLayer> {
|
||||||
|
@ -58,6 +58,14 @@ fun roundTowardsPositiveInfinity(value: Double): Int {
|
|||||||
return value.toInt()
|
return value.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun divideUp(a: Int, b: Int): Int {
|
||||||
|
return if (a % b == 0) {
|
||||||
|
a / b
|
||||||
|
} else {
|
||||||
|
a / b + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private const val EPSILON = 0.00000001
|
private const val EPSILON = 0.00000001
|
||||||
|
|
||||||
fun weakCompare(a: Double, b: Double, epsilon: Double = EPSILON): Int {
|
fun weakCompare(a: Double, b: Double, epsilon: Double = EPSILON): Int {
|
||||||
|
@ -42,8 +42,6 @@ import kotlin.collections.HashSet
|
|||||||
abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType, This>>(
|
abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType, This>>(
|
||||||
val world: WorldType,
|
val world: WorldType,
|
||||||
val pos: ChunkPos,
|
val pos: ChunkPos,
|
||||||
val width: Int,
|
|
||||||
val height: Int
|
|
||||||
) : ICellAccess {
|
) : ICellAccess {
|
||||||
var changeset = 0
|
var changeset = 0
|
||||||
private set(value) {
|
private set(value) {
|
||||||
@ -72,7 +70,7 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
private var isEmpty = true
|
private var isEmpty = true
|
||||||
|
|
||||||
protected val cells by lazy {
|
protected val cells by lazy {
|
||||||
Object2DArray.nulls<Cell>(width, height)
|
Object2DArray.nulls<Cell>(CHUNK_SIZE, CHUNK_SIZE)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCell(x: Int, y: Int): IChunkCell {
|
override fun getCell(x: Int, y: Int): IChunkCell {
|
||||||
@ -91,7 +89,7 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
val localForegroundView = TileView.Foreground(this)
|
val localForegroundView = TileView.Foreground(this)
|
||||||
|
|
||||||
// relative world cells access (accessing 0, 0 will lookup cell in world, relative to this chunk)
|
// relative world cells access (accessing 0, 0 will lookup cell in world, relative to this chunk)
|
||||||
val worldView = OffsetCellAccess(world.chunkMap, pos.x * width, pos.y * height)
|
val worldView = OffsetCellAccess(world, pos.x * CHUNK_SIZE, pos.y * CHUNK_SIZE)
|
||||||
val worldBackgroundView = TileView.Background(worldView)
|
val worldBackgroundView = TileView.Background(worldView)
|
||||||
val worldForegroundView = TileView.Foreground(worldView)
|
val worldForegroundView = TileView.Foreground(worldView)
|
||||||
|
|
||||||
@ -104,7 +102,7 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
|
|
||||||
private val body by lazy {
|
private val body by lazy {
|
||||||
world.physics.createBody(BodyDef(
|
world.physics.createBody(BodyDef(
|
||||||
position = pos.tile(width, height).toDoubleVector(),
|
position = pos.tile.toDoubleVector(),
|
||||||
userData = this
|
userData = this
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -149,8 +147,8 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
}
|
}
|
||||||
|
|
||||||
inner class Cell(x: Int, y: Int) : IChunkCell {
|
inner class Cell(x: Int, y: Int) : IChunkCell {
|
||||||
override val x: Int = x + pos.x * width
|
override val x: Int = x + pos.x * CHUNK_SIZE
|
||||||
override val y: Int = y + pos.y * width
|
override val y: Int = y + pos.y * CHUNK_SIZE
|
||||||
|
|
||||||
inner class Tile(private val foreground: Boolean) : ITileState {
|
inner class Tile(private val foreground: Boolean) : ITileState {
|
||||||
private fun change() {
|
private fun change() {
|
||||||
@ -342,7 +340,7 @@ abstract class Chunk<WorldType : World<WorldType, This>, This : Chunk<WorldType,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun randomLongFor(x: Int, y: Int): Long {
|
override fun randomLongFor(x: Int, y: Int): Long {
|
||||||
return world.chunkMap.randomLongFor(x or pos.x shl CHUNK_SIZE_BITS, y or pos.y shl CHUNK_SIZE_BITS)
|
return world.randomLongFor(x or pos.x shl CHUNK_SIZE_BITS, y or pos.y shl CHUNK_SIZE_BITS)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected val entities = ReferenceOpenHashSet<Entity>()
|
protected val entities = ReferenceOpenHashSet<Entity>()
|
||||||
|
@ -29,9 +29,9 @@ private fun circulate(value: Int, bounds: Int): Int {
|
|||||||
data class ChunkPos(val x: Int, val y: Int) : IStruct2i, Comparable<ChunkPos> {
|
data class ChunkPos(val x: Int, val y: Int) : IStruct2i, Comparable<ChunkPos> {
|
||||||
constructor(pos: IStruct2i) : this(pos.component1(), pos.component2())
|
constructor(pos: IStruct2i) : this(pos.component1(), pos.component2())
|
||||||
|
|
||||||
fun tile(width: Int, height: Int): Vector2i {
|
val tileX = x shl CHUNK_SIZE_BITS
|
||||||
return Vector2i(x * width, y * height)
|
val tileY = y shl CHUNK_SIZE_BITS
|
||||||
}
|
val tile = Vector2i(tileX, tileY)
|
||||||
|
|
||||||
val top: ChunkPos get() {
|
val top: ChunkPos get() {
|
||||||
return ChunkPos(x, y + 1)
|
return ChunkPos(x, y + 1)
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package ru.dbotthepony.kstarbound.world
|
package ru.dbotthepony.kstarbound.world
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.math.divideUp
|
||||||
|
|
||||||
abstract class CoordinateMapper {
|
abstract class CoordinateMapper {
|
||||||
protected fun positiveModulo(a: Int, b: Int): Int {
|
protected fun positiveModulo(a: Int, b: Int): Int {
|
||||||
val result = a % b
|
val result = a % b
|
||||||
@ -16,16 +18,16 @@ abstract class CoordinateMapper {
|
|||||||
return if (result < 0f) result + b else result
|
return if (result < 0f) result + b else result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract val chunks: Int
|
||||||
|
|
||||||
abstract fun cell(value: Int): Int
|
abstract fun cell(value: Int): Int
|
||||||
abstract fun cell(value: Double): Double
|
abstract fun cell(value: Double): Double
|
||||||
abstract fun cell(value: Float): Float
|
abstract fun cell(value: Float): Float
|
||||||
abstract fun chunk(value: Int): Int
|
abstract fun chunk(value: Int): Int
|
||||||
|
|
||||||
abstract fun cellToChunk(value: Int): Int
|
abstract fun chunkFromCell(value: Int): Int
|
||||||
fun cellToChunk(value: Float): Int = cellToChunk(value.toInt())
|
fun chunkFromCell(value: Float): Int = chunkFromCell(value.toInt())
|
||||||
fun cellToChunk(value: Double): Int = cellToChunk(value.toInt())
|
fun chunkFromCell(value: Double): Int = chunkFromCell(value.toInt())
|
||||||
|
|
||||||
abstract fun cellModulus(value: Int): Int
|
|
||||||
|
|
||||||
// inside world bounds
|
// inside world bounds
|
||||||
abstract fun inBoundsCell(value: Int): Boolean
|
abstract fun inBoundsCell(value: Int): Boolean
|
||||||
@ -36,25 +38,24 @@ abstract class CoordinateMapper {
|
|||||||
open fun isValidChunkIndex(value: Int): Boolean = inBoundsChunk(value)
|
open fun isValidChunkIndex(value: Int): Boolean = inBoundsChunk(value)
|
||||||
|
|
||||||
object Infinite : CoordinateMapper() {
|
object Infinite : CoordinateMapper() {
|
||||||
|
override val chunks: Int
|
||||||
|
get() = Int.MAX_VALUE
|
||||||
|
|
||||||
override fun cell(value: Int): Int = value
|
override fun cell(value: Int): Int = value
|
||||||
override fun cell(value: Double): Double = value
|
override fun cell(value: Double): Double = value
|
||||||
override fun cell(value: Float): Float = value
|
override fun cell(value: Float): Float = value
|
||||||
override fun chunk(value: Int): Int = value
|
override fun chunk(value: Int): Int = value
|
||||||
|
|
||||||
override fun cellToChunk(value: Int): Int {
|
override fun chunkFromCell(value: Int): Int {
|
||||||
return value shr CHUNK_SIZE_BITS
|
return value shr CHUNK_SIZE_BITS
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cellModulus(value: Int): Int {
|
|
||||||
return value and CHUNK_SIZE_MASK
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun inBoundsCell(value: Int) = true
|
override fun inBoundsCell(value: Int) = true
|
||||||
override fun inBoundsChunk(value: Int) = true
|
override fun inBoundsChunk(value: Int) = true
|
||||||
}
|
}
|
||||||
|
|
||||||
class Wrapper(private val cells: Int, private val chunkSize: Int) : CoordinateMapper() {
|
class Wrapper(private val cells: Int) : CoordinateMapper() {
|
||||||
private val chunks = cells / chunkSize
|
override val chunks = divideUp(cells, CHUNK_SIZE)
|
||||||
|
|
||||||
override fun inBoundsCell(value: Int) = value in 0 until cells
|
override fun inBoundsCell(value: Int) = value in 0 until cells
|
||||||
override fun inBoundsChunk(value: Int) = value in 0 until chunks
|
override fun inBoundsChunk(value: Int) = value in 0 until chunks
|
||||||
@ -62,12 +63,8 @@ abstract class CoordinateMapper {
|
|||||||
override fun isValidCellIndex(value: Int) = true
|
override fun isValidCellIndex(value: Int) = true
|
||||||
override fun isValidChunkIndex(value: Int) = true
|
override fun isValidChunkIndex(value: Int) = true
|
||||||
|
|
||||||
override fun cellToChunk(value: Int): Int {
|
override fun chunkFromCell(value: Int): Int {
|
||||||
return chunk(value / chunkSize)
|
return chunk(value shr CHUNK_SIZE_BITS)
|
||||||
}
|
|
||||||
|
|
||||||
override fun cellModulus(value: Int): Int {
|
|
||||||
return positiveModulo(value, chunkSize)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cell(value: Int): Int = positiveModulo(value, cells)
|
override fun cell(value: Int): Int = positiveModulo(value, cells)
|
||||||
@ -76,19 +73,15 @@ abstract class CoordinateMapper {
|
|||||||
override fun chunk(value: Int): Int = positiveModulo(value, chunks)
|
override fun chunk(value: Int): Int = positiveModulo(value, chunks)
|
||||||
}
|
}
|
||||||
|
|
||||||
class Clamper(private val cells: Int, private val chunkSize: Int) : CoordinateMapper() {
|
class Clamper(private val cells: Int) : CoordinateMapper() {
|
||||||
private val chunks = cells / chunkSize
|
override val chunks = divideUp(cells, CHUNK_SIZE)
|
||||||
|
|
||||||
override fun inBoundsCell(value: Int): Boolean {
|
override fun inBoundsCell(value: Int): Boolean {
|
||||||
return value in 0 until cells
|
return value in 0 until cells
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun cellModulus(value: Int): Int {
|
override fun chunkFromCell(value: Int): Int {
|
||||||
return positiveModulo(value, chunkSize)
|
return chunk(value shr CHUNK_SIZE_BITS)
|
||||||
}
|
|
||||||
|
|
||||||
override fun cellToChunk(value: Int): Int {
|
|
||||||
return chunk(value / chunkSize)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun inBoundsChunk(value: Int): Boolean {
|
override fun inBoundsChunk(value: Int): Boolean {
|
||||||
|
@ -22,7 +22,6 @@ 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.arrays.Object2DArray
|
||||||
import ru.dbotthepony.kvector.util2d.AABB
|
import ru.dbotthepony.kvector.util2d.AABB
|
||||||
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.lang.ref.ReferenceQueue
|
import java.lang.ref.ReferenceQueue
|
||||||
@ -34,69 +33,55 @@ 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_SIZEd = CHUNK_SIZE.toDouble()
|
const val CHUNK_SIZEd = CHUNK_SIZE.toDouble()
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
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 size: Vector2i?,
|
val size: Vector2i?,
|
||||||
val loopX: Boolean,
|
loopX: Boolean,
|
||||||
val loopY: Boolean
|
loopY: Boolean
|
||||||
) {
|
) : ICellAccess {
|
||||||
private fun determineChunkSize(cells: Int): Int {
|
val x: CoordinateMapper = if (size == null) CoordinateMapper.Infinite else if (loopX) CoordinateMapper.Wrapper(size.x) else CoordinateMapper.Clamper(size.x)
|
||||||
for (i in 32 downTo 1) {
|
val y: CoordinateMapper = if (size == null) CoordinateMapper.Infinite else if (loopY) CoordinateMapper.Wrapper(size.y) else CoordinateMapper.Clamper(size.y)
|
||||||
if (cells % i == 0) {
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw RuntimeException("unreachable code")
|
// whenever provided cell position is within actual world borders, ignoring wrapping
|
||||||
|
fun inBounds(x: Int, y: Int) = this.x.inBoundsCell(x) && this.y.inBoundsCell(y)
|
||||||
|
fun inBounds(value: IStruct2i) = this.x.inBoundsCell(value.component1()) && this.y.inBoundsCell(value.component2())
|
||||||
|
|
||||||
|
fun chunkFromCell(x: Int, y: Int) = ChunkPos(this.x.chunkFromCell(x), this.y.chunkFromCell(y))
|
||||||
|
fun chunkFromCell(x: Double, y: Double) = ChunkPos(this.x.chunkFromCell(x.toInt()), this.y.chunkFromCell(y.toInt()))
|
||||||
|
fun chunkFromCell(value: IStruct2i) = chunkFromCell(value.component1(), value.component2())
|
||||||
|
fun chunkFromCell(value: IStruct2d) = chunkFromCell(value.component1(), value.component2())
|
||||||
|
|
||||||
|
val background = TileView.Background(this)
|
||||||
|
val foreground = TileView.Foreground(this)
|
||||||
|
|
||||||
|
final override fun randomLongFor(x: Int, y: Int) = super.randomLongFor(x, y) xor seed
|
||||||
|
|
||||||
|
override fun getCellDirect(x: Int, y: Int): IChunkCell? {
|
||||||
|
if (!this.x.inBoundsCell(x) || !this.y.inBoundsCell(y)) return null
|
||||||
|
return getCell(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
val chunkWidth = size?.x?.let(::determineChunkSize) ?: CHUNK_SIZE
|
override fun getCell(x: Int, y: Int): IChunkCell? {
|
||||||
val chunkHeight = size?.y?.let(::determineChunkSize) ?: CHUNK_SIZE
|
return chunkMap.getCell(x, y)
|
||||||
|
}
|
||||||
abstract inner class ChunkMap : ICellAccess {
|
|
||||||
abstract val x: CoordinateMapper
|
|
||||||
abstract val y: CoordinateMapper
|
|
||||||
|
|
||||||
val background = TileView.Background(this)
|
|
||||||
val foreground = TileView.Foreground(this)
|
|
||||||
|
|
||||||
fun inBounds(x: Int, y: Int) = this.x.inBoundsCell(x) && this.y.inBoundsCell(y)
|
|
||||||
fun inBounds(value: IStruct2i) = this.x.inBoundsCell(value.component1()) && this.y.inBoundsCell(value.component2())
|
|
||||||
|
|
||||||
fun cellToChunk(x: Int, y: Int) = ChunkPos(this.x.cellToChunk(x), this.y.cellToChunk(y))
|
|
||||||
fun cellToChunk(x: Double, y: Double) = ChunkPos(this.x.cellToChunk(x.toInt()), this.y.cellToChunk(y.toInt()))
|
|
||||||
fun cellToChunk(value: IStruct2i) = cellToChunk(value.component1(), value.component2())
|
|
||||||
fun cellToChunk(value: IStruct2d) = cellToChunk(value.component1(), value.component2())
|
|
||||||
|
|
||||||
final override fun randomLongFor(x: Int, y: Int) = super.randomLongFor(x, y) xor seed
|
|
||||||
|
|
||||||
|
abstract inner class ChunkMap {
|
||||||
abstract operator fun get(x: Int, y: Int): ChunkType?
|
abstract operator fun get(x: Int, y: Int): ChunkType?
|
||||||
operator fun get(pos: ChunkPos) = get(pos.x, pos.y)
|
|
||||||
|
|
||||||
abstract fun promote(self: ChunkType)
|
abstract fun promote(self: ChunkType)
|
||||||
|
abstract fun purge()
|
||||||
|
abstract fun remove(x: Int, y: Int)
|
||||||
|
|
||||||
|
abstract fun getCell(x: Int, y: Int): IChunkCell?
|
||||||
|
|
||||||
protected val queue = ReferenceQueue<ChunkType>()
|
protected val queue = ReferenceQueue<ChunkType>()
|
||||||
|
operator fun get(pos: ChunkPos) = get(pos.x, pos.y)
|
||||||
abstract fun purge()
|
|
||||||
|
|
||||||
protected inner class Ref(chunk: ChunkType) : WeakReference<ChunkType>(chunk, queue) {
|
protected inner class Ref(chunk: ChunkType) : WeakReference<ChunkType>(chunk, queue) {
|
||||||
val pos = chunk.pos
|
val pos = chunk.pos
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun remove(x: Int, y: Int)
|
|
||||||
|
|
||||||
override fun getCell(x: Int, y: Int): IChunkCell? {
|
|
||||||
if (!this.x.isValidCellIndex(x) || !this.y.isValidCellIndex(y)) return null
|
|
||||||
val ix = this.x.cell(x)
|
|
||||||
val iy = this.y.cell(y)
|
|
||||||
return get(this.x.cellToChunk(ix), this.y.cellToChunk(iy))?.getCell(this.x.cellModulus(ix), this.y.cellModulus(iy))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getCellDirect(x: Int, y: Int): IChunkCell? {
|
|
||||||
if (!this.x.inBoundsCell(x) || !this.y.inBoundsCell(y)) return null
|
|
||||||
return getCell(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun create(x: Int, y: Int): ChunkType {
|
protected fun create(x: Int, y: Int): ChunkType {
|
||||||
purge()
|
purge()
|
||||||
val pos = ChunkPos(x, y)
|
val pos = ChunkPos(x, y)
|
||||||
@ -106,7 +91,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
for (ent in orphanedEntities) {
|
for (ent in orphanedEntities) {
|
||||||
val (ex, ey) = ent.position
|
val (ex, ey) = ent.position
|
||||||
|
|
||||||
if (this.x.cellToChunk(ex) == x && this.y.cellToChunk(ey) == y) {
|
if (this@World.x.chunkFromCell(ex) == x && this@World.y.chunkFromCell(ey) == y) {
|
||||||
orphanedInThisChunk.add(ent)
|
orphanedInThisChunk.add(ent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,33 +104,41 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// infinite chunk map is around 30% slower than rectangular one
|
// hash chunk map is around 30% slower than rectangular one
|
||||||
inner class InfiniteChunkMap : ChunkMap() {
|
inner class HashChunkMap : ChunkMap() {
|
||||||
private val map = Long2ObjectOpenHashMap<Any>()
|
private val map = Long2ObjectOpenHashMap<Any>()
|
||||||
override val x = CoordinateMapper.Infinite
|
|
||||||
override val y = CoordinateMapper.Infinite
|
|
||||||
|
|
||||||
override fun get(x: Int, y: Int): ChunkType {
|
override fun getCell(x: Int, y: Int): IChunkCell? {
|
||||||
|
if (!this@World.x.isValidCellIndex(x) || !this@World.y.isValidCellIndex(y)) return null
|
||||||
|
val ix = this@World.x.cell(x)
|
||||||
|
val iy = this@World.y.cell(y)
|
||||||
|
return this[this@World.x.chunkFromCell(ix), this@World.y.chunkFromCell(iy)]?.getCell(ix and CHUNK_SIZE_MASK, iy and CHUNK_SIZE_MASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(x: Int, y: Int): ChunkType? {
|
||||||
|
if (!this@World.x.isValidChunkIndex(x) || !this@World.y.isValidChunkIndex(y)) return null
|
||||||
|
|
||||||
|
val x = this@World.x.chunk(x)
|
||||||
|
val y = this@World.y.chunk(y)
|
||||||
|
|
||||||
return map[ChunkPos.toLong(x, y)]?.let {
|
return map[ChunkPos.toLong(x, y)]?.let {
|
||||||
if (it is World<*, *>.ChunkMap.Ref) {
|
if (it is World<*, *>.ChunkMap.Ref) {
|
||||||
it.get() as ChunkType?
|
it.get() as ChunkType?
|
||||||
} else {
|
} else {
|
||||||
it as ChunkType?
|
it as ChunkType?
|
||||||
}
|
}
|
||||||
} ?: create(x, y).also {
|
} ?: create(x, y).also { map[ChunkPos.toLong(x, y)] = Ref(it) }
|
||||||
map[ChunkPos.toLong(x, y)] = Ref(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun purge() {
|
override fun purge() {
|
||||||
var next = queue.poll() as World<*, *>.ChunkMap.Ref?
|
var next = queue.poll() as World<*, *>.ChunkMap.Ref?
|
||||||
|
|
||||||
while (next != null) {
|
while (next != null) {
|
||||||
val get = map[ChunkPos.toLong(next.pos.x, next.pos.y)]
|
val k = ChunkPos.toLong(next.pos.x, next.pos.y)
|
||||||
|
val get = map[k]
|
||||||
|
|
||||||
if (get === next) {
|
if (get === next)
|
||||||
map.remove(ChunkPos.toLong(next.pos.x, next.pos.y))
|
map.remove(k)
|
||||||
}
|
|
||||||
|
|
||||||
next = queue.poll() as World<*, *>.ChunkMap.Ref?
|
next = queue.poll() as World<*, *>.ChunkMap.Ref?
|
||||||
}
|
}
|
||||||
@ -167,6 +160,9 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun remove(x: Int, y: Int) {
|
override fun remove(x: Int, y: Int) {
|
||||||
|
val x = this@World.x.chunk(x)
|
||||||
|
val y = this@World.y.chunk(y)
|
||||||
|
|
||||||
val ref = map.remove(ChunkPos.toLong(x, y))
|
val ref = map.remove(ChunkPos.toLong(x, y))
|
||||||
|
|
||||||
if (ref is World<*, *>.ChunkMap.Ref) {
|
if (ref is World<*, *>.ChunkMap.Ref) {
|
||||||
@ -175,38 +171,44 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class RectChunkMap : ChunkMap() {
|
inner class ArrayChunkMap : ChunkMap() {
|
||||||
val width = size!!.x
|
val width = size!!.x
|
||||||
val height = size!!.y
|
val height = size!!.y
|
||||||
|
|
||||||
override val x: CoordinateMapper = if (loopX) CoordinateMapper.Wrapper(width, chunkWidth) else CoordinateMapper.Clamper(width, chunkWidth)
|
private val map = Object2DArray.nulls<Any>(divideUp(width, CHUNK_SIZE), divideUp(height, CHUNK_SIZE))
|
||||||
override val y: CoordinateMapper = if (loopY) CoordinateMapper.Wrapper(height, chunkHeight) else CoordinateMapper.Clamper(height, chunkHeight)
|
|
||||||
|
|
||||||
private val map = Object2DArray.nulls<Any>(width / chunkWidth, height / chunkHeight)
|
private fun getRaw(x: Int, y: Int): ChunkType {
|
||||||
|
return map[x, y]?.let {
|
||||||
override fun get(x: Int, y: Int): ChunkType? {
|
|
||||||
if (!this.x.isValidChunkIndex(x) || !this.y.isValidChunkIndex(y)) return null
|
|
||||||
val ix = this.x.chunk(x)
|
|
||||||
val iy = this.x.chunk(y)
|
|
||||||
|
|
||||||
return map[ix, iy]?.let {
|
|
||||||
if (it is World<*, *>.ChunkMap.Ref) {
|
if (it is World<*, *>.ChunkMap.Ref) {
|
||||||
it.get() as ChunkType?
|
it.get() as ChunkType?
|
||||||
} else {
|
} else {
|
||||||
it as ChunkType?
|
it as ChunkType?
|
||||||
}
|
}
|
||||||
} ?: create(ix, iy).also {
|
} ?: create(x, y).also {
|
||||||
map[ix, iy] = Ref(it)
|
map[x, y] = Ref(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getCell(x: Int, y: Int): IChunkCell? {
|
||||||
|
if (!this@World.x.isValidCellIndex(x) || !this@World.y.isValidCellIndex(y)) return null
|
||||||
|
|
||||||
|
val ix = this@World.x.cell(x)
|
||||||
|
val iy = this@World.y.cell(y)
|
||||||
|
|
||||||
|
return getRaw(ix ushr CHUNK_SIZE_BITS, iy ushr CHUNK_SIZE_BITS).getCell(ix and CHUNK_SIZE_MASK, iy and CHUNK_SIZE_MASK)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun get(x: Int, y: Int): ChunkType? {
|
||||||
|
if (!this@World.x.isValidChunkIndex(x) || !this@World.y.isValidChunkIndex(y)) return null
|
||||||
|
return getRaw(this@World.x.chunk(x), this@World.y.chunk(y))
|
||||||
|
}
|
||||||
|
|
||||||
override fun purge() {
|
override fun purge() {
|
||||||
while (queue.poll() != null) {}
|
while (queue.poll() != null) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun promote(self: ChunkType) {
|
override fun promote(self: ChunkType) {
|
||||||
val (x, y) = self.pos
|
val (x, y) = self.pos
|
||||||
|
|
||||||
val ref = map[x, y]
|
val ref = map[x, y]
|
||||||
|
|
||||||
if (ref !is World<*, *>.ChunkMap.Ref) {
|
if (ref !is World<*, *>.ChunkMap.Ref) {
|
||||||
@ -221,22 +223,20 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun remove(x: Int, y: Int) {
|
override fun remove(x: Int, y: Int) {
|
||||||
if (!this.x.isValidChunkIndex(x) || !this.y.isValidChunkIndex(y)) return
|
val x = this@World.x.chunk(x)
|
||||||
|
val y = this@World.y.chunk(y)
|
||||||
|
|
||||||
val ix = this.x.chunk(x)
|
val old = map[x, y]
|
||||||
val iy = this.x.chunk(y)
|
|
||||||
|
|
||||||
val old = map[ix, iy]
|
|
||||||
|
|
||||||
if (old is World<*, *>.ChunkMap.Ref) {
|
if (old is World<*, *>.ChunkMap.Ref) {
|
||||||
old.clear()
|
old.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
map[ix, iy] = null
|
map[x, y] = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val chunkMap: ChunkMap = if (size != null) RectChunkMap() else InfiniteChunkMap()
|
val chunkMap: ChunkMap = if (size != null) ArrayChunkMap() else HashChunkMap()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Chunks, which have their collision mesh changed
|
* Chunks, which have their collision mesh changed
|
||||||
@ -410,7 +410,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
|
|
||||||
for (x in tiles.mins.x .. tiles.maxs.x) {
|
for (x in tiles.mins.x .. tiles.maxs.x) {
|
||||||
for (y in tiles.mins.y .. tiles.maxs.y) {
|
for (y in tiles.mins.y .. tiles.maxs.y) {
|
||||||
if (predicate.test(chunkMap.getCell(x, y) ?: continue)) {
|
if (predicate.test(getCell(x, y) ?: continue)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -434,7 +434,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
val tile = chunkMap.getCell(worldPosX, worldPosY)
|
val tile = getCell(worldPosX, worldPosY)
|
||||||
|
|
||||||
var newIntensity: Int
|
var newIntensity: Int
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ abstract class Entity(override val world: World<*, *>) : IEntity {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val chunkPos = world.chunkMap.cellToChunk(position)
|
val chunkPos = world.chunkFromCell(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})")
|
||||||
@ -120,13 +120,13 @@ abstract class Entity(override val world: World<*, *>) : IEntity {
|
|||||||
return
|
return
|
||||||
|
|
||||||
val old = field
|
val old = field
|
||||||
field = Vector2d(world.chunkMap.x.cell(value.x), world.chunkMap.x.cell(value.y))
|
field = Vector2d(world.x.cell(value.x), world.y.cell(value.y))
|
||||||
|
|
||||||
movement.notifyPositionChanged()
|
movement.notifyPositionChanged()
|
||||||
|
|
||||||
if (isSpawned && !isRemoved) {
|
if (isSpawned && !isRemoved) {
|
||||||
val oldChunkPos = world.chunkMap.cellToChunk(old)
|
val oldChunkPos = world.chunkFromCell(old)
|
||||||
val newChunkPos = world.chunkMap.cellToChunk(field)
|
val newChunkPos = world.chunkFromCell(field)
|
||||||
|
|
||||||
if (oldChunkPos != newChunkPos) {
|
if (oldChunkPos != newChunkPos) {
|
||||||
chunk = world.chunkMap[newChunkPos]
|
chunk = world.chunkMap[newChunkPos]
|
||||||
@ -154,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.chunkMap[world.chunkMap.cellToChunk(position)]
|
chunk = world.chunkMap[world.chunkFromCell(position)]
|
||||||
|
|
||||||
if (chunk == null) {
|
if (chunk == null) {
|
||||||
world.orphanedEntities.add(this)
|
world.orphanedEntities.add(this)
|
||||||
|
Loading…
Reference in New Issue
Block a user