Sparse chunk map instead of infinite chunk map, allowing to create huge worlds, without complexity of "infinite" worlds
This commit is contained in:
parent
93deb62d4b
commit
8afef646ae
@ -78,7 +78,7 @@ fun main() {
|
|||||||
var reader = DataInputStream(BufferedInputStream(InflaterInputStream(ByteArrayInputStream(data), Inflater())))
|
var reader = DataInputStream(BufferedInputStream(InflaterInputStream(ByteArrayInputStream(data), Inflater())))
|
||||||
reader.skipBytes(3)
|
reader.skipBytes(3)
|
||||||
|
|
||||||
val chunk = client.world!!.chunkMap.compute(chunkX - 2, chunkY)
|
val chunk = client.world!!.chunkMap.compute(chunkX, chunkY)
|
||||||
|
|
||||||
if (chunk != null) {
|
if (chunk != null) {
|
||||||
for (y in 0 .. 31) {
|
for (y in 0 .. 31) {
|
||||||
|
@ -32,7 +32,7 @@ import java.util.concurrent.Future
|
|||||||
class ClientWorld(
|
class ClientWorld(
|
||||||
val client: StarboundClient,
|
val client: StarboundClient,
|
||||||
seed: Long,
|
seed: Long,
|
||||||
size: Vector2i? = null,
|
size: Vector2i,
|
||||||
loopX: Boolean = false,
|
loopX: Boolean = false,
|
||||||
loopY: Boolean = false
|
loopY: Boolean = false
|
||||||
) : World<ClientWorld, ClientChunk>(seed, size, loopX, loopY) {
|
) : World<ClientWorld, ClientChunk>(seed, size, loopX, loopY) {
|
||||||
@ -49,13 +49,13 @@ class ClientWorld(
|
|||||||
override val isClient: Boolean
|
override val isClient: Boolean
|
||||||
get() = true
|
get() = true
|
||||||
|
|
||||||
val renderRegionWidth = if (size == null) 16 else determineChunkSize(size.x)
|
val renderRegionWidth = determineChunkSize(size.x)
|
||||||
val renderRegionHeight = if (size == null) 16 else determineChunkSize(size.y)
|
val renderRegionHeight = determineChunkSize(size.y)
|
||||||
val renderRegionsX = if (size == null) 0 else size.x / renderRegionWidth
|
val renderRegionsX = size.x / renderRegionWidth
|
||||||
val renderRegionsY = if (size == null) 0 else size.y / renderRegionHeight
|
val renderRegionsY = size.y / renderRegionHeight
|
||||||
|
|
||||||
fun isValidRenderRegionX(value: Int): Boolean {
|
fun isValidRenderRegionX(value: Int): Boolean {
|
||||||
if (size == null || loopX) {
|
if (loopX) {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return value in 0 .. renderRegionsX
|
return value in 0 .. renderRegionsX
|
||||||
@ -63,7 +63,7 @@ class ClientWorld(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isValidRenderRegionY(value: Int): Boolean {
|
fun isValidRenderRegionY(value: Int): Boolean {
|
||||||
if (size == null || loopY) {
|
if (loopY) {
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return value in 0 .. renderRegionsY
|
return value in 0 .. renderRegionsY
|
||||||
|
@ -37,23 +37,6 @@ abstract class CoordinateMapper {
|
|||||||
open fun isValidCellIndex(value: Int): Boolean = inBoundsCell(value)
|
open fun isValidCellIndex(value: Int): Boolean = inBoundsCell(value)
|
||||||
open fun isValidChunkIndex(value: Int): Boolean = inBoundsChunk(value)
|
open fun isValidChunkIndex(value: Int): Boolean = inBoundsChunk(value)
|
||||||
|
|
||||||
object Infinite : CoordinateMapper() {
|
|
||||||
override val chunks: Int
|
|
||||||
get() = Int.MAX_VALUE
|
|
||||||
|
|
||||||
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 chunkFromCell(value: Int): Int {
|
|
||||||
return value shr CHUNK_SIZE_BITS
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun inBoundsCell(value: Int) = true
|
|
||||||
override fun inBoundsChunk(value: Int) = true
|
|
||||||
}
|
|
||||||
|
|
||||||
class Wrapper(private val cells: Int) : CoordinateMapper() {
|
class Wrapper(private val cells: Int) : CoordinateMapper() {
|
||||||
override val chunks = divideUp(cells, CHUNK_SIZE)
|
override val chunks = divideUp(cells, CHUNK_SIZE)
|
||||||
|
|
||||||
|
@ -26,15 +26,14 @@ import java.util.concurrent.ForkJoinPool
|
|||||||
import java.util.concurrent.locks.ReentrantLock
|
import java.util.concurrent.locks.ReentrantLock
|
||||||
import java.util.random.RandomGenerator
|
import java.util.random.RandomGenerator
|
||||||
|
|
||||||
@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,
|
val loopX: Boolean,
|
||||||
val loopY: Boolean
|
val loopY: Boolean
|
||||||
) : ICellAccess {
|
) : ICellAccess {
|
||||||
val x: CoordinateMapper = if (size == null) CoordinateMapper.Infinite else if (loopX) CoordinateMapper.Wrapper(size.x) else CoordinateMapper.Clamper(size.x)
|
val x: CoordinateMapper = if (loopX) CoordinateMapper.Wrapper(size.x) else CoordinateMapper.Clamper(size.x)
|
||||||
val y: CoordinateMapper = if (size == null) CoordinateMapper.Infinite else if (loopY) CoordinateMapper.Wrapper(size.y) else CoordinateMapper.Clamper(size.y)
|
val y: CoordinateMapper = if (loopY) CoordinateMapper.Wrapper(size.y) else CoordinateMapper.Clamper(size.y)
|
||||||
|
|
||||||
// whenever provided cell position is within actual world borders, ignoring wrapping
|
// 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(x: Int, y: Int) = this.x.inBoundsCell(x) && this.y.inBoundsCell(y)
|
||||||
@ -96,8 +95,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// hash chunk map is around 30% slower than rectangular one
|
inner class SparseChunkMap : ChunkMap() {
|
||||||
inner class HashChunkMap : ChunkMap() {
|
|
||||||
private val map = Long2ObjectOpenHashMap<ChunkType>()
|
private val map = Long2ObjectOpenHashMap<ChunkType>()
|
||||||
|
|
||||||
override fun getCell(x: Int, y: Int): AbstractCell {
|
override fun getCell(x: Int, y: Int): AbstractCell {
|
||||||
@ -107,13 +105,8 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
return this[this@World.x.chunkFromCell(ix), this@World.y.chunkFromCell(iy)]?.getCell(ix and CHUNK_SIZE_MASK, iy and CHUNK_SIZE_MASK) ?: AbstractCell.NULL
|
return this[this@World.x.chunkFromCell(ix), this@World.y.chunkFromCell(iy)]?.getCell(ix and CHUNK_SIZE_MASK, iy and CHUNK_SIZE_MASK) ?: AbstractCell.NULL
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("NAME_SHADOWING")
|
|
||||||
override fun get(x: Int, y: Int): ChunkType? {
|
override fun get(x: Int, y: Int): ChunkType? {
|
||||||
if (!this@World.x.isValidChunkIndex(x) || !this@World.y.isValidChunkIndex(y)) return null
|
if (!this@World.x.inBoundsChunk(x) || !this@World.y.inBoundsChunk(y)) return null
|
||||||
|
|
||||||
val x = this@World.x.chunk(x)
|
|
||||||
val y = this@World.y.chunk(y)
|
|
||||||
|
|
||||||
return map[ChunkPos.toLong(x, y)]
|
return map[ChunkPos.toLong(x, y)]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,10 +130,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
}
|
}
|
||||||
|
|
||||||
inner class ArrayChunkMap : ChunkMap() {
|
inner class ArrayChunkMap : ChunkMap() {
|
||||||
val width = size!!.x
|
private val map = Object2DArray.nulls<ChunkType>(divideUp(size.x, CHUNK_SIZE), divideUp(size.y, CHUNK_SIZE))
|
||||||
val height = size!!.y
|
|
||||||
|
|
||||||
private val map = Object2DArray.nulls<ChunkType>(divideUp(width, CHUNK_SIZE), divideUp(height, CHUNK_SIZE))
|
|
||||||
|
|
||||||
private fun getRaw(x: Int, y: Int): ChunkType {
|
private fun getRaw(x: Int, y: Int): ChunkType {
|
||||||
return map[x, y] ?: create(x, y).also { map[x, y] = it }
|
return map[x, y] ?: create(x, y).also { map[x, y] = it }
|
||||||
@ -166,8 +156,8 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun get(x: Int, y: Int): ChunkType? {
|
override fun get(x: Int, y: Int): ChunkType? {
|
||||||
if (!this@World.x.isValidChunkIndex(x) || !this@World.y.isValidChunkIndex(y)) return null
|
if (!this@World.x.inBoundsChunk(x) || !this@World.y.inBoundsChunk(y)) return null
|
||||||
return getRaw(this@World.x.chunk(x), this@World.y.chunk(y))
|
return getRaw(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun remove(x: Int, y: Int) {
|
override fun remove(x: Int, y: Int) {
|
||||||
@ -175,7 +165,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val chunkMap: ChunkMap = if (size != null) ArrayChunkMap() else HashChunkMap()
|
val chunkMap: ChunkMap = if (size.x <= 32000 && size.y <= 32000) ArrayChunkMap() else SparseChunkMap()
|
||||||
|
|
||||||
val random: RandomGenerator = RandomGenerator.of("Xoroshiro128PlusPlus")
|
val random: RandomGenerator = RandomGenerator.of("Xoroshiro128PlusPlus")
|
||||||
var gravity = Vector2d(0.0, -EARTH_FREEFALL_ACCELERATION)
|
var gravity = Vector2d(0.0, -EARTH_FREEFALL_ACCELERATION)
|
||||||
|
Loading…
Reference in New Issue
Block a user