Original game tile collision geometry

This commit is contained in:
DBotThePony 2023-10-25 22:28:49 +07:00
parent d782b33970
commit 792e0c6ff6
Signed by: DBot
GPG Key ID: DCC23B5715498507
9 changed files with 221 additions and 73 deletions

View File

@ -69,7 +69,7 @@ fun main() {
for (chunkX in 0 .. 100) { for (chunkX in 0 .. 100) {
//for (chunkX in 0 .. 17) { //for (chunkX in 0 .. 17) {
// for (chunkY in 21 .. 21) { // for (chunkY in 21 .. 21) {
for (chunkY in 0 .. 24) { for (chunkY in 18 .. 24) {
val data = db.read(byteArrayOf(1, (chunkX shr 8).toByte(), chunkX.toByte(), (chunkY shr 8).toByte(), chunkY.toByte())) val data = db.read(byteArrayOf(1, (chunkX shr 8).toByte(), chunkX.toByte(), (chunkY shr 8).toByte(), chunkY.toByte()))
val data2 = db.read(byteArrayOf(2, (chunkX shr 8).toByte(), chunkX.toByte(), (chunkY shr 8).toByte(), chunkY.toByte())) val data2 = db.read(byteArrayOf(2, (chunkX shr 8).toByte(), chunkX.toByte(), (chunkY shr 8).toByte(), chunkY.toByte()))
@ -116,7 +116,7 @@ fun main() {
val rand = Random() val rand = Random()
for (i in 0 until 0) { for (i in 0 until 128) {
val item = ItemEntity(client.world!!, Registries.items.keys.values.random().value) val item = ItemEntity(client.world!!, Registries.items.keys.values.random().value)
item.position = Vector2d(225.0 - i, 785.0) item.position = Vector2d(225.0 - i, 785.0)

View File

@ -167,6 +167,7 @@ class Registry<T : Any>(val name: String) {
private inner class RefImpl(override val key: Either<String, Int>) : Ref<T>() { private inner class RefImpl(override val key: Either<String, Int>) : Ref<T>() {
override var entry: Entry<T>? = null override var entry: Entry<T>? = null
var references = 0
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
return this === other || other is Registry<*>.RefImpl && other.key == key && other.registry == registry return this === other || other is Registry<*>.RefImpl && other.key == key && other.registry == registry
@ -192,7 +193,9 @@ class Registry<T : Any>(val name: String) {
val ref = RefImpl(Either.left(it as String)) val ref = RefImpl(Either.left(it as String))
ref.entry = keysInternal[it] ref.entry = keysInternal[it]
ref ref
}) }).also {
it.references++
}
} }
fun ref(index: Int): Ref<T> = lock.withLock { fun ref(index: Int): Ref<T> = lock.withLock {
@ -200,7 +203,9 @@ class Registry<T : Any>(val name: String) {
val ref = RefImpl(Either.right(it)) val ref = RefImpl(Either.right(it))
ref.entry = idsInternal[it] ref.entry = idsInternal[it]
ref ref
}) }).also {
it.references++
}
} }
operator fun contains(index: String) = lock.withLock { index in keysInternal } operator fun contains(index: String) = lock.withLock { index in keysInternal }
@ -211,14 +216,14 @@ class Registry<T : Any>(val name: String) {
keyRefs.values.forEach { keyRefs.values.forEach {
if (!it.isPresent) { if (!it.isPresent) {
LOGGER.warn("Registry '$name' reference at '${it.key.left()}' is not bound to value, expect problems") LOGGER.warn("Registry '$name' reference at '${it.key.left()}' is not bound to value, expect problems (referenced ${it.references} times)")
any = false any = false
} }
} }
idRefs.values.forEach { idRefs.values.forEach {
if (!it.isPresent) { if (!it.isPresent) {
LOGGER.warn("Registry '$name' reference with ID '${it.key.right()}' is not bound to value, expect problems") LOGGER.warn("Registry '$name' reference with ID '${it.key.right()}' is not bound to value, expect problems (referenced ${it.references} times)")
any = false any = false
} }
} }

View File

@ -308,7 +308,7 @@ class Image private constructor(
private val dataCache: AsyncLoadingCache<IStarboundFile, ByteBuffer> = Caffeine.newBuilder() private val dataCache: AsyncLoadingCache<IStarboundFile, ByteBuffer> = Caffeine.newBuilder()
.expireAfterAccess(Duration.ofMinutes(1)) .expireAfterAccess(Duration.ofMinutes(1))
.weigher<IStarboundFile, ByteBuffer> { key, value -> value.capacity() } .weigher<IStarboundFile, ByteBuffer> { key, value -> value.capacity() }
.maximumWeight(1_024L * 1_024L * 256L /* 256 МиБ */) .maximumWeight((Runtime.getRuntime().maxMemory() / 4L).coerceIn(1_024L * 1_024L * 32L /* 32 МиБ */, 1_024L * 1_024L * 256L /* 256 МиБ */))
.scheduler(Scheduler.systemScheduler()) .scheduler(Scheduler.systemScheduler())
.buildAsync(CacheLoader { .buildAsync(CacheLoader {
val getWidth = intArrayOf(0) val getWidth = intArrayOf(0)

View File

@ -15,11 +15,15 @@ import ru.dbotthepony.kstarbound.world.api.TileView
import ru.dbotthepony.kstarbound.world.entities.Entity import ru.dbotthepony.kstarbound.world.entities.Entity
import ru.dbotthepony.kstarbound.world.entities.WorldObject import ru.dbotthepony.kstarbound.world.entities.WorldObject
import ru.dbotthepony.kstarbound.world.physics.CollisionPoly import ru.dbotthepony.kstarbound.world.physics.CollisionPoly
import ru.dbotthepony.kstarbound.world.physics.CollisionType
import ru.dbotthepony.kstarbound.world.physics.Poly import ru.dbotthepony.kstarbound.world.physics.Poly
import ru.dbotthepony.kstarbound.world.physics.getBlockPlatforms
import ru.dbotthepony.kstarbound.world.physics.getBlocksMarchingSquares
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.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.util.concurrent.ForkJoinPool import java.util.concurrent.ForkJoinPool
@ -95,6 +99,7 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
} }
} }
// around 30% slower than ArrayChunkMap, but can support insanely large worlds
inner class SparseChunkMap : ChunkMap() { inner class SparseChunkMap : ChunkMap() {
private val map = Long2ObjectOpenHashMap<ChunkType>() private val map = Long2ObjectOpenHashMap<ChunkType>()
@ -222,62 +227,11 @@ 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) {
val cell = getCell(x, y) ?: continue getBlocksMarchingSquares(x, y, foreground, CollisionType.DYNAMIC, result)
getBlockPlatforms(x, y, foreground, CollisionType.PLATFORM, result)
result.add(CollisionPoly(
BLOCK_POLY + Vector2d(x.toDouble(), y.toDouble()),
cell.foreground.material.value.collisionKind,
//velocity = Vector2d(EARTH_FREEFALL_ACCELERATION, 0.0)
))
} }
} }
return result return result
} }
companion object {
private val POLY_VERTICES: ImmutableList<Vector2d> = ImmutableList.of(
Vector2d(0.5, 0.5),
Vector2d(0.5, 1.0),
Vector2d(0.5, 1.5),
Vector2d(1.0, 1.5),
Vector2d(1.5, 1.5),
Vector2d(1.5, 1.0),
Vector2d(1.5, 0.5),
Vector2d(1.0, 0.5),
Vector2d(1.0, 1.0)
)
private val POLY_INDICES: ImmutableList<IntList> = ImmutableList.of(
IntList.of(9, 9, 9, 9, 9, 9),
IntList.of(1, 2, 3, 9, 9, 9),
IntList.of(3, 4, 5, 9, 9, 9),
IntList.of(1, 2, 4, 5, 9, 9),
IntList.of(7, 5, 6, 9, 9, 9),
IntList.of(1, 2, 3, 5, 6, 7),
IntList.of(7, 3, 4, 6, 9, 9),
IntList.of(1, 2, 4, 6, 7, 9),
IntList.of(0, 1, 7, 9, 9, 9),
IntList.of(0, 2, 3, 7, 9, 9),
IntList.of(0, 1, 3, 4, 5, 7),
IntList.of(0, 2, 4, 5, 7, 9),
IntList.of(0, 1, 5, 6, 9, 9),
IntList.of(0, 2, 3, 5, 6, 9),
IntList.of(0, 1, 3, 4, 6, 9),
IntList.of(0, 2, 4, 6, 9, 9),
// special cases for squared off top corners
IntList.of(5, 6, 7, 8, 9, 9), // top left corner
IntList.of(0, 1, 8, 7, 9, 9), // top right corner
// special cases for hollowed out bottom corners
IntList.of(0, 2, 3, 8, 9, 9), // lower left corner part 1
IntList.of(0, 8, 5, 6, 9, 9), // lower left corner part 2
IntList.of(0, 1, 8, 6, 9, 9), // lower right corner part 1
IntList.of(6, 8, 3, 4, 9, 9) // lower right corner part 2
)
private val BLOCK_POLY = Poly(listOf(Vector2d.ZERO, Vector2d(0.0, 1.0), Vector2d(1.0, 1.0), Vector2d(1.0, 0.0)))
private val PEAK = Poly(listOf(Vector2d(0.0, 0.0), Vector2d(0.5, 1.0), Vector2d(1.0, 0.0)))
private val PX_NY = Poly(listOf(Vector2d(0.0, 0.0), Vector2d(0.0, 1.0), Vector2d(1.0, 0.0)))
private val NX_NY = Poly(listOf(Vector2d(-1.0, 0.0), Vector2d(0.0, 1.0), Vector2d(0.0, 0.0)))
}
} }

