mirror of
https://github.com/curioustorvald/Terrarum.git
synced 2026-06-18 14:34:04 +09:00
newton's law-compliant physics simulator for ActorWithBody, which would make implementing fluid resistance simulation easier job. Player's stop will now be processed by this phys simulator.
Former-commit-id: d9767dc54f8eaea7bbb4886fdad5ef38bab4c933 Former-commit-id: 74b7ea76c5d04272474b68760ad3958e715fe551
This commit is contained in:
@@ -106,7 +106,6 @@ open class ActorWithBody constructor() : Actor(), Visible {
|
||||
|
||||
@Transient private val UD_COMPENSATOR_MAX = TSIZE
|
||||
@Transient private val LR_COMPENSATOR_MAX = TSIZE
|
||||
@Transient private val TILE_AUTOCLIMB_RATE = 4
|
||||
|
||||
/**
|
||||
* A constant to make falling faster so that the game is more playable
|
||||
@@ -136,6 +135,8 @@ open class ActorWithBody constructor() : Actor(), Visible {
|
||||
private var posAdjustX = 0
|
||||
private var posAdjustY = 0
|
||||
|
||||
private val BASE_FRICTION = 0.3f
|
||||
|
||||
init {
|
||||
map = Terrarum.game.map
|
||||
}
|
||||
@@ -211,9 +212,6 @@ open class ActorWithBody constructor() : Actor(), Visible {
|
||||
// copy gravitational constant from the map the actor is in
|
||||
gravitation = map.gravitation
|
||||
|
||||
// Auto climb rate. Clamp to TSIZE
|
||||
AUTO_CLIMB_RATE = Math.min(TSIZE / 8 * FastMath.sqrt(scale), TSIZE.toFloat()).toInt()
|
||||
|
||||
// Actors are subject to the gravity and the buoyancy if they are not levitating
|
||||
if (!isNoSubjectToGrav) {
|
||||
applyGravitation()
|
||||
@@ -234,8 +232,10 @@ open class ActorWithBody constructor() : Actor(), Visible {
|
||||
//}
|
||||
//else {
|
||||
// compensate for colliding
|
||||
updateHorizontalPos()
|
||||
updateVerticalPos()
|
||||
updateHorizontalCollision()
|
||||
updateVerticalCollision()
|
||||
|
||||
setHorizontalFriction()
|
||||
//}
|
||||
|
||||
// apply our compensation to actual hitbox
|
||||
@@ -274,7 +274,19 @@ open class ActorWithBody constructor() : Actor(), Visible {
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateVerticalPos() {
|
||||
private fun setHorizontalFriction() {
|
||||
val friction = BASE_FRICTION * (tileFriction / 16f) // ground frction * !@#$!@#$ // default val: 0.3
|
||||
if (veloX < 0) {
|
||||
veloX += friction
|
||||
if (veloX > 0) veloX = 0f
|
||||
}
|
||||
else if (veloX > 0) {
|
||||
veloX -= friction
|
||||
if (veloX < 0) veloX = 0f
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateVerticalCollision() {
|
||||
if (!isNoCollideWorld) {
|
||||
if (veloY >= 0) { // check downward
|
||||
if (isColliding(CONTACT_AREA_BOTTOM)) { // the ground has dug into the body
|
||||
@@ -347,7 +359,7 @@ open class ActorWithBody constructor() : Actor(), Visible {
|
||||
nextHitbox.setPosition(newX, newY)
|
||||
}
|
||||
|
||||
private fun updateHorizontalPos() {
|
||||
private fun updateHorizontalCollision() {
|
||||
if (!isNoCollideWorld) {
|
||||
if (veloX >= 0.5) { // check right
|
||||
if (isColliding(CONTACT_AREA_RIGHT) && !isColliding(CONTACT_AREA_LEFT)) {
|
||||
@@ -555,17 +567,15 @@ open class ActorWithBody constructor() : Actor(), Visible {
|
||||
get() {
|
||||
var friction = 0
|
||||
|
||||
//get highest fluid density
|
||||
val tilePosXStart = (nextHitbox.posX / TSIZE).roundToInt()
|
||||
val tilePosXEnd = (nextHitbox.hitboxEnd.x / TSIZE).roundToInt()
|
||||
val tilePosY = (nextHitbox.pointedY / TSIZE).roundToInt()
|
||||
//get highest friction
|
||||
val tilePosXStart = (hitbox.posX / TSIZE).roundToInt()
|
||||
val tilePosXEnd = (hitbox.hitboxEnd.x / TSIZE).roundToInt()
|
||||
val tilePosY = (hitbox.pointedY.plus(1) / TSIZE).roundToInt()
|
||||
for (x in tilePosXStart..tilePosXEnd) {
|
||||
val tile = map.getTileFromTerrain(x, tilePosY)
|
||||
if (TilePropCodex.getProp(tile).isFluid) {
|
||||
val thisFluidDensity = TilePropCodex.getProp(tile).friction
|
||||
val thisFriction = TilePropCodex.getProp(tile).friction
|
||||
|
||||
if (thisFluidDensity > friction) friction = thisFluidDensity
|
||||
}
|
||||
if (thisFriction > friction) friction = thisFriction
|
||||
}
|
||||
|
||||
return friction
|
||||
@@ -615,13 +625,11 @@ open class ActorWithBody constructor() : Actor(), Visible {
|
||||
private fun updateNextHitboxFromVelo() {
|
||||
|
||||
nextHitbox.set(
|
||||
(hitbox.posX + veloX).round()
|
||||
, (hitbox.posY + veloY).round()
|
||||
, (baseHitboxW * scale).round()
|
||||
, (baseHitboxH * scale).round()
|
||||
(hitbox.posX + veloX)
|
||||
, (hitbox.posY + veloY)
|
||||
, (baseHitboxW * scale)
|
||||
, (baseHitboxH * scale)
|
||||
)
|
||||
/** Full quantisation; wonder what havoc these statements would wreak...
|
||||
*/
|
||||
}
|
||||
|
||||
private fun updateHitboxX() {
|
||||
@@ -712,6 +720,9 @@ open class ActorWithBody constructor() : Actor(), Visible {
|
||||
this.density = density.toFloat()
|
||||
}
|
||||
|
||||
private val AUTO_CLIMB_RATE: Int
|
||||
get() = Math.min(TSIZE / 8 * FastMath.sqrt(scale), TSIZE.toFloat()).toInt()
|
||||
|
||||
fun Float.round() = Math.round(this).toFloat()
|
||||
fun Float.roundToInt(): Int = Math.round(this)
|
||||
fun Float.abs() = FastMath.abs(this)
|
||||
@@ -720,7 +731,6 @@ open class ActorWithBody constructor() : Actor(), Visible {
|
||||
companion object {
|
||||
|
||||
@Transient private val TSIZE = MapDrawer.TILE_SIZE
|
||||
private var AUTO_CLIMB_RATE = TSIZE / 8
|
||||
|
||||
private fun div16TruncateToMapWidth(x: Int): Int {
|
||||
if (x < 0)
|
||||
|
||||
@@ -21,45 +21,93 @@ object CollisionSolver {
|
||||
|
||||
private val collCandidateX = ArrayList<Pair<ActorWithBody, ActorWithBody>>(COLL_CANDIDATES_SIZE)
|
||||
private val collCandidateY = ArrayList<Pair<ActorWithBody, ActorWithBody>>(COLL_CANDIDATES_SIZE)
|
||||
private val collCandidates = ArrayList<Pair<ActorWithBody, ActorWithBody>>(COLL_FINAL_CANDIDATES_SIZE)
|
||||
private var collCandidates = ArrayList<Pair<ActorWithBody, ActorWithBody>>(COLL_FINAL_CANDIDATES_SIZE)
|
||||
|
||||
private val collCandidateStack = Stack<CollisionMarkings>()
|
||||
|
||||
/**
|
||||
* @link https://www.toptal.com/game/video-game-physics-part-ii-collision-detection-for-solid-objects
|
||||
*/
|
||||
fun process() {
|
||||
// clean up before we go
|
||||
collListX.clear()
|
||||
collListY.clear()
|
||||
collCandidateX.clear()
|
||||
collCandidateY.clear()
|
||||
|
||||
// mark list x
|
||||
Terrarum.game.actorContainer.forEach { it ->
|
||||
if (it is ActorWithBody) {
|
||||
collListX.add(CollisionMarkings(it.hitbox.hitboxStart.x, STARTPOINT, it.referenceID))
|
||||
collListX.add(CollisionMarkings(it.hitbox.hitboxEnd.x, ENDPOINT, it.referenceID))
|
||||
collListX.add(CollisionMarkings(it.hitbox.hitboxStart.x, STARTPOINT, it))
|
||||
collListX.add(CollisionMarkings(it.hitbox.hitboxEnd.x, ENDPOINT, it))
|
||||
}
|
||||
}
|
||||
|
||||
// sort list x
|
||||
collListX.sortBy { it.pos }
|
||||
|
||||
// set candidateX
|
||||
for (it in collListX) {
|
||||
if (it.kind == STARTPOINT) {
|
||||
collCandidateStack.push(it)
|
||||
}
|
||||
else if (it.kind == ENDPOINT) {
|
||||
val mark_this = it
|
||||
val mark_other = collCandidateStack.pop()
|
||||
val collCandidate: Pair<ActorWithBody, ActorWithBody>
|
||||
if (mark_this < mark_other) // make sure actor with lower pos comes left
|
||||
collCandidate = Pair(mark_this.actor, mark_other.actor)
|
||||
else
|
||||
collCandidate = Pair(mark_other.actor, mark_this.actor)
|
||||
|
||||
collCandidateX.add(collCandidate)
|
||||
}
|
||||
}
|
||||
collCandidateStack.clear()
|
||||
|
||||
// mark list y
|
||||
Terrarum.game.actorContainer.forEach { it ->
|
||||
if (it is ActorWithBody) {
|
||||
collListY.add(CollisionMarkings(it.hitbox.hitboxStart.y, STARTPOINT, it.referenceID))
|
||||
collListY.add(CollisionMarkings(it.hitbox.hitboxEnd.y, ENDPOINT, it.referenceID))
|
||||
collListY.add(CollisionMarkings(it.hitbox.hitboxStart.y, STARTPOINT, it))
|
||||
collListY.add(CollisionMarkings(it.hitbox.hitboxEnd.y, ENDPOINT, it))
|
||||
}
|
||||
}
|
||||
|
||||
// sort list y
|
||||
collListY.sortBy { it.pos }
|
||||
|
||||
// set candidateY
|
||||
for (it in collListY) {
|
||||
if (it.kind == STARTPOINT) {
|
||||
collCandidateStack.push(it)
|
||||
}
|
||||
else if (it.kind == ENDPOINT) {
|
||||
val mark_this = it
|
||||
val mark_other = collCandidateStack.pop()
|
||||
val collCandidate: Pair<ActorWithBody, ActorWithBody>
|
||||
if (mark_this < mark_other) // make sure actor with lower pos comes left
|
||||
collCandidate = Pair(mark_this.actor, mark_other.actor)
|
||||
else
|
||||
collCandidate = Pair(mark_other.actor, mark_this.actor)
|
||||
|
||||
collCandidateY.add(collCandidate)
|
||||
}
|
||||
}
|
||||
// look for overlaps in candidate X/Y and put them into collCandidates
|
||||
// overlapping in X and Y means they are actually overlapping physically
|
||||
collCandidateY.retainAll(collCandidateX) // list Y will have intersection of X and Y now
|
||||
collCandidates = collCandidateY // renaming. X and Y won't be used anyway.
|
||||
|
||||
// solve collision for actors in collCandidates
|
||||
collCandidates.forEach { solveCollision(it.first, it.second) }
|
||||
}
|
||||
|
||||
private fun solveCollision(a: ActorWithBody, b: ActorWithBody) {
|
||||
// some of the Pair(a, b) are either duplicates or erroneously reported.
|
||||
// e.g. (A, B), (B, C) and then (A, C);
|
||||
// in some situation (A, C) will not making any contact with each other
|
||||
// we are going to filter them
|
||||
if (a isCollidingWith b) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private infix fun ActorWithBody.isCollidingWith(other: ActorWithBody): Boolean {
|
||||
@@ -90,11 +138,16 @@ object CollisionSolver {
|
||||
fun Float.abs() = if (this < 0) -this else this
|
||||
fun Float.sqr() = this * this
|
||||
|
||||
data class CollisionMarkings(
|
||||
class CollisionMarkings(
|
||||
val pos: Float,
|
||||
val kind: Int,
|
||||
val actorID: Int
|
||||
)
|
||||
val actor: ActorWithBody
|
||||
) : Comparable<CollisionMarkings> {
|
||||
override fun compareTo(other: CollisionMarkings): Int =
|
||||
if (this.pos > other.pos) 1
|
||||
else if (this.pos < other.pos) -1
|
||||
else 0
|
||||
}
|
||||
|
||||
/**
|
||||
* === Some useful physics knowledge ===
|
||||
|
||||
@@ -23,7 +23,6 @@ class Player : ActorWithBody, Controllable, Pocketed, Factionable, Luminous, Lan
|
||||
/**
|
||||
* empirical value.
|
||||
*/
|
||||
// private transient final float JUMP_ACCELERATION_MOD = ???f / 10000f; //quadratic mode
|
||||
@Transient private val JUMP_ACCELERATION_MOD = 170f / 10000f //linear mode
|
||||
@Transient private val WALK_FRAMES_TO_MAX_ACCEL = 6
|
||||
|
||||
@@ -75,9 +74,8 @@ class Player : ActorWithBody, Controllable, Pocketed, Factionable, Luminous, Lan
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Transient internal const val ACCEL_MULT_IN_FLIGHT = 0.48f
|
||||
@Transient internal const val WALK_STOP_ACCEL = 0.32f
|
||||
@Transient internal const val WALK_ACCEL_BASE = 0.32f
|
||||
@Transient internal const val ACCEL_MULT_IN_FLIGHT = 0.31f
|
||||
@Transient internal const val WALK_ACCEL_BASE = 0.67f
|
||||
|
||||
@Transient const val PLAYER_REF_ID: Int = 0x51621D
|
||||
@Transient const val BASE_HEIGHT = 40
|
||||
@@ -202,8 +200,9 @@ class Player : ActorWithBody, Controllable, Pocketed, Factionable, Luminous, Lan
|
||||
return 0.5f + 0.5f * -FastMath.cos(10 * x / (WALK_FRAMES_TO_MAX_ACCEL * FastMath.PI))
|
||||
}
|
||||
|
||||
// stops; let the friction kick in by doing nothing to the velocity here
|
||||
private fun walkHStop() {
|
||||
if (veloX > 0) {
|
||||
/*if (veloX > 0) {
|
||||
veloX -= actorValue.getAsFloat(AVKey.ACCEL)!! *
|
||||
actorValue.getAsFloat(AVKey.ACCELMULT)!! *
|
||||
FastMath.sqrt(scale)
|
||||
@@ -219,13 +218,16 @@ class Player : ActorWithBody, Controllable, Pocketed, Factionable, Luminous, Lan
|
||||
if (veloX > 0) veloX = 0f
|
||||
} else {
|
||||
veloX = 0f
|
||||
}
|
||||
}*/
|
||||
|
||||
//veloX = 0f
|
||||
|
||||
walkPowerCounter = 0
|
||||
}
|
||||
|
||||
// stops; let the friction kick in by doing nothing to the velocity here
|
||||
private fun walkVStop() {
|
||||
if (veloY > 0) {
|
||||
/*if (veloY > 0) {
|
||||
veloY -= WALK_STOP_ACCEL *
|
||||
actorValue.getAsFloat(AVKey.ACCELMULT)!! *
|
||||
FastMath.sqrt(scale)
|
||||
@@ -242,7 +244,9 @@ class Player : ActorWithBody, Controllable, Pocketed, Factionable, Luminous, Lan
|
||||
if (veloY > 0) veloY = 0f
|
||||
} else {
|
||||
veloY = 0f
|
||||
}
|
||||
}*/
|
||||
|
||||
///veloY = 0f
|
||||
|
||||
walkPowerCounter = 0
|
||||
}
|
||||
@@ -250,12 +254,12 @@ class Player : ActorWithBody, Controllable, Pocketed, Factionable, Luminous, Lan
|
||||
private fun updateMovementControl() {
|
||||
if (!noClip) {
|
||||
if (grounded) {
|
||||
actorValue.set(AVKey.ACCELMULT, 1f)
|
||||
actorValue[AVKey.ACCELMULT] = 1f
|
||||
} else {
|
||||
actorValue.set(AVKey.ACCELMULT, ACCEL_MULT_IN_FLIGHT)
|
||||
actorValue[AVKey.ACCELMULT] = ACCEL_MULT_IN_FLIGHT
|
||||
}
|
||||
} else {
|
||||
actorValue.set(AVKey.ACCELMULT, 1f)
|
||||
actorValue[AVKey.ACCELMULT] = 1f
|
||||
}
|
||||
}
|
||||
|
||||
@@ -412,8 +416,6 @@ class Player : ActorWithBody, Controllable, Pocketed, Factionable, Luminous, Lan
|
||||
val jumpAcc = pwr * timedJumpCharge * JUMP_ACCELERATION_MOD * FastMath.sqrt(scale)
|
||||
|
||||
veloY -= jumpAcc
|
||||
|
||||
// try concave mode?
|
||||
}
|
||||
|
||||
// for mob ai:
|
||||
|
||||
Reference in New Issue
Block a user