phys is broken again -- at least I'm re-doing the code

This commit is contained in:
minjaesong
2019-01-21 23:21:15 +09:00
parent afba402c6c
commit 1bbead6fd5
10 changed files with 362 additions and 346 deletions

View File

@@ -738,7 +738,6 @@ fun absMax(left: Double, right: Double): Double {
}
}
fun Double.magnSqr() = if (this >= 0.0) this.sqr() else -this.sqr()
fun Double.sign() = if (this > 0.0) 1.0 else if (this < 0.0) -1.0 else 0.0
fun interpolateLinear(scale: Double, startValue: Double, endValue: Double): Double {
if (startValue == endValue) {

View File

@@ -94,13 +94,15 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
true
)
private val nullVec = Vector2(0.0, 0.0)
/**
* TODO Pixels per 1/60 seconds.
*
* When the engine resolves this value, the framerate must be accounted for. E.g.:
* 3.0 is resolved as 3.0 if FPS is 60, but the same value should be resolved as 6.0 if FPS is 30.
* v_resolved = v * (60/FPS) or, v * (60 * delta_t)
* (Use this code verbatim: '(Terrarum.PHYS_REF_FPS * delta)')
* (Use this code verbatim: '(Terrarum.PHYS_REF_FPS * delta)', for easy grep later)
*
*
* Elevators/Movingwalks/etc.: edit hitbox manually!
@@ -110,14 +112,17 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
* veloY += 3.0
* +3.0 is acceleration. You __accumulate__ acceleration to the velocity.
*/
internal val externalForce = Vector2(0.0, 0.0)
internal val externalV = Vector2(0.0, 0.0)
@Transient private val VELO_HARD_LIMIT = 100.0
/**
* for "Controllable" actors
*/
var controllerMoveDelta: Vector2? = if (this is Controllable) Vector2() else null
var controllerMoveV: Vector2? = if (this is Controllable) Vector2() else null
// not sure we need this...
//var jumpable = true // this is kind of like "semaphore"
@@ -218,7 +223,7 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
* meter to pixel : 24/FPS
*/
private val gravitation: Vector2
get() = world?.gravitation ?: Vector2(0.0, 9.8)
get() = world?.gravitation ?: Vector2(0.0, 0.36)
@Transient val DRAG_COEFF_DEFAULT = 1.2
/** Drag coefficient. Parachutes have much higher value than bare body (1.2) */
var dragCoefficient: Double
@@ -348,7 +353,7 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
* @param acc : Acceleration in Vector2
*/
fun applyForce(acc: Vector2) {
externalForce += acc * speedMultByTile
externalV += acc * speedMultByTile
}
private val bounceDampenVelThreshold = 0.5
@@ -381,7 +386,7 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
////////////////////////////////////////////////////////////////
// Codes that modifies velocity (moveDelta and externalForce) //
// Codes that modifies velocity (moveDelta and externalV) //
////////////////////////////////////////////////////////////////
// --> Apply more forces <-- //
@@ -396,8 +401,8 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
}
// hard limit velocity
externalForce.x = externalForce.x.bipolarClamp(VELO_HARD_LIMIT) // displaceHitbox SHOULD use moveDelta
externalForce.y = externalForce.y.bipolarClamp(VELO_HARD_LIMIT)
externalV.x = externalV.x.bipolarClamp(VELO_HARD_LIMIT) // displaceHitbox SHOULD use moveDelta
externalV.y = externalV.y.bipolarClamp(VELO_HARD_LIMIT)
if (!isChronostasis) {
///////////////////////////////////////////////////
@@ -413,7 +418,7 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
displaceHitbox(delta)
}
else {
val vecSum = externalForce + (controllerMoveDelta ?: Vector2(0.0, 0.0))
val vecSum = externalV + (controllerMoveV ?: Vector2(0.0, 0.0))
hitbox.translate(vecSum * (Terrarum.PHYS_REF_FPS * delta))
}
@@ -428,7 +433,7 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
// TODO less friction for non-animating objects (make items glide far more on ice)
// FIXME asymmetry on friction
setHorizontalFriction(delta) // friction SHOULD use and alter externalForce
setHorizontalFriction(delta) // friction SHOULD use and alter externalV
//if (isNoClip) { // TODO also hanging on the rope, etc.
setVerticalFriction(delta)
//}
@@ -470,28 +475,28 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
if (!(isWalled(hitbox, COLLIDING_LEFT) && walkX < 0)
|| !(isWalled(hitbox, COLLIDING_RIGHT) && walkX > 0)
) {
moveDelta.x = externalForce.x + walkX
moveDelta.x = externalV.x + walkX
}
// decide whether to ignore walkY
if (!(isWalled(hitbox, COLLIDING_TOP) && walkY < 0)
|| !(isWalled(hitbox, COLLIDING_BOTTOM) && walkY > 0)
) {
moveDelta.y = externalForce.y + walkY
moveDelta.y = externalV.y + walkY
}
}
else {
if (!isWalled(hitbox, COLLIDING_LEFT)
|| !isWalled(hitbox, COLLIDING_RIGHT)
) {
moveDelta.x = externalForce.x
moveDelta.x = externalV.x
}
// decide whether to ignore walkY
if (!isWalled(hitbox, COLLIDING_TOP)
|| !isWalled(hitbox, COLLIDING_BOTTOM)
) {
moveDelta.y = externalForce.y
moveDelta.y = externalV.y
}
}
}*/
@@ -501,7 +506,7 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
* weight; gravitational force in action
* W = mass * G (9.8 [m/s^2])
*/
val W: Vector2 = gravitation * Terrarum.PHYS_TIME_FRAME.toDouble()
val W: Vector2 = gravitation// * Terrarum.PHYS_TIME_FRAME.toDouble()
/**
* Area
*/
@@ -510,11 +515,12 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
* Drag of atmosphere
* D = Cd (drag coefficient) * 0.5 * rho (density) * V^2 (velocity sqr) * A (area)
*/
val D: Vector2 = Vector2(externalForce.x.magnSqr(), externalForce.y.magnSqr()) * dragCoefficient * 0.5 * A// * tileDensityFluid.toDouble()
val V = externalV + controllerMoveV
val D: Vector2 = V.magnSqr() * dragCoefficient * 0.5 * A// * tileDensityFluid.toDouble()
val V: Vector2 = (W - D) / Terrarum.PHYS_TIME_FRAME * SI_TO_GAME_ACC
val final: Vector2 = (W - D)
return V * (Terrarum.PHYS_REF_FPS * delta).sqrt()
return final
// FIXME v * const, where const = 1.0 for FPS=60, sqrt(2.0) for FPS=30, etc.
// this is "close enough" solution and not perfect.
@@ -538,9 +544,8 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
private fun applyGravitation(delta: Double) {
if (!isNoSubjectToGrav && !(gravitation.y > 0 && walledBottom || gravitation.y < 0 && walledTop)) {
//if (!isWalled(hitbox, COLLIDING_BOTTOM)) {
applyForce(getDrag(delta, externalForce))
//}
//applyForce(getDrag(delta, externalV))
applyForce(gravitation)
}
}
@@ -587,7 +592,7 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
// if not touching:
// do nothing
// [Friction]:
// deform vector "externalForce"
// deform vector "externalV"
// if isControllable:
// also alter walkX/Y
// translate ((nextHitbox)) hitbox by moveDelta (forces), this consumes force
@@ -596,287 +601,293 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
//
// ((comments)) [Label]
if (world != null) {
if (world == null) {
return
}
fun debug1(wut: Any?) {
// vvvvv set it true to make debug print work
if (true) printdbg(this, wut)
fun debug1(wut: Any?) {
// vvvvv set it true to make debug print work
if (true) printdbg(this, wut)
}
fun debug2(wut: Any?) {
// vvvvv set it true to make debug print work
if (false) printdbg(this, wut)
}
fun debug3(wut: Any?) {
// vvvvv set it true to make debug print work
if (false) printdbg(this, wut)
}
fun debug4(wut: Any?) {
// vvvvv set it true to make debug print work
if (false) printdbg(this, wut)
}
fun BlockAddress.isFeetTile(hitbox: Hitbox): Boolean {
val (x, y) = LandUtil.resolveBlockAddr(world!!, this)
val newTilewiseHitbox = Hitbox.fromTwoPoints(
hitbox.startX.div(TILE_SIZE).floor(),
hitbox.startY.div(TILE_SIZE).floor(),
hitbox.endX.minus(0.00001).div(TILE_SIZE).floor(),
hitbox.endY.minus(0.00001).div(TILE_SIZE).floor(),
true
)
// offset 1 pixel to the down so that friction would work
return (y == hitbox.endY.plus(1.0).div(TILE_SIZE).floorInt()) && // copied from forEachFeetTileNum
(x in newTilewiseHitbox.startX.toInt()..newTilewiseHitbox.endX.toInt()) // copied from forEachOccupyingTilePos
}
fun Double.modTile() = this.toInt().div(TILE_SIZE).times(TILE_SIZE)
fun Double.modTileDelta() = this - this.modTile()
// displacement relative to the original position
// s(t) = s_0 + v_0 * t + 0.5at^2
// s(t) = s_0 + 0.5 * (v_0 + v(t)) * t
// v(t) = v_0 + at
// v_0 = myV
val t = (Terrarum.PHYS_REF_FPS * delta)
val displacement = (externalV + (controllerMoveV ?: nullVec)) * t
val ccdSteps = minOf(16, (displacement.magnitudeSquared / TILE_SIZE.sqr()).floorInt() + 1) // adaptive
// * NEW idea: wall pushes the actors (ref. SM64 explained by dutch pancake) *
// direction to push is determined by the velocity
// proc:
// 10 I detect being walled and displace myself
// 11 There's 16 possible case so work all 16 (some can be merged obviously)
// 12 Amount of displacement can be obtained with modTileDelta()
// 13 isWalled() is confirmed to be working
// 20 sixteenStep may be optional, I think, but it'd be good to have
// ignore MOST of the codes below (it might be possible to recycle the structure??)
// and the idea above has not yet implemented, and may never will. --Torvald, 2018-12-30
val sixteenStep = (0..ccdSteps).map { hitbox.clone().translate(displacement * (it / ccdSteps.toDouble())) } // zeroth step is for special condition
var collidingStep: Int? = null
for (step in 1..ccdSteps) {
val stepBox = sixteenStep[step]
forEachOccupyingTilePos(stepBox) {
val tileCoord = LandUtil.resolveBlockAddr(world!!, it)
val tile = world!!.getTileFromTerrain(tileCoord.first, tileCoord.second) ?: Block.STONE
if (shouldICollideWithThis(tile) || (it.isFeetTile(stepBox) && shouldICollideWithThisFeet(tile))) {
collidingStep = step
}
}
fun debug2(wut: Any?) {
// vvvvv set it true to make debug print work
if (false) printdbg(this, wut)
if (collidingStep != null) break
}
val COLL_LEFTSIDE = 1
val COLL_BOTTOMSIDE = 2
val COLL_RIGHTSIDE = 4
val COLL_TOPSIDE = 8
var bounceX = false
var bounceY = false
var zeroX = false
var zeroY = false
// collision NOT detected
if (collidingStep == null) {
hitbox.translate(displacement)
// grounded = false
}
// collision detected
else {
debug1("== Collision step: $collidingStep / $ccdSteps")
val newHitbox = hitbox.reassign(sixteenStep[collidingStep!!])
var selfCollisionStatus = 0
if (isWalled(newHitbox, COLLIDING_LEFT)) selfCollisionStatus += COLL_LEFTSIDE // 1
if (isWalled(newHitbox, COLLIDING_RIGHT)) selfCollisionStatus += COLL_RIGHTSIDE // 4
if (isWalled(newHitbox, COLLIDING_TOP)) selfCollisionStatus += COLL_TOPSIDE // 8
if (isWalled(newHitbox, COLLIDING_BOTTOM)) selfCollisionStatus += COLL_BOTTOMSIDE // 2
// fixme UP and RIGHT && LEFT and DOWN bug
when (selfCollisionStatus) {
0 -> {
debug1("[ActorWBMovable] Contradiction -- collision detected by CCD, but isWalled() says otherwise")
}
5 -> {
zeroX = true
}
10 -> {
zeroY = true
}
15 -> {
newHitbox.reassign(sixteenStep[0]); zeroX = true; zeroY = true
}
// one-side collision
1, 11 -> {
newHitbox.translatePosX(TILE_SIZE - newHitbox.startX.modTileDelta()); bounceX = true
}
4, 14 -> {
newHitbox.translatePosX(-newHitbox.endX.modTileDelta()); bounceX = true
}
8, 13 -> {
newHitbox.translatePosY(TILE_SIZE - newHitbox.startY.modTileDelta()); bounceY = true
}
2, 7 -> {
newHitbox.translatePosY(-newHitbox.endY.modTileDelta()); bounceY = true
}
}
fun debug3(wut: Any?) {
// vvvvv set it true to make debug print work
if (false) printdbg(this, wut)
// fire Collision Event with one/two/three-side collision
// for the ease of writing, this jumptable is separated from above.
when (selfCollisionStatus) {
// TODO compose CollisionInfo and fire collided()
}
fun debug4(wut: Any?) {
// vvvvv set it true to make debug print work
if (false) printdbg(this, wut)
}
fun BlockAddress.isFeetTile(hitbox: Hitbox): Boolean {
val (x, y) = LandUtil.resolveBlockAddr(world!!, this)
val newTilewiseHitbox = Hitbox.fromTwoPoints(
hitbox.startX.div(TILE_SIZE).floor(),
hitbox.startY.div(TILE_SIZE).floor(),
hitbox.endX.minus(0.00001).div(TILE_SIZE).floor(),
hitbox.endY.minus(0.00001).div(TILE_SIZE).floor(),
true
// two-side collision
if (selfCollisionStatus in listOf(3, 6, 9, 12)) {
debug1("twoside collision $selfCollisionStatus")
// !! this code is based on Dyn4j Vector's coord system; V(1,0) -> 0, V(0,1) -> pi, V(0,-1) -> -pi !! //
// we can use selfCollisionStatus to tell which of those four side we care
// points to the EDGE of the tile in world dimension (don't use this directly to get tilewise coord!!)
val offendingTileWorldX = if (selfCollisionStatus in listOf(6, 12))
newHitbox.endX.div(TILE_SIZE).floor() * TILE_SIZE - 0.00001
else
newHitbox.startX.div(TILE_SIZE).ceil() * TILE_SIZE
// points to the EDGE of the tile in world dimension (don't use this directly to get tilewise coord!!)
val offendingTileWorldY = if (selfCollisionStatus in listOf(3, 6))
newHitbox.endY.div(TILE_SIZE).floor() * TILE_SIZE - 0.00001
else
newHitbox.startY.div(TILE_SIZE).ceil() * TILE_SIZE
val offendingHitboxPointX = if (selfCollisionStatus in listOf(6, 12))
newHitbox.endX
else
newHitbox.startX
val offendingHitboxPointY = if (selfCollisionStatus in listOf(3, 6))
newHitbox.endY
else
newHitbox.startY
val angleOfIncidence =
if (selfCollisionStatus in listOf(3, 9))
displacement.direction.toPositiveRad()
else
displacement.direction
val angleThreshold =
if (selfCollisionStatus in listOf(3, 9))
(Vector2(offendingHitboxPointX, offendingHitboxPointY) -
Vector2(offendingTileWorldX, offendingTileWorldY)).direction.toPositiveRad()
else
(Vector2(offendingHitboxPointX, offendingHitboxPointY) -
Vector2(offendingTileWorldX, offendingTileWorldY)).direction
debug1("vectorSum: $displacement, vectorDirRaw: ${displacement.direction / Math.PI}pi")
debug1("incidentAngle: ${angleOfIncidence / Math.PI}pi, threshold: ${angleThreshold / Math.PI}pi")
val displacementAbs = Vector2(
(offendingTileWorldX - offendingHitboxPointX).abs(),
(offendingTileWorldY - offendingHitboxPointY).abs()
)
// offset 1 pixel to the down so that friction would work
return (y == hitbox.endY.plus(1.0).div(TILE_SIZE).floorInt()) && // copied from forEachFeetTileNum
(x in newTilewiseHitbox.startX.toInt()..newTilewiseHitbox.endX.toInt()) // copied from forEachOccupyingTilePos
}
fun Double.modTile() = this.toInt().div(TILE_SIZE).times(TILE_SIZE)
fun Double.modTileDelta() = this - this.modTile()
// FIXME jump-thru-ceil bug on 1px-wide (the edge), case-9 collision (does not occur on case-12 coll.)
val vectorSum = (externalForce + controllerMoveDelta) * (Terrarum.PHYS_REF_FPS * delta)
val ccdSteps = minOf(16, (vectorSum.magnitudeSquared / TILE_SIZE.sqr()).floorInt() + 1) // adaptive
// * NEW idea: wall pushes the actors (ref. SM64 explained by dutch pancake) *
// direction to push is determined by the velocity
// proc:
// 10 I detect being walled and displace myself
// 11 There's 16 possible case so work all 16 (some can be merged obviously)
// 12 Amount of displacement can be obtained with modTileDelta()
// 13 isWalled() is confirmed to be working
// 20 sixteenStep may be optional, I think, but it'd be good to have
// ignore MOST of the codes below (it might be possible to recycle the structure??)
// and the idea above has not yet implemented, and may never will. --Torvald, 2018-12-30
val sixteenStep = (0..ccdSteps).map { hitbox.clone().translate(vectorSum * (it / ccdSteps.toDouble())) } // zeroth step is for special condition
var collidingStep: Int? = null
for (step in 1..ccdSteps) {
val stepBox = sixteenStep[step]
forEachOccupyingTilePos(stepBox) {
val tileCoord = LandUtil.resolveBlockAddr(world!!, it)
val tile = world!!.getTileFromTerrain(tileCoord.first, tileCoord.second) ?: Block.STONE
if (shouldICollideWithThis(tile) || (it.isFeetTile(stepBox) && shouldICollideWithThisFeet(tile))) {
collidingStep = step
}
}
if (collidingStep != null) break
}
val COLL_LEFTSIDE = 1
val COLL_BOTTOMSIDE = 2
val COLL_RIGHTSIDE = 4
val COLL_TOPSIDE = 8
var bounceX = false
var bounceY = false
var zeroX = false
var zeroY = false
// collision NOT detected
if (collidingStep == null) {
hitbox.translate(vectorSum)
// grounded = false
}
// collision detected
else {
debug1("== Collision step: $collidingStep / $ccdSteps")
val newHitbox = hitbox.reassign(sixteenStep[collidingStep!!])
var selfCollisionStatus = 0
if (isWalled(newHitbox, COLLIDING_LEFT)) selfCollisionStatus += COLL_LEFTSIDE // 1
if (isWalled(newHitbox, COLLIDING_RIGHT)) selfCollisionStatus += COLL_RIGHTSIDE // 4
if (isWalled(newHitbox, COLLIDING_TOP)) selfCollisionStatus += COLL_TOPSIDE // 8
if (isWalled(newHitbox, COLLIDING_BOTTOM)) selfCollisionStatus += COLL_BOTTOMSIDE // 2
// fixme UP and RIGHT && LEFT and DOWN bug
when (selfCollisionStatus) {
0 -> {
debug1("[ActorWBMovable] Contradiction -- collision detected by CCD, but isWalled() says otherwise")
}
5 -> {
zeroX = true
}
10 -> {
zeroY = true
}
15 -> {
newHitbox.reassign(sixteenStep[0]); zeroX = true; zeroY = true
}
// one-side collision
1, 11 -> {
newHitbox.translatePosX(TILE_SIZE - newHitbox.startX.modTileDelta()); bounceX = true
}
4, 14 -> {
newHitbox.translatePosX(-newHitbox.endX.modTileDelta()); bounceX = true
}
8, 13 -> {
newHitbox.translatePosY(TILE_SIZE - newHitbox.startY.modTileDelta()); bounceY = true
}
2, 7 -> {
newHitbox.translatePosY(-newHitbox.endY.modTileDelta()); bounceY = true
}
}
// fire Collision Event with one/two/three-side collision
// for the ease of writing, this jumptable is separated from above.
when (selfCollisionStatus) {
// TODO compose CollisionInfo and fire collided()
}
// two-side collision
if (selfCollisionStatus in listOf(3, 6, 9, 12)) {
debug1("twoside collision $selfCollisionStatus")
// !! this code is based on Dyn4j Vector's coord system; V(1,0) -> 0, V(0,1) -> pi, V(0,-1) -> -pi !! //
// we can use selfCollisionStatus to tell which of those four side we care
// points to the EDGE of the tile in world dimension (don't use this directly to get tilewise coord!!)
val offendingTileWorldX = if (selfCollisionStatus in listOf(6, 12))
newHitbox.endX.div(TILE_SIZE).floor() * TILE_SIZE - 0.00001
else
newHitbox.startX.div(TILE_SIZE).ceil() * TILE_SIZE
// points to the EDGE of the tile in world dimension (don't use this directly to get tilewise coord!!)
val offendingTileWorldY = if (selfCollisionStatus in listOf(3, 6))
newHitbox.endY.div(TILE_SIZE).floor() * TILE_SIZE - 0.00001
else
newHitbox.startY.div(TILE_SIZE).ceil() * TILE_SIZE
val offendingHitboxPointX = if (selfCollisionStatus in listOf(6, 12))
newHitbox.endX
else
newHitbox.startX
val offendingHitboxPointY = if (selfCollisionStatus in listOf(3, 6))
newHitbox.endY
else
newHitbox.startY
val angleOfIncidence =
if (selfCollisionStatus in listOf(3, 9))
vectorSum.direction.toPositiveRad()
else
vectorSum.direction
val angleThreshold =
if (selfCollisionStatus in listOf(3, 9))
(Vector2(offendingHitboxPointX, offendingHitboxPointY) -
Vector2(offendingTileWorldX, offendingTileWorldY)).direction.toPositiveRad()
else
(Vector2(offendingHitboxPointX, offendingHitboxPointY) -
Vector2(offendingTileWorldX, offendingTileWorldY)).direction
debug1("vectorSum: $vectorSum, vectorDirRaw: ${vectorSum.direction / Math.PI}pi")
debug1("incidentAngle: ${angleOfIncidence / Math.PI}pi, threshold: ${angleThreshold / Math.PI}pi")
val displacementAbs = Vector2(
(offendingTileWorldX - offendingHitboxPointX).abs(),
(offendingTileWorldY - offendingHitboxPointY).abs()
)
// FIXME jump-thru-ceil bug on 1px-wide (the edge), case-9 collision (does not occur on case-12 coll.)
val displacementUnitVector =
if (angleOfIncidence == angleThreshold)
-vectorSum
else {
when (selfCollisionStatus) {
3 -> if (angleOfIncidence > angleThreshold) Vector2(1.0, 0.0) else Vector2(0.0, -1.0)
6 -> if (angleOfIncidence > angleThreshold) Vector2(0.0, -1.0) else Vector2(-1.0, 0.0)
9 -> if (angleOfIncidence > angleThreshold) Vector2(0.0, 1.0) else Vector2(1.0, 0.0)
12 -> if (angleOfIncidence > angleThreshold) Vector2(-1.0, 0.0) else Vector2(0.0, 1.0)
else -> throw InternalError("Blame hardware or universe")
}
val displacementUnitVector =
if (angleOfIncidence == angleThreshold)
-displacement
else {
when (selfCollisionStatus) {
3 -> if (angleOfIncidence > angleThreshold) Vector2(1.0, 0.0) else Vector2(0.0, -1.0)
6 -> if (angleOfIncidence > angleThreshold) Vector2(0.0, -1.0) else Vector2(-1.0, 0.0)
9 -> if (angleOfIncidence > angleThreshold) Vector2(0.0, 1.0) else Vector2(1.0, 0.0)
12 -> if (angleOfIncidence > angleThreshold) Vector2(-1.0, 0.0) else Vector2(0.0, 1.0)
else -> throw InternalError("Blame hardware or universe")
}
}
val finalDisplacement =
if (angleOfIncidence == angleThreshold)
displacementUnitVector
else
Vector2(
displacementAbs.x * displacementUnitVector.x,
displacementAbs.y * displacementUnitVector.y
)
val finalDisplacement =
if (angleOfIncidence == angleThreshold)
displacementUnitVector
else
Vector2(
displacementAbs.x * displacementUnitVector.x,
displacementAbs.y * displacementUnitVector.y
)
newHitbox.translate(finalDisplacement)
newHitbox.translate(finalDisplacement)
debug1("displacement: $finalDisplacement")
debug1("displacement: $finalDisplacement")
// TODO: translate other axis proportionally to the incident vector
// TODO: translate other axis proportionally to the incident vector
bounceX = angleOfIncidence == angleThreshold || displacementUnitVector.x != 0.0
bounceY = angleOfIncidence == angleThreshold || displacementUnitVector.y != 0.0
bounceX = angleOfIncidence == angleThreshold || displacementUnitVector.x != 0.0
bounceY = angleOfIncidence == angleThreshold || displacementUnitVector.y != 0.0
}
}
// bounce X/Y
if (bounceX) {
externalForce.x *= elasticity
controllerMoveDelta?.let { controllerMoveDelta!!.x *= elasticity }
}
if (bounceY) {
externalForce.y *= elasticity
controllerMoveDelta?.let { controllerMoveDelta!!.y *= elasticity }
}
if (zeroX) {
externalForce.x = 0.0
controllerMoveDelta?.let { controllerMoveDelta!!.x = 0.0 }
}
if (zeroY) {
externalForce.y = 0.0
controllerMoveDelta?.let { controllerMoveDelta!!.y = 0.0 }
}
// bounce X/Y
if (bounceX) {
externalV.x *= elasticity
controllerMoveV?.let { controllerMoveV!!.x *= elasticity }
}
if (bounceY) {
externalV.y *= elasticity
controllerMoveV?.let { controllerMoveV!!.y *= elasticity }
}
if (zeroX) {
externalV.x = 0.0
controllerMoveV?.let { controllerMoveV!!.x = 0.0 }
}
if (zeroY) {
externalV.y = 0.0
controllerMoveV?.let { controllerMoveV!!.y = 0.0 }
}
hitbox.reassign(newHitbox)
hitbox.reassign(newHitbox)
// slam-into-whatever damage (such dirty; much hack; wow)
// vvvv hack (supposed to be 1.0) vvv 50% hack
val collisionDamage = mass * (vectorSum.magnitude / (10.0 / Terrarum.PHYS_TIME_FRAME).sqr()) / fallDamageDampening.sqr() * GAME_TO_SI_ACC
// kg * m / s^2 (mass * acceleration), acceleration -> (vectorMagn / (0.01)^2).gameToSI()
if (collisionDamage != 0.0) debug1("Collision damage: $collisionDamage N")
// FIXME instead of 0.5mv^2, we can model after "change of velocity (aka accel)", just as in real-life; big change of accel on given unit time is what kills
// slam-into-whatever damage (such dirty; much hack; wow)
// vvvv hack (supposed to be 1.0) vvv 50% hack
val collisionDamage = mass * (displacement.magnitude / (10.0 / Terrarum.PHYS_TIME_FRAME).sqr()) / fallDamageDampening.sqr() * GAME_TO_SI_ACC
// kg * m / s^2 (mass * acceleration), acceleration -> (vectorMagn / (0.01)^2).gameToSI()
if (collisionDamage != 0.0) debug1("Collision damage: $collisionDamage N")
// FIXME instead of 0.5mv^2, we can model after "change of velocity (aka accel)", just as in real-life; big change of accel on given unit time is what kills
// grounded = true
}// end of collision not detected
// grounded = true
}// end of collision not detected
return
return
// if collision not detected, just don't care; it's not your job to apply moveDelta
// if collision not detected, just don't care; it's not your job to apply moveDelta
}
}
@@ -1012,11 +1023,11 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
(BlockCodex[tile].isSolid) ||
// platforms, moving downward AND not "going down"
(this is ActorHumanoid && BlockCodex[tile].isPlatform &&
externalForce.y + (controllerMoveDelta?.y ?: 0.0) >= 0.0 &&
externalV.y + (controllerMoveV?.y ?: 0.0) >= 0.0 &&
!this.isDownDown && this.axisY <= 0f) ||
// platforms, moving downward
(this !is ActorHumanoid && BlockCodex[tile].isPlatform &&
externalForce.y + (controllerMoveDelta?.y ?: 0.0) >= 0.0)
externalV.y + (controllerMoveV?.y ?: 0.0) >= 0.0)
// TODO: as for the platform, only apply it when it's a feet tile
@@ -1079,23 +1090,23 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
BASE_FRICTION * if (grounded) feetFriction else bodyFriction
}
if (externalForce.x < 0) {
externalForce.x += friction
if (externalForce.x > 0) externalForce.x = 0.0 // compensate overshoot
if (externalV.x < 0) {
externalV.x += friction
if (externalV.x > 0) externalV.x = 0.0 // compensate overshoot
}
else if (externalForce.x > 0) {
externalForce.x -= friction
if (externalForce.x < 0) externalForce.x = 0.0 // compensate overshoot
else if (externalV.x > 0) {
externalV.x -= friction
if (externalV.x < 0) externalV.x = 0.0 // compensate overshoot
}
if (this is Controllable) {
if (controllerMoveDelta!!.x < 0) {
controllerMoveDelta!!.x += friction
if (controllerMoveDelta!!.x > 0) controllerMoveDelta!!.x = 0.0
if (controllerMoveV!!.x < 0) {
controllerMoveV!!.x += friction
if (controllerMoveV!!.x > 0) controllerMoveV!!.x = 0.0
}
else if (controllerMoveDelta!!.x > 0) {
controllerMoveDelta!!.x -= friction
if (controllerMoveDelta!!.x < 0) controllerMoveDelta!!.x = 0.0
else if (controllerMoveV!!.x > 0) {
controllerMoveV!!.x -= friction
if (controllerMoveV!!.x < 0) controllerMoveV!!.x = 0.0
}
}
}
@@ -1106,23 +1117,23 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
else
BASE_FRICTION * bodyFriction
if (externalForce.y < 0) {
externalForce.y += friction
if (externalForce.y > 0) externalForce.y = 0.0 // compensate overshoot
if (externalV.y < 0) {
externalV.y += friction
if (externalV.y > 0) externalV.y = 0.0 // compensate overshoot
}
else if (externalForce.y > 0) {
externalForce.y -= friction
if (externalForce.y < 0) externalForce.y = 0.0 // compensate overshoot
else if (externalV.y > 0) {
externalV.y -= friction
if (externalV.y < 0) externalV.y = 0.0 // compensate overshoot
}
if (this is Controllable) {
if (controllerMoveDelta!!.y < 0) {
controllerMoveDelta!!.y += friction
if (controllerMoveDelta!!.y > 0) controllerMoveDelta!!.y = 0.0
if (controllerMoveV!!.y < 0) {
controllerMoveV!!.y += friction
if (controllerMoveV!!.y > 0) controllerMoveV!!.y = 0.0
}
else if (controllerMoveDelta!!.y > 0) {
controllerMoveDelta!!.y -= friction
if (controllerMoveDelta!!.y < 0) controllerMoveDelta!!.y = 0.0
else if (controllerMoveV!!.y > 0) {
controllerMoveV!!.y -= friction
if (controllerMoveV!!.y < 0) controllerMoveV!!.y = 0.0
}
}
}
@@ -1443,8 +1454,8 @@ open class ActorWBMovable(renderOrder: RenderOrder, val immobileBody: Boolean =
field = value
if (value) {
externalForce.zero()
controllerMoveDelta?.zero()
externalV.zero()
controllerMoveV?.zero()
}
}

View File

@@ -46,7 +46,7 @@ import org.luaj.vm2.*
*/
fun composeActorObject(actor: ActorWBMovable): LuaTable {
val t: LuaTable = LuaTable()
val moveDelta = actor.externalForce + actor.controllerMoveDelta
val moveDelta = actor.externalV + actor.controllerMoveV
t["name"] = actor.actorValue.getAsString(AVKey.NAME).toLua()
t["startX"] = actor.hitbox.centeredX.toLua()

View File

@@ -52,8 +52,8 @@ open class GameWorld {
//public World physWorld = new World( new Vec2(0, -Terrarum.game.gravitationalAccel) );
//physics
/** Meter per second squared. Currently only the downward gravity is supported. No reverse gravity :p */
var gravitation: Vector2 = Vector2(0.0, 9.80665)
/** Some arbitrary and empirical value */
var gravitation: Vector2 = Vector2(0.0, 0.36)
/** 0.0..1.0+ */
var globalLight = Color(0f,0f,0f,0f)
var averageTemperature = 288f // 15 deg celsius; simulates global warming

View File

@@ -124,11 +124,11 @@ open class ActorHumanoid(
@Transient private val KEY_NULL = -1
/** how long the jump button has down, in frames */
internal var jumpCounter = 0
internal var jumpCounter = 0f
internal var jumpAcc = 0.0
/** how long the walk button has down, in frames */
internal var walkCounterX = 0
internal var walkCounterY = 0
internal var walkCounterX = 0f
internal var walkCounterY = 0f
@Transient private val MAX_JUMP_LENGTH = 25 // manages "heaviness" of the jump control. Higher = heavier
private var readonly_totalX = 0.0
@@ -289,24 +289,24 @@ open class ActorHumanoid(
if (hasController) {
if (axisX != 0f) {
walkHorizontal(axisX < 0f, axisX.abs())
walkHorizontal(delta, axisX < 0f, axisX.abs())
}
}
// ↑F, ↓S
if (isRightDown && !isLeftDown) {
walkHorizontal(false, AXIS_KEYBOARD)
walkHorizontal(delta, false, AXIS_KEYBOARD)
prevHMoveKey = AppLoader.getConfigInt("keyright")
} // ↓F, ↑S
else if (isLeftDown && !isRightDown) {
walkHorizontal(true, AXIS_KEYBOARD)
walkHorizontal(delta, true, AXIS_KEYBOARD)
prevHMoveKey = AppLoader.getConfigInt("keyleft")
} // ↓F, ↓S
/*else if (isLeftDown && isRightDown) {
if (prevHMoveKey == KeyMap.getKeyCode(EnumKeyFunc.MOVE_LEFT)) {
walkHorizontal(false, AXIS_KEYBOARD)
walkHorizontal(delta, false, AXIS_KEYBOARD)
prevHMoveKey = KeyMap.getKeyCode(EnumKeyFunc.MOVE_RIGHT)
} else if (prevHMoveKey == KeyMap.getKeyCode(EnumKeyFunc.MOVE_RIGHT)) {
walkHorizontal(true, AXIS_KEYBOARD)
walkHorizontal(delta, true, AXIS_KEYBOARD)
prevHMoveKey = KeyMap.getKeyCode(EnumKeyFunc.MOVE_LEFT)
}
}*/
@@ -317,24 +317,24 @@ open class ActorHumanoid(
if (isNoClip || COLLISION_TEST_MODE) {
if (hasController) {
if (axisY != 0f) {
walkVertical(axisY < 0, axisY.abs())
walkVertical(delta, axisY < 0, axisY.abs())
}
}
// ↑E, ↓D
if (isDownDown && !isUpDown) {
walkVertical(false, AXIS_KEYBOARD)
walkVertical(delta, false, AXIS_KEYBOARD)
prevVMoveKey = AppLoader.getConfigInt("keydown")
} // ↓E, ↑D
else if (isUpDown && !isDownDown) {
walkVertical(true, AXIS_KEYBOARD)
walkVertical(delta, true, AXIS_KEYBOARD)
prevVMoveKey = AppLoader.getConfigInt("keyup")
} // ↓E, ↓D
/*else if (isUpDown && isDownDown) {
if (prevVMoveKey == KeyMap.getKeyCode(EnumKeyFunc.MOVE_UP)) {
walkVertical(false, AXIS_KEYBOARD)
walkVertical(delta, false, AXIS_KEYBOARD)
prevVMoveKey = KeyMap.getKeyCode(EnumKeyFunc.MOVE_DOWN)
} else if (prevVMoveKey == KeyMap.getKeyCode(EnumKeyFunc.MOVE_DOWN)) {
walkVertical(true, AXIS_KEYBOARD)
walkVertical(delta, true, AXIS_KEYBOARD)
prevVMoveKey = KeyMap.getKeyCode(EnumKeyFunc.MOVE_UP)
}
}*/
@@ -349,15 +349,15 @@ open class ActorHumanoid(
(!airJumpingAllowed && walledBottom)) {
jumping = true
}
jump()
jump(delta)
}
else {
walkVertical(true, AXIS_KEYBOARD)
walkVertical(delta, true, AXIS_KEYBOARD)
}
}
else {
jumping = false
jumpCounter = 0
jumpCounter = 0f
jumpAcc = 0.0
}
@@ -388,7 +388,7 @@ open class ActorHumanoid(
* @param absAxisVal (set AXIS_KEYBOARD if keyboard controlled)
* @author minjaesong
*/
private fun walkHorizontal(left: Boolean, absAxisVal: Float) {
private fun walkHorizontal(delta: Float, left: Boolean, absAxisVal: Float) {
if (avAcceleration.isNaN()) {
@@ -406,12 +406,12 @@ open class ActorHumanoid(
avAcceleration * applyVelo(walkCounterX) * (if (left) -1f else 1f) * absAxisVal
if (absAxisVal != AXIS_KEYBOARD)
controllerMoveDelta?.x?.let { controllerMoveDelta!!.x = controllerMoveDelta!!.x.plus(readonly_totalX).bipolarClamp(avSpeedCap * absAxisVal) }
controllerMoveV?.x?.let { controllerMoveV!!.x = controllerMoveV!!.x.plus(readonly_totalX).bipolarClamp(avSpeedCap * absAxisVal) }
else
controllerMoveDelta?.x?.let { controllerMoveDelta!!.x = controllerMoveDelta!!.x.plus(readonly_totalX).bipolarClamp(avSpeedCap) }
controllerMoveV?.x?.let { controllerMoveV!!.x = controllerMoveV!!.x.plus(readonly_totalX).bipolarClamp(avSpeedCap) }
if (walkCounterX < 1000000) {
walkCounterX += 1
walkCounterX += 1 * (Terrarum.PHYS_REF_FPS * delta).toFloat()
}
isWalkingH = true
@@ -428,7 +428,7 @@ open class ActorHumanoid(
* *
* @param absAxisVal (set AXIS_KEYBOARD if keyboard controlled)
*/
private fun walkVertical(up: Boolean, absAxisVal: Float) {
private fun walkVertical(delta: Float, up: Boolean, absAxisVal: Float) {
if (up && walledTop || !up && walledBottom) return
@@ -444,12 +444,12 @@ open class ActorHumanoid(
avAcceleration * applyVelo(walkCounterY) * (if (up) -1f else 1f) * absAxisVal
if (absAxisVal != AXIS_KEYBOARD)
controllerMoveDelta?.y?.let { controllerMoveDelta!!.y = controllerMoveDelta!!.y.plus(readonly_totalY).bipolarClamp(avSpeedCap * absAxisVal) }
controllerMoveV?.y?.let { controllerMoveV!!.y = controllerMoveV!!.y.plus(readonly_totalY).bipolarClamp(avSpeedCap * absAxisVal) }
else
controllerMoveDelta?.y?.let { controllerMoveDelta!!.y = controllerMoveDelta!!.y.plus(readonly_totalY).bipolarClamp(avSpeedCap) }
controllerMoveV?.y?.let { controllerMoveV!!.y = controllerMoveV!!.y.plus(readonly_totalY).bipolarClamp(avSpeedCap) }
if (walkCounterY < 1000000) {
walkCounterY += 1
walkCounterY += 1 * (Terrarum.PHYS_REF_FPS * delta).toFloat()
}
@@ -462,7 +462,7 @@ open class ActorHumanoid(
else 0.0
}
private fun applyVelo(x: Int): Double {
private fun applyVelo(x: Float): Double {
return if (x < WALK_FRAMES_TO_MAX_ACCEL)
0.5 - 0.5 * Math.cos(Math.PI * x / WALK_FRAMES_TO_MAX_ACCEL)
else 1.0
@@ -470,13 +470,13 @@ open class ActorHumanoid(
// stops; let the friction kick in by doing nothing to the velocity here
private fun walkHStop() {
walkCounterX = 0
walkCounterX = 0f
isWalkingH = false
}
// stops; let the friction kick in by doing nothing to the velocity here
private fun walkVStop() {
walkCounterY = 0
walkCounterY = 0f
isWalkingV = false
}
@@ -489,6 +489,7 @@ open class ActorHumanoid(
private var oldJUMPPOWERBUFF = -1.0 // init
private var oldScale = -1.0
private var oldDragCoefficient = -1.0
// used by some AIs?
var jumpAirTime: Double = -1.0
get() {
// compare all the affecting variables
@@ -512,7 +513,7 @@ open class ActorHumanoid(
var simYPos = 0.0
var forceVec = Vector2(0.0, 0.0)
var jmpCtr = 0
var jmpCtr = 0f
while (true) {
if (jmpCtr < MAX_JUMP_LENGTH) jmpCtr++
@@ -542,7 +543,7 @@ open class ActorHumanoid(
private val jumpPower: Double
get() = actorValue.getAsDouble(AVKey.JUMPPOWER)!! * (actorValue.getAsDouble(AVKey.JUMPPOWERBUFF) ?: 1.0)
private fun jumpFunc(len: Int, counter: Int): Double {
private fun jumpFunc(len: Int, counter: Float): Double {
// linear time mode
val init = (len + 1) / 2.0
var timedJumpCharge = init - init / len * counter
@@ -555,16 +556,16 @@ open class ActorHumanoid(
*
* TODO linear function (play Super Mario Bros. and you'll get what I'm talking about) -- SCRATCH THAT!
*/
private fun jump() {
private fun jump(delta: Float) {
if (jumping) {// && jumpable) {
// increment jump counter
if (jumpCounter < MAX_JUMP_LENGTH) jumpCounter += 1
if (jumpCounter < MAX_JUMP_LENGTH) jumpCounter += 1 / (Terrarum.PHYS_REF_FPS * delta).toFloat()
val timedJumpCharge = jumpFunc(MAX_JUMP_LENGTH, jumpCounter)
jumpAcc = getJumpAcc(jumpPower, timedJumpCharge)
controllerMoveDelta?.y?.let { controllerMoveDelta!!.y -= jumpAcc } // feed negative value to the vector
controllerMoveV?.y?.let { controllerMoveV!!.y -= jumpAcc } // feed negative value to the vector
// do not think of resetting this to zero when counter hit the ceiling; that's HOW NOT
// newtonian physics work, stupid myself :(
@@ -579,7 +580,7 @@ open class ActorHumanoid(
if (jumpCounter >= MAX_JUMP_LENGTH && !isGamer) {
isJumpDown = false
jumping = false
jumpCounter = 0
jumpCounter = 0f
jumpAcc = 0.0
}
}
@@ -609,7 +610,7 @@ open class ActorHumanoid(
sprite?.update(delta)
spriteGlow?.update(delta)
if (walledBottom && controllerMoveDelta?.x != 0.0) {
if (walledBottom && controllerMoveV?.x != 0.0) {
//switch row
sprite?.switchRow(SPRITE_ROW_WALK)
spriteGlow?.switchRow(SPRITE_ROW_WALK)
@@ -617,8 +618,8 @@ open class ActorHumanoid(
// set anim frame delay
// 4f of the divider is a magic number, empirically decided
if (this is HasAssembledSprite) {
sprite?.delays?.set(SPRITE_ROW_WALK, scale.sqrt().toFloat() / (4f * (controllerMoveDelta?.x ?: 0.0001).abs().toFloat())) // FIXME empirical value
spriteGlow?.delays?.set(SPRITE_ROW_WALK, scale.sqrt().toFloat() / (4f * (controllerMoveDelta?.x ?: 0.0001).abs().toFloat())) // FIXME empirical value
sprite?.delays?.set(SPRITE_ROW_WALK, scale.sqrt().toFloat() / (4f * (controllerMoveV?.x ?: 0.0001).abs().toFloat())) // FIXME empirical value
spriteGlow?.delays?.set(SPRITE_ROW_WALK, scale.sqrt().toFloat() / (4f * (controllerMoveV?.x ?: 0.0001).abs().toFloat())) // FIXME empirical value
}

View File

@@ -10,7 +10,6 @@ import net.torvald.terrarum.gameactors.AVKey
import net.torvald.terrarum.gameactors.ActorWBMovable
import net.torvald.terrarum.gameactors.Controllable
import net.torvald.terrarum.gameactors.Hitbox
import net.torvald.terrarum.gameworld.GameWorld
/**
* Created by minjaesong on 2018-01-17.
@@ -37,7 +36,7 @@ class PhysTestLuarLander : ActorWBMovable(RenderOrder.MIDTOP), Controllable {
super.update(delta)
if (Gdx.input.isKeyPressed(Input.Keys.UP)) {
controllerMoveDelta!!.y = avSpeedCap
controllerMoveV!!.y = avSpeedCap
}
}

View File

@@ -9,7 +9,6 @@ import net.torvald.terrarum.blockproperties.BlockCodex
import net.torvald.terrarum.gameactors.ActorWBMovable
import net.torvald.terrarum.gameactors.Hitbox
import net.torvald.terrarum.gameactors.Luminous
import net.torvald.terrarum.gameworld.GameWorld
import org.dyn4j.geometry.Vector2
import java.util.*
@@ -54,7 +53,7 @@ open class ProjectileSimple(
posPre = Point2d(fromPoint.x, fromPoint.y)
// lightbox sized 8x8 centered to the bullet
lightBoxList.add(Hitbox(-4.0, -4.0, 8.0, 8.0))
//this.externalForce.set(velocity)
//this.externalV.set(velocity)
damage = bulletDatabase[type][OFFSET_DAMAGE] as Int
displayColour = bulletDatabase[type][OFFSET_COL] as Color
@@ -64,7 +63,7 @@ open class ProjectileSimple(
setHitboxDimension(2, 2, 0, 0) // should be following sprite's properties if there IS one
externalForce.set((fromPoint to toPoint).setMagnitude(speed.toDouble()))
externalV.set((fromPoint to toPoint).setMagnitude(speed.toDouble()))

View File

@@ -150,8 +150,8 @@ object CollisionSolver {
// if they actually makes collision (e.g. player vs ball), solve it
if (a makesCollisionWith b) {
val a_moveDelta = a.externalForce + a.controllerMoveDelta
val b_moveDelta = b.externalForce + b.controllerMoveDelta
val a_moveDelta = a.externalV + a.controllerMoveV
val b_moveDelta = b.externalV + b.controllerMoveV
val ux_1 = a_moveDelta.x
val ux_2 = b_moveDelta.x

View File

@@ -108,11 +108,11 @@ class BasicDebugInfoWindow : UICanvas() {
+ ccG
+ "${WorldCamera.y}")
printLine(batch, 3, "veloX reported $ccG${player.externalForce.x}")
printLine(batch, 4, "veloY reported $ccG${player.externalForce.y}")
printLine(batch, 3, "veloX reported $ccG${player.externalV.x}")
printLine(batch, 4, "veloY reported $ccG${player.externalV.y}")
printLine(batch, 5, "p_WalkX $ccG${player.controllerMoveDelta?.x}")
printLine(batch, 6, "p_WalkY $ccG${player.controllerMoveDelta?.y}")
printLine(batch, 5, "p_WalkX $ccG${player.controllerMoveV?.x}")
printLine(batch, 6, "p_WalkY $ccG${player.controllerMoveV?.y}")
printLineColumn(batch, 2, 3, "veloX measured $ccG${xdelta}")
printLineColumn(batch, 2, 4, "veloY measured $ccG${ydelta}")

View File

@@ -360,6 +360,13 @@ class Vector2 {
return Vector2(newX, newY)
}
private fun Double.magnSqr() = if (this >= 0.0) this * this else -(this * this)
/**
* Returns new vector whose magnitude is squared, and direction is preserved.
*/
fun magnSqr(): Vector2 = Vector2(x.magnSqr(), y.magnSqr())
/**
* Adds the given [Vector2] to this [Vector2].
* @param vector the [Vector2]