View File

@ -1,12 +1,18 @@
package ru.dbotthepony.kstarbound.world.api package ru.dbotthepony.kstarbound.world.api
import ru.dbotthepony.kstarbound.world.physics.CollisionType
import ru.dbotthepony.kstarbound.world.physics.CollisionTypeGetter
import ru.dbotthepony.kvector.api.IStruct2i import ru.dbotthepony.kvector.api.IStruct2i
// for getting tiles directly, avoiding manual layer specification // for getting tiles directly, avoiding manual layer specification
interface ITileAccess : ICellAccess { interface ITileAccess : ICellAccess, CollisionTypeGetter {
// relative // relative
fun getTile(x: Int, y: Int): AbstractTileState? fun getTile(x: Int, y: Int): AbstractTileState
fun getTile(pos: IStruct2i) = getTile(pos.component1(), pos.component2()) fun getTile(pos: IStruct2i) = getTile(pos.component1(), pos.component2())
fun getTileDirect(x: Int, y: Int): AbstractTileState? fun getTileDirect(x: Int, y: Int): AbstractTileState
fun getTileDirect(pos: IStruct2i) = getTile(pos.component1(), pos.component2()) fun getTileDirect(pos: IStruct2i) = getTile(pos.component1(), pos.component2())
override fun collisionType(x: Int, y: Int): CollisionType {
return getTile(x, y).material.value.collisionKind
}
} }

