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:
Song Minjae
2016-04-26 01:15:53 +09:00
parent 519ecec774
commit 6ae31f1858
6 changed files with 116 additions and 185 deletions

View File

@@ -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)

View File

@@ -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 ===

View File

@@ -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: