Collision detection updates

This commit is contained in:
DBotThePony 2023-11-20 14:18:57 +07:00
parent 1526a1c127
commit 2b94bfd41f
Signed by: DBot
GPG Key ID: DCC23B5715498507
6 changed files with 41 additions and 32 deletions

View File

@ -82,7 +82,7 @@ dependencies {
implementation("net.java.dev.jna:jna:5.13.0") implementation("net.java.dev.jna:jna:5.13.0")
implementation("com.github.jnr:jnr-ffi:2.2.13") implementation("com.github.jnr:jnr-ffi:2.2.13")
implementation("ru.dbotthepony:kvector:2.11.0") implementation("ru.dbotthepony:kvector:2.11.1")
implementation("com.github.ben-manes.caffeine:caffeine:3.1.5") implementation("com.github.ben-manes.caffeine:caffeine:3.1.5")
implementation("org.classdump.luna:luna-all-shaded:0.4.1") implementation("org.classdump.luna:luna-all-shaded:0.4.1")

View File

@ -82,7 +82,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()))

View File

@ -188,29 +188,29 @@ abstract class Entity(val world: World<*, *>) {
if (polies.isEmpty()) break if (polies.isEmpty()) break
val intersects = ArrayList<Poly.Penetration<CollisionPoly>>() val intersects = ArrayList<Pair<Poly.Penetration, CollisionPoly>>()
localHitboxes.forEach { hitbox -> localHitboxes.forEach { hitbox ->
polies.forEach { poly -> hitbox.intersect(poly.poly, poly)?.let { intersects.add(it) } } polies.forEach { poly -> hitbox.intersect(poly.poly)?.let { intersects.add(it to poly) } }
} }
if (intersects.isEmpty()) { if (intersects.isEmpty()) {
break break
} else { } else {
val max = intersects.max() val (max, data) = intersects.maxByOrNull { it.first }!!
// resolve collision // resolve collision
position += max.vector position += max.axis * max.penetration
// collision response // collision response
val response = max.axis * velocity.dot(max.axis * (1.0 + max.data.bounceFactor) * (1.0 + movementParameters.bounceFactor.orElse(0.0))) val response = max.axis * velocity.dot(max.axis * (1.0 + data.bounceFactor) * (1.0 + movementParameters.bounceFactor.orElse(0.0)))
velocity -= response velocity -= response
val gravityDot = world.gravity.unitVector.dot(max.axis) val gravityDot = world.gravity.unitVector.dot(max.axis)
// impulse? // impulse?
velocity += max.data.velocity * gravityDot * dt velocity += data.velocity * gravityDot * dt
// friction // friction
velocity *= 1.0 - gravityDot.absoluteValue * 0.08 velocity *= 1.0 - gravityDot.absoluteValue * 0.08
onTouch(response, max.axis, max.data) onTouch(response, max.axis, data)
} }
} }
} }

View File

@ -9,7 +9,5 @@ enum class CollisionType(val isEmpty: Boolean) {
PLATFORM(false), PLATFORM(false),
DYNAMIC(false), DYNAMIC(false),
SLIPPERY(false), SLIPPERY(false),
BLOCK(false), BLOCK(false);
// stairs made out of blocks
BLOCK_SLOPE(false);
} }

View File