View File

@ -2,22 +2,22 @@ package ru.dbotthepony.kstarbound.world.api
sealed class TileView(parent: ICellAccess) : ITileAccess, ICellAccess by parent { sealed class TileView(parent: ICellAccess) : ITileAccess, ICellAccess by parent {
class Foreground(parent: ICellAccess) : TileView(parent) { class Foreground(parent: ICellAccess) : TileView(parent) {
override fun getTile(x: Int, y: Int): AbstractTileState? { override fun getTile(x: Int, y: Int): AbstractTileState {
return getCell(x, y)?.foreground return getCell(x, y).foreground
} }
override fun getTileDirect(x: Int, y: Int): AbstractTileState? { override fun getTileDirect(x: Int, y: Int): AbstractTileState {
return getCellDirect(x, y)?.foreground return getCellDirect(x, y).foreground
} }
} }
class Background(parent: ICellAccess) : TileView(parent) { class Background(parent: ICellAccess) : TileView(parent) {
override fun getTile(x: Int, y: Int): AbstractTileState? { override fun getTile(x: Int, y: Int): AbstractTileState {
return getCell(x, y)?.background return getCell(x, y).background
} }
override fun getTileDirect(x: Int, y: Int): AbstractTileState? { override fun getTileDirect(x: Int, y: Int): AbstractTileState {
return getCellDirect(x, y)?.background return getCellDirect(x, y).background
} }
} }
} }

View File

