mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-12 03:24:06 +09:00
hitbox displacement rewritten
This commit is contained in:
@@ -10,6 +10,7 @@ import net.torvald.spriteanimation.SpriteAnimation
|
|||||||
import net.torvald.terrarum.worlddrawer.WorldCamera
|
import net.torvald.terrarum.worlddrawer.WorldCamera
|
||||||
import net.torvald.terrarum.blockproperties.Block
|
import net.torvald.terrarum.blockproperties.Block
|
||||||
import net.torvald.terrarum.blockproperties.BlockProp
|
import net.torvald.terrarum.blockproperties.BlockProp
|
||||||
|
import net.torvald.terrarum.gameactors.ai.toInt
|
||||||
import org.dyn4j.geometry.Vector2
|
import org.dyn4j.geometry.Vector2
|
||||||
import org.newdawn.slick.GameContainer
|
import org.newdawn.slick.GameContainer
|
||||||
import org.newdawn.slick.Graphics
|
import org.newdawn.slick.Graphics
|
||||||
@@ -52,14 +53,14 @@ open class ActorWithPhysics(renderOrder: RenderOrder, val immobileBody: Boolean
|
|||||||
* !! external class should not hitbox.set(); use setHitboxDimension() and setPosition()
|
* !! external class should not hitbox.set(); use setHitboxDimension() and setPosition()
|
||||||
*/
|
*/
|
||||||
override val hitbox = Hitbox(0.0, 0.0, 0.0, 0.0) // Hitbox is implemented using Double;
|
override val hitbox = Hitbox(0.0, 0.0, 0.0, 0.0) // Hitbox is implemented using Double;
|
||||||
@Transient val nextHitbox = Hitbox(0.0, 0.0, 0.0, 0.0) // 52 mantissas ought to be enough for anybody...
|
//@Transient val nextHitbox = Hitbox(0.0, 0.0, 0.0, 0.0) // 52 mantissas ought to be enough for anybody...
|
||||||
|
|
||||||
val tilewiseHitbox: Hitbox
|
val tilewiseHitbox: Hitbox
|
||||||
get() = Hitbox.fromTwoPoints(
|
get() = Hitbox.fromTwoPoints(
|
||||||
nextHitbox.posX.div(TILE_SIZE).floor(),
|
hitbox.posX.div(TILE_SIZE).floor(),
|
||||||
nextHitbox.posY.div(TILE_SIZE).floor(),
|
hitbox.posY.div(TILE_SIZE).floor(),
|
||||||
nextHitbox.endPointX.div(TILE_SIZE).floor(),
|
hitbox.endPointX.div(TILE_SIZE).floor(),
|
||||||
nextHitbox.endPointY.div(TILE_SIZE).floor()
|
hitbox.endPointY.div(TILE_SIZE).floor()
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -298,11 +299,11 @@ open class ActorWithPhysics(renderOrder: RenderOrder, val immobileBody: Boolean
|
|||||||
baseHitboxW * scale,
|
baseHitboxW * scale,
|
||||||
baseHitboxH * scale)
|
baseHitboxH * scale)
|
||||||
|
|
||||||
nextHitbox.setFromWidthHeight(
|
/*nextHitbox.setFromWidthHeight(
|
||||||
x - (baseHitboxW / 2 - hitboxTranslateX) * scale,
|
x - (baseHitboxW / 2 - hitboxTranslateX) * scale,
|
||||||
y - (baseHitboxH - hitboxTranslateY) * scale,
|
y - (baseHitboxH - hitboxTranslateY) * scale,
|
||||||
baseHitboxW * scale,
|
baseHitboxW * scale,
|
||||||
baseHitboxH * scale)
|
baseHitboxH * scale)*/
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun translatePosition(dx: Double, dy: Double) {
|
private fun translatePosition(dx: Double, dy: Double) {
|
||||||
@@ -408,7 +409,7 @@ open class ActorWithPhysics(renderOrder: RenderOrder, val immobileBody: Boolean
|
|||||||
// ((comments)) [Label]
|
// ((comments)) [Label]
|
||||||
|
|
||||||
|
|
||||||
displaceByCCD()
|
displaceHitbox()
|
||||||
applyNormalForce()
|
applyNormalForce()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -600,8 +601,10 @@ open class ActorWithPhysics(renderOrder: RenderOrder, val immobileBody: Boolean
|
|||||||
/**
|
/**
|
||||||
* nextHitbox must NOT be altered before this method is called!
|
* nextHitbox must NOT be altered before this method is called!
|
||||||
*/
|
*/
|
||||||
|
private val backtrackSteps = 16 // max allowed velocity = backtrackSteps * TILE_SIZE
|
||||||
|
|
||||||
private fun displaceHitbox() {
|
private fun displaceHitbox() {
|
||||||
// I kinda need these notes when my brain is stress-throttled
|
// I kinda need these notes when I'm not caffeinated
|
||||||
//
|
//
|
||||||
// First of all: Rules
|
// First of all: Rules
|
||||||
// 1. If two sides are touching each other (they share exactly same coord along one axis),
|
// 1. If two sides are touching each other (they share exactly same coord along one axis),
|
||||||
@@ -614,9 +617,234 @@ open class ActorWithPhysics(renderOrder: RenderOrder, val immobileBody: Boolean
|
|||||||
// backtrack (16) steps to determine it is embedded to the wall, or passed through it
|
// backtrack (16) steps to determine it is embedded to the wall, or passed through it
|
||||||
// if (above procedure returns true):
|
// if (above procedure returns true):
|
||||||
// displace "hitbox" using linear interpolation
|
// displace "hitbox" using linear interpolation
|
||||||
|
// else:
|
||||||
|
// hitbox = hitbox moved by the vector
|
||||||
// [END OF SUBROUTINE]
|
// [END OF SUBROUTINE]
|
||||||
|
|
||||||
|
|
||||||
|
fun getBacktrackDelta(counter: Double): Vector2 =
|
||||||
|
moveDelta * (counter) / backtrackSteps.toDouble()
|
||||||
|
|
||||||
|
var embeddedToTerrainCounter = 0.0 //0..16: embedding detected (detected at this-th try)
|
||||||
|
|
||||||
|
// it's rather a _simulator_ rather than _backtracker_, though
|
||||||
|
var simulationHitbox = hitbox.clone()
|
||||||
|
for (i in 0..backtrackSteps - 1) {
|
||||||
|
// check collision
|
||||||
|
if (isColliding(simulationHitbox)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
val backtrackSize = getBacktrackDelta(i.toDouble()) //to avoid fp precision error derived from the Integration
|
||||||
|
simulationHitbox = hitbox.translate(backtrackSize) // make new hitbox every turn to avoid fp precision error
|
||||||
|
embeddedToTerrainCounter += 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// collision detected
|
||||||
|
if (embeddedToTerrainCounter < backtrackSteps) {
|
||||||
|
// linear interpolation kicks in
|
||||||
|
// or block-wise binary search for the collision point
|
||||||
|
// do this along X- and Y-axis and take the lowest
|
||||||
|
|
||||||
|
val HB_START = 1; val HB_END = -1
|
||||||
|
val xDestInTileCoord: Int // may contain placeholder value
|
||||||
|
val yDestInTileCoord: Int // may contain placeholder value
|
||||||
|
val xDestMode: Int? // null means NO OPERATION
|
||||||
|
val yDestMode: Int? // null means NO OPERATION
|
||||||
|
|
||||||
|
// doing whatever for X-axis
|
||||||
|
// determine if we should use LEFT or RIGHT side
|
||||||
|
if (moveDelta.x > 0) { // going RIGHT, use RIGHT side
|
||||||
|
var embedLow = embeddedToTerrainCounter
|
||||||
|
var embedHigh = embeddedToTerrainCounter + 1.0
|
||||||
|
var low = (hitbox.endPointX + getBacktrackDelta(embedLow).x).div(TILE_SIZE).floorInt()
|
||||||
|
var lowY = hitbox.posY + getBacktrackDelta(embedLow).y
|
||||||
|
var high = (hitbox.endPointX + getBacktrackDelta(embedHigh).x).div(TILE_SIZE).floorInt()
|
||||||
|
var highY = hitbox.posY + getBacktrackDelta(embedHigh).y
|
||||||
|
|
||||||
|
while (low <= high) {
|
||||||
|
val mid = (low + high).ushr(1)
|
||||||
|
val midY = (lowY / 2) + (highY / 2) // to eliminate precision loss (which is negligible anyway...)
|
||||||
|
// y pos : middle of (low, high) hbxs
|
||||||
|
val yStart = midY.div(TILE_SIZE).floorInt()
|
||||||
|
val yEnd = (midY + hitbox.height).div(TILE_SIZE).floorInt()
|
||||||
|
|
||||||
|
val isColliding = (yStart..yEnd).map {
|
||||||
|
BlockCodex[world.getTileFromTerrain(mid, it) ?: Block.STONE].isSolid.toInt()
|
||||||
|
}.sum() != 0
|
||||||
|
|
||||||
|
if (isColliding) {
|
||||||
|
high = mid - 1
|
||||||
|
embedHigh = (embedLow / 2) + (embedHigh / 2)
|
||||||
|
highY = hitbox.posY + getBacktrackDelta(embedHigh).y
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
low = mid + 1
|
||||||
|
embedLow = (embedLow / 2) + (embedHigh / 2)
|
||||||
|
lowY = hitbox.posY + getBacktrackDelta(embedLow).y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xDestInTileCoord = minOf(low, high)
|
||||||
|
xDestMode = HB_END
|
||||||
|
}
|
||||||
|
else if (moveDelta.x < 0) { // going LEFT, use LEFT side
|
||||||
|
var embedLow = embeddedToTerrainCounter + 1.0
|
||||||
|
var embedHigh = embeddedToTerrainCounter
|
||||||
|
var low = (hitbox.posX + getBacktrackDelta(embedLow).x).div(TILE_SIZE).floorInt()
|
||||||
|
var lowY = hitbox.posY + getBacktrackDelta(embedLow).y
|
||||||
|
var high = (hitbox.posX + getBacktrackDelta(embedHigh).x).div(TILE_SIZE).floorInt()
|
||||||
|
var highY = hitbox.posY + getBacktrackDelta(embedHigh).y
|
||||||
|
|
||||||
|
while (low <= high) {
|
||||||
|
val mid = (low + high).ushr(1)
|
||||||
|
val midY = (lowY / 2) + (highY / 2) // to eliminate precision loss (which is negligible anyway...)
|
||||||
|
// y pos : middle of (low, high) hbxs
|
||||||
|
val yStart = midY.div(TILE_SIZE).floorInt()
|
||||||
|
val yEnd = (midY + hitbox.height).div(TILE_SIZE).floorInt()
|
||||||
|
|
||||||
|
val isColliding = (yStart..yEnd).map {
|
||||||
|
BlockCodex[world.getTileFromTerrain(mid, it) ?: Block.STONE].isSolid.toInt()
|
||||||
|
}.sum() != 0
|
||||||
|
|
||||||
|
if (!isColliding) {
|
||||||
|
high = mid - 1
|
||||||
|
embedHigh = (embedLow / 2) + (embedHigh / 2)
|
||||||
|
highY = hitbox.posY + getBacktrackDelta(embedHigh).y
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
low = mid + 1
|
||||||
|
embedLow = (embedLow / 2) + (embedHigh / 2)
|
||||||
|
lowY = hitbox.posY + getBacktrackDelta(embedLow).y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xDestInTileCoord = maxOf(low, high)
|
||||||
|
xDestMode = HB_START
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
xDestInTileCoord = 0 // placeholder
|
||||||
|
xDestMode = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// doing whatever for Y-axis
|
||||||
|
// determine if we should use UP or DOWN side
|
||||||
|
if (moveDelta.y > 0) { // going DOWN, use DOWN side
|
||||||
|
var embedLow = embeddedToTerrainCounter
|
||||||
|
var embedHigh = embeddedToTerrainCounter + 1.0
|
||||||
|
var low = (hitbox.endPointY + getBacktrackDelta(embedLow).y).div(TILE_SIZE).floorInt()
|
||||||
|
var lowX = hitbox.posX + getBacktrackDelta(embedLow).x
|
||||||
|
var high = (hitbox.endPointY + getBacktrackDelta(embedHigh).y).div(TILE_SIZE).floorInt()
|
||||||
|
var highX = hitbox.posX + getBacktrackDelta(embedHigh).x
|
||||||
|
|
||||||
|
while (low <= high) {
|
||||||
|
val mid = (low + high).ushr(1)
|
||||||
|
val midX = (lowX / 2) + (highX / 2) // to eliminate precision loss (which is negligible anyway...)
|
||||||
|
// x pos : middle of (low, high) hbxs
|
||||||
|
val xStart = midX.div(TILE_SIZE).floorInt()
|
||||||
|
val xEnd = (midX + hitbox.width).div(TILE_SIZE).floorInt()
|
||||||
|
|
||||||
|
val isColliding = (xStart..xEnd).map {
|
||||||
|
BlockCodex[world.getTileFromTerrain(it, mid) ?: Block.STONE].isSolid.toInt()
|
||||||
|
}.sum() != 0
|
||||||
|
|
||||||
|
if (isColliding) {
|
||||||
|
high = mid - 1
|
||||||
|
embedHigh = (embedLow / 2) + (embedHigh / 2)
|
||||||
|
highX = hitbox.posX + getBacktrackDelta(embedHigh).x
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
low = mid + 1
|
||||||
|
embedLow = (embedLow / 2) + (embedHigh / 2)
|
||||||
|
lowX = hitbox.posX + getBacktrackDelta(embedLow).x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
yDestInTileCoord = minOf(low, high)
|
||||||
|
yDestMode = HB_END
|
||||||
|
}
|
||||||
|
else if (moveDelta.y < 0) { // going UP, use UP side
|
||||||
|
var embedLow = embeddedToTerrainCounter + 1.0
|
||||||
|
var embedHigh = embeddedToTerrainCounter
|
||||||
|
var low = (hitbox.posY + getBacktrackDelta(embedLow).y).div(TILE_SIZE).floorInt()
|
||||||
|
var lowX = hitbox.posX + getBacktrackDelta(embedLow).x
|
||||||
|
var high = (hitbox.posY + getBacktrackDelta(embedHigh).y).div(TILE_SIZE).floorInt()
|
||||||
|
var highX = hitbox.posX + getBacktrackDelta(embedHigh).x
|
||||||
|
|
||||||
|
while (low <= high) {
|
||||||
|
val mid = (low + high).ushr(1)
|
||||||
|
val midX = (lowX / 2) + (highX / 2) // to eliminate precision loss (which is negligible anyway...)
|
||||||
|
// x pos : middle of (low, high) hbxs
|
||||||
|
val xStart = midX.div(TILE_SIZE).floorInt()
|
||||||
|
val xEnd = (midX + hitbox.width).div(TILE_SIZE).floorInt()
|
||||||
|
|
||||||
|
val isColliding = (xStart..xEnd).map {
|
||||||
|
BlockCodex[world.getTileFromTerrain(it, mid) ?: Block.STONE].isSolid.toInt()
|
||||||
|
}.sum() != 0
|
||||||
|
|
||||||
|
if (!isColliding) {
|
||||||
|
high = mid - 1
|
||||||
|
embedHigh = (embedLow / 2) + (embedHigh / 2)
|
||||||
|
highX = hitbox.posX + getBacktrackDelta(embedHigh).x
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
low = mid + 1
|
||||||
|
embedLow = (embedLow / 2) + (embedHigh / 2)
|
||||||
|
lowX = hitbox.posX + getBacktrackDelta(embedLow).x
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
yDestInTileCoord = maxOf(low, high)
|
||||||
|
yDestMode = HB_START
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
yDestInTileCoord = 0 // placeholder
|
||||||
|
yDestMode = null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// displace for axes
|
||||||
|
if (xDestMode != null) {
|
||||||
|
if (xDestMode == HB_START) {
|
||||||
|
hitbox.setFromTwoPoints(
|
||||||
|
xDestInTileCoord.times(TILE_SIZE).toDouble(),
|
||||||
|
hitbox.posY,
|
||||||
|
xDestInTileCoord.times(TILE_SIZE) + hitbox.width,
|
||||||
|
hitbox.endPointY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else if (xDestMode == HB_END) {
|
||||||
|
hitbox.setFromTwoPoints(
|
||||||
|
xDestInTileCoord.times(TILE_SIZE) - hitbox.width,
|
||||||
|
hitbox.posY,
|
||||||
|
xDestInTileCoord.times(TILE_SIZE).toDouble(),
|
||||||
|
hitbox.endPointY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (yDestMode != null) {
|
||||||
|
if (yDestMode == HB_START) {
|
||||||
|
hitbox.setFromTwoPoints(
|
||||||
|
hitbox.posX,
|
||||||
|
yDestInTileCoord.times(TILE_SIZE).toDouble(),
|
||||||
|
hitbox.endPointX,
|
||||||
|
yDestInTileCoord.times(TILE_SIZE) + hitbox.height
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else if (yDestMode == HB_END) {
|
||||||
|
hitbox.setFromTwoPoints(
|
||||||
|
hitbox.posX,
|
||||||
|
yDestInTileCoord.times(TILE_SIZE) - hitbox.height,
|
||||||
|
hitbox.endPointX,
|
||||||
|
yDestInTileCoord.times(TILE_SIZE).toDouble()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // END IF collision detected
|
||||||
|
else {
|
||||||
|
hitbox.translate(moveDelta)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user