@ -91,7 +91,7 @@ fun getBlockPlatforms(x: Int, y: Int, getter: CollisionTypeGetter, kind: Collisi
target.addBlock(listOf(Vector2d(dx, dy + 1), Vector2d(dx + 1, dy + 1)), kind) target.addBlock(listOf(Vector2d(dx, dy + 1), Vector2d(dx + 1, dy + 1)), kind)
} else if (upLeft && downLeft && !upRight && !downRight) { } else if (upLeft && downLeft && !upRight && !downRight) {
target.addBlock(listOf(Vector2d(dx + 1, dy), Vector2d(dx, dy + 1)), kind) 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 } else if (upRight && downRight && !upLeft) { // Fix: copy-paste typo in original sources
target.addBlock(listOf(Vector2d(dx, dy), Vector2d(dx + 1, dy + 1)), kind) target.addBlock(listOf(Vector2d(dx, dy), Vector2d(dx + 1, dy + 1)), kind)
} else if (upRight && downLeft) { } else if (upRight && downLeft) {
target.addBlock(listOf(Vector2d(dx, dy), Vector2d(dx + 1, dy + 1)), kind) target.addBlock(listOf(Vector2d(dx, dy), Vector2d(dx + 1, dy + 1)), kind)

View File

@ -7,6 +7,7 @@ import com.google.gson.TypeAdapterFactory
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter import com.google.gson.stream.JsonWriter
import it.unimi.dsi.fastutil.objects.ObjectArrayList
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
import org.lwjgl.opengl.GL11.GL_LINES import org.lwjgl.opengl.GL11.GL_LINES
import ru.dbotthepony.kstarbound.client.StarboundClient import ru.dbotthepony.kstarbound.client.StarboundClient
@ -21,22 +22,33 @@ import ru.dbotthepony.kvector.vector.RGBAColor
import ru.dbotthepony.kvector.vector.Vector2d 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>): Pair<ImmutableList<Poly.Edge>, ImmutableList<Vector2d>> {
require(points.size >= 2) { "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>()
for (i in points.indices) { if (points.size == 2) {
val p0 = points[i] // line, to make it one faced, we need to make only one edge
val p1 = points[(i + 1) % points.size]
val (p0, p1) = points
val diff = (p1 - p0).unitVector val diff = (p1 - p0).unitVector
val normal = Vector2d(-diff.y, diff.x) val normal = Vector2d(-diff.y, diff.x)
edges.add(Poly.Edge(p0, p1, normal)) edges.add(Poly.Edge(p0, p1, normal))
} else {
for (i in points.indices) {
val p0 = points[i]
val p1 = points[(i + 1) % points.size]
val diff = (p1 - p0).unitVector
val normal = Vector2d(-diff.y, diff.x)
edges.add(Poly.Edge(p0, p1, normal))
}
} }
return edges.build() return edges.build() to ImmutableList.copyOf(points)
} }
/** /**
@ -45,7 +57,8 @@ private fun calculateEdges(points: List<Vector2d>): ImmutableList<Poly.Edge> {
* If poly shape is not convex behavior of SAT is undefined * If poly shape is not convex behavior of SAT is undefined
*/ */
class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: ImmutableList<Vector2d>) { class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: ImmutableList<Vector2d>) {
constructor(points: List<Vector2d>) : this(calculateEdges(points), ImmutableList.copyOf(points)) private constructor(pair: Pair<ImmutableList<Edge>, ImmutableList<Vector2d>>) : this(pair.first, pair.second)
constructor(points: List<Vector2d>) : this(calculateEdges(points))
constructor(aabb: AABB) : this(listOf(aabb.bottomLeft, aabb.topLeft, aabb.topRight, aabb.bottomRight)) constructor(aabb: AABB) : this(listOf(aabb.bottomLeft, aabb.topLeft, aabb.topRight, aabb.bottomRight))
val aabb = AABB( val aabb = AABB(
@ -71,15 +84,15 @@ class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: Imm
} }
} }
data class Penetration<T>(val axis: Vector2d, val penetration: Double, val data: T) : Comparable<Penetration<*>> { data class Penetration(val axis: Vector2d, val penetration: Double) : Comparable<Penetration> {
val vector = axis * penetration val vector = axis * penetration
override fun compareTo(other: Penetration<*>): Int { override fun compareTo(other: Penetration): Int {
return penetration.absoluteValue.compareTo(other.penetration.absoluteValue) return penetration.absoluteValue.compareTo(other.penetration.absoluteValue)
} }
} }
operator fun plus(value: Penetration<*>): Poly { operator fun plus(value: Penetration): Poly {
val vertices = ImmutableList.Builder<Vector2d>() val vertices = ImmutableList.Builder<Vector2d>()
val edges = ImmutableList.Builder<Edge>() val edges = ImmutableList.Builder<Edge>()
@ -144,7 +157,7 @@ class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: Imm
return Vector2d(min, max) return Vector2d(min, max)
} }
fun <T> intersect(other: Poly, data: T): Penetration<T>? { fun intersect(other: Poly): Penetration? {
if (!aabb.intersectWeak(other.aabb)) if (!aabb.intersectWeak(other.aabb))
return null return null
@ -152,7 +165,7 @@ class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: Imm
edges.forEach { normals.add(it.normal) } edges.forEach { normals.add(it.normal) }
other.edges.forEach { normals.add(it.normal) } other.edges.forEach { normals.add(it.normal) }
val intersections = ArrayList<Penetration<T>>() val intersections = ArrayList<Penetration>()
for (normal in normals) { for (normal in normals) {
val projectThis = project(normal) val projectThis = project(normal)
@ -173,10 +186,10 @@ class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: Imm
if (minMin <= maxMax) { if (minMin <= maxMax) {
// push to left // push to left
intersections.add(Penetration(normal, -minMin - width, data)) intersections.add(Penetration(normal, -minMin - width))
} else { } else {
// push to right // push to right
intersections.add(Penetration(normal, maxMax + width, data)) intersections.add(Penetration(normal, maxMax + width))
} }
} else if ( } else if (
projectThis.component1() in projectOther.component1() .. projectOther.component2() && projectThis.component1() in projectOther.component1() .. projectOther.component2() &&
@ -188,17 +201,17 @@ class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: Imm
if (minMin <= maxMax) { if (minMin <= maxMax) {
// push to left // push to left
intersections.add(Penetration(normal, -minMin - width, data)) intersections.add(Penetration(normal, -minMin - width))
} else { } else {
// push to right // push to right
intersections.add(Penetration(normal, maxMax + width, data)) intersections.add(Penetration(normal, maxMax + width))
} }
} else if (projectOther.component1() in projectThis.component1() .. projectThis.component2()) { } else if (projectOther.component1() in projectThis.component1() .. projectThis.component2()) {
// other's min point is within this // other's min point is within this
intersections.add(Penetration(normal, projectOther.component1() - projectThis.component2(), data)) intersections.add(Penetration(normal, projectOther.component1() - projectThis.component2()))
} else { } else {
// other's max point in within this // other's max point in within this
intersections.add(Penetration(normal, projectOther.component2() - projectThis.component1(), data)) intersections.add(Penetration(normal, projectOther.component2() - projectThis.component1()))
} }
if (intersections.last().penetration == 0.0) { if (intersections.last().penetration == 0.0) {
@ -213,8 +226,6 @@ class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: Imm
return intersections.min() return intersections.min()
} }
fun intersect(other: Poly) = intersect(other, Unit)
fun render(client: StarboundClient = StarboundClient.current(), color: RGBAColor = RGBAColor.LIGHT_GREEN) { fun render(client: StarboundClient = StarboundClient.current(), color: RGBAColor = RGBAColor.LIGHT_GREEN) {
val program = client.programs.position val program = client.programs.position
val lines = program.builder.builder val lines = program.builder.builder