@ -16,6 +16,7 @@ import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2d import ru.dbotthepony.kvector.vector.Vector2d
import java.util.EnumSet import java.util.EnumSet
import kotlin.concurrent.withLock import kotlin.concurrent.withLock
import kotlin.math.absoluteValue
abstract class Entity(val world: World<*, *>) { abstract class Entity(val world: World<*, *>) {
var chunk: Chunk<*, *>? = null var chunk: Chunk<*, *>? = null
@ -202,7 +203,7 @@ abstract class Entity(val world: World<*, *>) {
// impulse? // impulse?
velocity += max.data.velocity * gravityDot * dt velocity += max.data.velocity * gravityDot * dt
// friction // friction
velocity *= 1.0 - gravityDot * 0.08 velocity *= 1.0 - gravityDot.absoluteValue * 0.08
onTouch(response, max.axis, max.data) onTouch(response, max.axis, max.data)
} }

View File

@ -0,0 +1,182 @@
package ru.dbotthepony.kstarbound.world.physics
import com.google.common.collect.ImmutableList
import it.unimi.dsi.fastutil.ints.IntList
import ru.dbotthepony.kvector.vector.Vector2d
private val POLY_VERTICES: ImmutableList<Vector2d> = ImmutableList.of(
Vector2d(0.5, 0.5),
Vector2d(0.5, 1.0),
Vector2d(0.5, 1.5),
Vector2d(1.0, 1.5),
Vector2d(1.5, 1.5),
Vector2d(1.5, 1.0),
Vector2d(1.5, 0.5),
Vector2d(1.0, 0.5),
Vector2d(1.0, 1.0)
)
private val POLIES = listOf(
IntList.of(1, 2, 3),
IntList.of(3, 4, 5),
IntList.of(1, 2, 4, 5),
IntList.of(7, 5, 6),
IntList.of(1, 2, 3, 5, 6, 7),
IntList.of(7, 3, 4, 6),
IntList.of(1, 2, 4, 6, 7),
IntList.of(0, 1, 7),
IntList.of(0, 2, 3, 7),
IntList.of(0, 1, 3, 4, 5, 7),
IntList.of(0, 2, 4, 5, 7),
IntList.of(0, 1, 5, 6),
IntList.of(0, 2, 3, 5, 6),
IntList.of(0, 1, 3, 4, 6),
IntList.of(0, 2, 4, 6),
// special cases for squared off top corners
IntList.of(5, 6, 7, 8), // top left corner
IntList.of(0, 1, 8, 7), // top right corner
// special cases for hollowed out bottom corners
IntList.of(0, 2, 3, 8), // lower left corner part 1
IntList.of(0, 8, 5, 6), // lower left corner part 2
IntList.of(0, 1, 8, 6), // lower right corner part 1
IntList.of(6, 8, 3, 4) // lower right corner part 2
).map { Poly(it.map { POLY_VERTICES[it] }) }
fun interface CollisionTypeGetter {
fun collisionType(x: Int, y: Int): CollisionType
}
private fun MutableList<CollisionPoly>.addBlock(x: Int, y: Int, getter: CollisionTypeGetter, index: Int) {
val poly = POLIES.getOrNull(index - 1) ?: return
add(CollisionPoly(
poly + Vector2d(x.toDouble(), y.toDouble()),
getter.collisionType(x, y).coerceAtLeast(getter.collisionType(x + 1, y)).coerceAtLeast(getter.collisionType(x + 1, y + 1)).coerceAtLeast(getter.collisionType(x, y + 1))
))
}
private fun MutableList<CollisionPoly>.addBlock(poly: List<Vector2d>, kind: CollisionType) {
add(CollisionPoly(
Poly(poly),
kind
))
}
fun getBlockPlatforms(x: Int, y: Int, getter: CollisionTypeGetter, kind: CollisionType, target: MutableList<CollisionPoly> = arrayListOf()): MutableList<CollisionPoly> {
// This was once simple and elegant and made sense but then I made it
// match the actual platform rendering more closely and now it's a big
// shitty pile of special cases again. RIP.
if (getter.collisionType(x, y) != kind) return target
val right = getter.collisionType(x + 1, y) == kind
val left = getter.collisionType(x - 1, y) == kind
val downRight = getter.collisionType(x + 1, y - 1) == kind && getter.collisionType(x + 1, y) != kind
val downLeft = getter.collisionType(x - 1, y - 1) == kind && getter.collisionType(x - 1, y) != kind
val upRight = getter.collisionType(x + 1, y + 1) == kind && !left && !right
val upLeft = getter.collisionType(x - 1, y + 1) == kind && !left && !right
val above = getter.collisionType(x, y + 1) == kind
val below = getter.collisionType(x, y - 1) == kind
val dx = x.toDouble()
val dy = y.toDouble()
if (downRight && downLeft && upRight && upLeft) {
target.addBlock(listOf(Vector2d(dx, dy), Vector2d(dx + 1, dy + 1)), kind)
target.addBlock(listOf(Vector2d(dx + 1, dy), Vector2d(dx, dy + 1)), kind)
} else if (above && below) {
target.addBlock(listOf(Vector2d(dx, dy + 1), Vector2d(dx + 1, dy + 1)), kind)
} else if (upLeft && downLeft && !upRight && !downRight) {
target.addBlock(listOf(Vector2d(dx + 1, dy), Vector2d(dx, dy + 1)), kind)
} else if (upRight && downRight && !upLeft && !upRight) { // TODO: figure out what original starbound devs meant by this condition
target.addBlock(listOf(Vector2d(dx, dy), Vector2d(dx + 1, dy + 1)), kind)
} else if (upRight && downLeft) {
target.addBlock(listOf(Vector2d(dx, dy), Vector2d(dx + 1, dy + 1)), kind)
// special case block for connecting flat platform above
if (above && getter.collisionType(x + 1, y + 1) == kind)
target.addBlock(listOf(Vector2d(dx + 1, dy + 1), Vector2d(dx + 2, dy + 2)), kind)
} else if (upLeft && downRight) {
target.addBlock(listOf(Vector2d(dx + 1, dy), Vector2d(dx, dy + 1)), kind)
// special case block for connecting flat platform above
if (above && getter.collisionType(x - 1, y + 1) == kind)
target.addBlock(listOf(Vector2d(dx, dy + 1), Vector2d(dx - 1, dy + 2)), kind)
} else if (above && !downRight && !downLeft) {
target.addBlock(listOf(Vector2d(dx, dy + 1), Vector2d(dx + 1, dy + 1)), kind)
} else if (upLeft && !upRight) {
target.addBlock(listOf(Vector2d(dx + 1, dy), Vector2d(dx, dy + 1)), kind)
} else if (upRight && !upLeft) {
target.addBlock(listOf(Vector2d(dx, dy), Vector2d(dx + 1, dy + 1)), kind)
} else if (downRight && (left || !below)) {
target.addBlock(listOf(Vector2d(dx + 1, dy), Vector2d(dx, dy + 1)), kind)
} else if (downLeft && (right || !below)) {
target.addBlock(listOf(Vector2d(dx, dy), Vector2d(dx + 1, dy + 1)), kind)
} else {
target.addBlock(listOf(Vector2d(dx, dy + 1), Vector2d(dx + 1, dy + 1)), kind)
}
return target
}
fun getBlocksMarchingSquares(x: Int, y: Int, getter: CollisionTypeGetter, kind: CollisionType, target: MutableList<CollisionPoly> = arrayListOf()): MutableList<CollisionPoly> {
var neighborMask = 0
if (getter.collisionType(x, y + 1) >= kind) neighborMask = neighborMask or 1
if (getter.collisionType(x + 1, y + 1) >= kind) neighborMask = neighborMask or 2
if (getter.collisionType(x + 1, y) >= kind) neighborMask = neighborMask or 4
if (getter.collisionType(x, y) >= kind) neighborMask = neighborMask or 8
when (neighborMask) {
4 -> {
if (getter.collisionType(x + 2, y) >= kind &&
getter.collisionType(x + 2, y + 1) < kind &&
getter.collisionType(x, y - 1) < kind) {
target.addBlock(x, y, getter, 16)
return target
}
}
8 -> {
if (getter.collisionType(x - 1, y) >= kind &&
getter.collisionType(x - 1, y + 1) < kind &&
getter.collisionType(x + 1, y - 1) < kind) {
target.addBlock(x, y, getter, 17)
return target
}
}
13 -> {
if (getter.collisionType(x, y + 2) >= kind &&
getter.collisionType(x + 1, y + 2) < kind &&
getter.collisionType(x + 2, y) >= kind) {
target.addBlock(x, y, getter, 18)
target.addBlock(x, y, getter, 19)
return target
}
}
14 -> {
if (getter.collisionType(x, y + 2) < kind &&
getter.collisionType(x + 1, y + 2) >= kind &&
getter.collisionType(x - 1, y) >= kind) {
target.addBlock(x, y, getter, 20)
target.addBlock(x, y, getter, 21)
return target
}
}
}
if (neighborMask != 0) {
target.addBlock(x, y, getter, neighborMask)
}
return target
}

View File

@ -22,7 +22,7 @@ import ru.dbotthepony.kvector.vector.Vector2d
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
private fun calculateEdges(points: List<Vector2d>): ImmutableList<Poly.Edge> { private fun calculateEdges(points: List<Vector2d>): ImmutableList<Poly.Edge> {
require(points.size >= 3) { "Provided poly is invalid (only ${points.size} points are defined)" } require(points.size >= 2) { "Provided poly is invalid (only ${points.size} points are defined)" }
val edges = ImmutableList.Builder<Poly.Edge>() val edges = ImmutableList.Builder<Poly.Edge>()