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 .. 17) {
// 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 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()
for (i in 0 until 0) {
for (i in 0 until 128) {
val item = ItemEntity(client.world!!, Registries.items.keys.values.random().value)
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>() {
override var entry: Entry<T>? = null
var references = 0
override fun equals(other: Any?): Boolean {
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))
ref.entry = keysInternal[it]
ref
})
}).also {
it.references++
}
}
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))
ref.entry = idsInternal[it]
ref
})
}).also {
it.references++
}
}
operator fun contains(index: String) = lock.withLock { index in keysInternal }
@ -211,14 +216,14 @@ class Registry<T : Any>(val name: String) {
keyRefs.values.forEach {
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
}
}
idRefs.values.forEach {
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
}
}

View File

@ -308,7 +308,7 @@ class Image private constructor(
private val dataCache: AsyncLoadingCache<IStarboundFile, ByteBuffer> = Caffeine.newBuilder()
.expireAfterAccess(Duration.ofMinutes(1))
.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())
.buildAsync(CacheLoader {
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.WorldObject
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.getBlockPlatforms
import ru.dbotthepony.kstarbound.world.physics.getBlocksMarchingSquares
import ru.dbotthepony.kvector.api.IStruct2d
import ru.dbotthepony.kvector.api.IStruct2i
import ru.dbotthepony.kvector.arrays.Object2DArray
import ru.dbotthepony.kvector.util2d.AABB
import ru.dbotthepony.kvector.util2d.AABBi
import ru.dbotthepony.kvector.vector.Vector2d
import ru.dbotthepony.kvector.vector.Vector2i
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() {
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 (y in tiles.mins.y .. tiles.maxs.y) {
val cell = getCell(x, y) ?: continue
result.add(CollisionPoly(
BLOCK_POLY + Vector2d(x.toDouble(), y.toDouble()),
cell.foreground.material.value.collisionKind,
//velocity = Vector2d(EARTH_FREEFALL_ACCELERATION, 0.0)
))
getBlocksMarchingSquares(x, y, foreground, CollisionType.DYNAMIC, result)
getBlockPlatforms(x, y, foreground, CollisionType.PLATFORM, 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
import ru.dbotthepony.kstarbound.world.physics.CollisionType
import ru.dbotthepony.kstarbound.world.physics.CollisionTypeGetter
import ru.dbotthepony.kvector.api.IStruct2i
// for getting tiles directly, avoiding manual layer specification
interface ITileAccess : ICellAccess {
interface ITileAccess : ICellAccess, CollisionTypeGetter {
// 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 getTileDirect(x: Int, y: Int): AbstractTileState?
fun getTileDirect(x: Int, y: Int): AbstractTileState
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 {
class Foreground(parent: ICellAccess) : TileView(parent) {
override fun getTile(x: Int, y: Int): AbstractTileState? {
return getCell(x, y)?.foreground
override fun getTile(x: Int, y: Int): AbstractTileState {
return getCell(x, y).foreground
}
override fun getTileDirect(x: Int, y: Int): AbstractTileState? {
return getCellDirect(x, y)?.foreground
override fun getTileDirect(x: Int, y: Int): AbstractTileState {
return getCellDirect(x, y).foreground
}
}
class Background(parent: ICellAccess) : TileView(parent) {
override fun getTile(x: Int, y: Int): AbstractTileState? {
return getCell(x, y)?.background
override fun getTile(x: Int, y: Int): AbstractTileState {
return getCell(x, y).background
}
override fun getTileDirect(x: Int, y: Int): AbstractTileState? {
return getCellDirect(x, y)?.background
override fun getTileDirect(x: Int, y: Int): AbstractTileState {
return getCellDirect(x, y).background
}
}
}

View File

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