collision detection and CCD: Not exactly fixed but it works good enough

Former-commit-id: 3feb9b1156fc941390b4efe4c1540ed4c5dbd109
Former-commit-id: 8c97b0ade765731800ef09b722e0742e44443338
This commit is contained in:
Song Minjae
2016-06-25 00:23:20 +09:00
parent e62f8feda5
commit 91f52eeab5
14 changed files with 400 additions and 481 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -3,19 +3,9 @@ package net.torvald.point
/** /**
* Created by minjaesong on 16-01-15. * Created by minjaesong on 16-01-15.
*/ */
class Point2d(x: Double, y: Double) { class Point2d(var x: Double, var y: Double) : Cloneable {
var x: Double = 0.toDouble() fun set(x: Double, y: Double) {
private set
var y: Double = 0.toDouble()
private set
init {
this.x = x
this.y = y
}
operator fun set(x: Double, y: Double) {
this.x = x this.x = x
this.y = y this.y = y
} }

View File

@@ -252,12 +252,9 @@ constructor(gamename: String) : StateBasedGame(gamename) {
get() { get() {
val lan = System.getProperty("user.language") val lan = System.getProperty("user.language")
var country = System.getProperty("user.country") var country = System.getProperty("user.country")
if (lan == "en") if (lan == "en") country = "US"
country = "US" else if (lan == "fr") country = "FR"
else if (lan == "fr") else if (lan == "de") country = "DE"
country = "FR"
else if (lan == "de")
country = "DE"
else if (lan == "ko") country = "KR" else if (lan == "ko") country = "KR"
return lan + country return lan + country

View File

@@ -6,30 +6,69 @@ package net.torvald.terrarum.gameactors
object AVKey { object AVKey {
const val MULT = "mult" const val MULT = "mult"
/** pixels per frame
* walking/running speed
*/
const val SPEED = "speed" const val SPEED = "speed"
const val SPEEDMULT = "speed$MULT" const val SPEEDMULT = "speed$MULT"
/** pixels per frame squared
* acceleration of the movement (e.g. running, flying, driving, etc.)
*/
const val ACCEL = "accel" const val ACCEL = "accel"
const val ACCELMULT = "accel$MULT" const val ACCELMULT = "accel$MULT"
const val SCALE = "scale" const val SCALE = "scale"
/** pixels */
const val BASEHEIGHT = "baseheight" const val BASEHEIGHT = "baseheight"
/** kilogrammes */
const val BASEMASS = "basemass" const val BASEMASS = "basemass"
/** pixels per frame */
const val JUMPPOWER = "jumppower" const val JUMPPOWER = "jumppower"
const val JUMPPOWERMULT = "jumppower$MULT" const val JUMPPOWERMULT = "jumppower$MULT"
/** Int
* "Default" value of 1 000
*/
const val STRENGTH = "strength" const val STRENGTH = "strength"
const val ENCUMBRANCE = "encumbrance" const val ENCUMBRANCE = "encumbrance"
/** 30-bit RGB (Int)
* 0000 0010000000 0010000000 0010000000
* ^ Red ^ Green ^ Blue
*/
const val LUMINOSITY = "luminosity" const val LUMINOSITY = "luminosity"
const val PHYSIQUEMULT = "physique$MULT" const val PHYSIQUEMULT = "physique$MULT"
const val DRAGCOEFF = "dragcoeff" const val DRAGCOEFF = "dragcoeff"
/** String
* e.g. Jarppi
*/
const val NAME = "name" const val NAME = "name"
/** String
* e.g. Duudson
*/
const val RACENAME = "racename" const val RACENAME = "racename"
/** String
* e.g. Duudsonit
*/
const val RACENAMEPLURAL = "racenameplural" const val RACENAMEPLURAL = "racenameplural"
/** killogrammes
* will affect attack strength, speed and inventory label
* (see "Attack momentum calculator.numbers")
* e.g. Hatchet (tiny)
*/
const val TOOLSIZE = "toolsize" const val TOOLSIZE = "toolsize"
/** Boolean
* whether the player can talk with it
*/
const val INTELLIGENT = "intelligent" const val INTELLIGENT = "intelligent"
const val BASEDEFENCE = "basedefence" // creature base /** (unit TBA)
const val ARMOURDEFENCE = "armourdefence" // armour points * base defence point of the species
*/
const val BASEDEFENCE = "basedefence"
/** (unit TBA)
* current defence point of worn armour(s)
*/
const val ARMOURDEFENCE = "armourdefence"
const val ARMOURDEFENCEMULT = "armourdefence$MULT" const val ARMOURDEFENCEMULT = "armourdefence$MULT"
} }

View File

@@ -8,6 +8,7 @@ import net.torvald.terrarum.tileproperties.TilePropCodex
import net.torvald.spriteanimation.SpriteAnimation import net.torvald.spriteanimation.SpriteAnimation
import com.jme3.math.FastMath import com.jme3.math.FastMath
import net.torvald.terrarum.tileproperties.TileNameCode import net.torvald.terrarum.tileproperties.TileNameCode
import org.dyn4j.Epsilon
import org.dyn4j.geometry.ChainedVector2 import org.dyn4j.geometry.ChainedVector2
import org.dyn4j.geometry.Vector2 import org.dyn4j.geometry.Vector2
import org.newdawn.slick.GameContainer import org.newdawn.slick.GameContainer
@@ -22,15 +23,15 @@ open class ActorWithBody : Actor(), Visible {
override var actorValue: ActorValue = ActorValue() override var actorValue: ActorValue = ActorValue()
var hitboxTranslateX: Double = 0.toDouble()// relative to spritePosX var hitboxTranslateX: Double = 0.0// relative to spritePosX
var hitboxTranslateY: Double = 0.toDouble()// relative to spritePosY var hitboxTranslateY: Double = 0.0// relative to spritePosY
var baseHitboxW: Int = 0 var baseHitboxW: Int = 0
var baseHitboxH: Int = 0 var baseHitboxH: Int = 0
@Transient private val map: GameMap = Terrarum.game.map @Transient private val map: GameMap = Terrarum.game.map
/** /**
* Velocity vector (broken down by axes) for newtonian sim. * Velocity vector for newtonian sim.
* Acceleration: used in code like: * Acceleration: used in code like:
* veloY += 3.0 * veloY += 3.0
* +3.0 is acceleration. You __accumulate__ acceleration to the velocity. * +3.0 is acceleration. You __accumulate__ acceleration to the velocity.
@@ -85,7 +86,7 @@ open class ActorWithBody : Actor(), Visible {
if (value <= 0) if (value <= 0)
throw IllegalArgumentException("mass cannot be less than or equal to zero.") throw IllegalArgumentException("mass cannot be less than or equal to zero.")
else if (value < MASS_LOWEST) { else if (value < MASS_LOWEST) {
println("[ActorWithBody] input too low; assigning 0.1 instead.") println("[ActorWithBody] input too small; using $MASS_LOWEST instead.")
actorValue[AVKey.BASEMASS] = MASS_LOWEST actorValue[AVKey.BASEMASS] = MASS_LOWEST
} }
@@ -95,7 +96,7 @@ open class ActorWithBody : Actor(), Visible {
var elasticity: Double = 0.0 var elasticity: Double = 0.0
set(value) { set(value) {
if (value < 0) if (value < 0)
throw IllegalArgumentException("[ActorWithBody] Invalid elasticity value: $value; valid elasticity value is [0, 1].") throw IllegalArgumentException("invalid elasticity value $value; valid elasticity value is [0, 1].")
else if (value >= ELASTICITY_MAX) { else if (value >= ELASTICITY_MAX) {
println("[ActorWithBody] Elasticity were capped to $ELASTICITY_MAX.") println("[ActorWithBody] Elasticity were capped to $ELASTICITY_MAX.")
field = ELASTICITY_MAX field = ELASTICITY_MAX
@@ -118,12 +119,6 @@ open class ActorWithBody : Actor(), Visible {
private var density = 1000.0 private var density = 1000.0
/**
* Gravitational Constant G. Load from gamemap.
* [m / s^2]
* s^2 = 1/FPS = 1/60 if FPS is targeted to 60
* meter to pixel : 24/FPS
*/
@Transient private val METER = 24.0 @Transient private val METER = 24.0
/** /**
* [m / s^2] * SI_TO_GAME_ACC -> [px / InternalFrame^2] * [m / s^2] * SI_TO_GAME_ACC -> [px / InternalFrame^2]
@@ -133,10 +128,15 @@ open class ActorWithBody : Actor(), Visible {
* [m / s] * SI_TO_GAME_VEL -> [px / InternalFrame] * [m / s] * SI_TO_GAME_VEL -> [px / InternalFrame]
*/ */
@Transient private val SI_TO_GAME_VEL = METER / Terrarum.TARGET_FPS @Transient private val SI_TO_GAME_VEL = METER / Terrarum.TARGET_FPS
/**
* Gravitational Constant G. Load from gamemap.
* [m / s^2]
* s^2 = 1/FPS = 1/60 if FPS is targeted to 60
* meter to pixel : 24/FPS
*/
@Transient private val gravitation: Vector2 = map.gravitation @Transient private val gravitation: Vector2 = map.gravitation
@Transient val DRAG_COEFF_DEFAULT = 1.2 @Transient val DRAG_COEFF_DEFAULT = 1.2
/** Drag coeffisient. Parachutes have much higher value than bare body (1.2) */ /** Drag coefficient. Parachutes have much higher value than bare body (1.2) */
private var DRAG_COEFF: Double private var DRAG_COEFF: Double
get() = actorValue.getAsDouble(AVKey.DRAGCOEFF) ?: DRAG_COEFF_DEFAULT get() = actorValue.getAsDouble(AVKey.DRAGCOEFF) ?: DRAG_COEFF_DEFAULT
set(value) { set(value) {
@@ -145,11 +145,6 @@ open class ActorWithBody : Actor(), Visible {
actorValue[AVKey.DRAGCOEFF] = value actorValue[AVKey.DRAGCOEFF] = value
} }
@Transient private val CONTACT_AREA_TOP = 0
@Transient private val CONTACT_AREA_RIGHT = 1
@Transient private val CONTACT_AREA_BOTTOM = 2
@Transient private val CONTACT_AREA_LEFT = 3
@Transient private val UD_COMPENSATOR_MAX = TSIZE @Transient private val UD_COMPENSATOR_MAX = TSIZE
@Transient private val LR_COMPENSATOR_MAX = TSIZE @Transient private val LR_COMPENSATOR_MAX = TSIZE
@@ -158,14 +153,6 @@ open class ActorWithBody : Actor(), Visible {
*/ */
@Transient private val G_MUL_PLAYABLE_CONST = 1.4142 @Transient private val G_MUL_PLAYABLE_CONST = 1.4142
@Transient private val EVENT_MOVE_TOP = 0
@Transient private val EVENT_MOVE_RIGHT = 1
@Transient private val EVENT_MOVE_BOTTOM = 2
@Transient private val EVENT_MOVE_LEFT = 3
@Transient private val EVENT_MOVE_NONE = -1
@Transient internal var eventMoving = EVENT_MOVE_NONE // cannot collide both X-axis and Y-axis, or else jump control breaks up.
/** /**
* Post-hit invincibility, in milliseconds * Post-hit invincibility, in milliseconds
*/ */
@@ -188,26 +175,36 @@ open class ActorWithBody : Actor(), Visible {
@Transient val DYNAMIC = 2 @Transient val DYNAMIC = 2
@Transient val STATIC = 3 // does not be budged by external forces, target of collision @Transient val STATIC = 3 // does not be budged by external forces, target of collision
private val SLEEP_THRE = 1.0 / 16.0 var collisionType = DYNAMIC
private val CCD_TICK = 1.0 / 16.0
@Transient private val CCD_TICK = 1.0 / 256.0
@Transient private val CCD_TRY_MAX = 25600
// to use with Controller (incl. player)
internal var walledLeft = false internal var walledLeft = false
internal var walledRight = false internal var walledRight = false
// just some trivial magic numbers
@Transient private val A_PIXEL = 2.0
@Transient private val COLLIDING_TOP = 0
@Transient private val COLLIDING_RIGHT = 1
@Transient private val COLLIDING_BOTTOM = 2
@Transient private val COLLIDING_LEFT = 3
@Transient private val COLLIDING_UD = 4
@Transient private val COLLIDING_LR = 5
@Transient private val COLLIDING_ALLSIDE = 6
@Transient private var assertPrinted = false
init { init {
// any initialiser goes here... // some initialiser goes here...
} }
/** /**
* @param w * @param w
* *
* @param h * @param h
* *
* @param tx +: translate drawn sprite to LEFT. * @param tx +: translate drawn sprite to LEFT.
* *
* @param ty +: translate drawn sprite to DOWN. * @param ty +: translate drawn sprite to DOWN.
* *
* @see ActorWithBody.drawBody * @see ActorWithBody.drawBody
* @see ActorWithBody.drawGlow * @see ActorWithBody.drawGlow
*/ */
@@ -221,7 +218,6 @@ open class ActorWithBody : Actor(), Visible {
/** /**
* Set hitbox position from bottom-center point * Set hitbox position from bottom-center point
* @param x * @param x
* *
* @param y * @param y
*/ */
fun setPosition(x: Double, y: Double) { fun setPosition(x: Double, y: Double) {
@@ -255,6 +251,8 @@ open class ActorWithBody : Actor(), Visible {
override fun update(gc: GameContainer, delta: Int) { override fun update(gc: GameContainer, delta: Int) {
if (isUpdate) { if (isUpdate) {
if (!assertPrinted) assertInit()
// make NoClip work for player // make NoClip work for player
if (this is Player) { if (this is Player) {
isNoSubjectToGrav = isPlayerNoClip isNoSubjectToGrav = isPlayerNoClip
@@ -281,47 +279,43 @@ open class ActorWithBody : Actor(), Visible {
if (veloX > VELO_HARD_LIMIT) veloX = VELO_HARD_LIMIT if (veloX > VELO_HARD_LIMIT) veloX = VELO_HARD_LIMIT
if (veloY > VELO_HARD_LIMIT) veloY = VELO_HARD_LIMIT if (veloY > VELO_HARD_LIMIT) veloY = VELO_HARD_LIMIT
moveDelta.x = veloX + walkX moveDelta.set(velocity + Vector2(walkX, walkY))
moveDelta.y = veloY + walkY
if (!physSleep) { if (!physSleep) {
// Set 'next' position (hitbox) to fiddle with // Set 'next' position (hitbox) from canonical and walking velocity
setNewNextHitbox() setNewNextHitbox()
applyNormalForce()
/** /**
* Solve collision * solveCollision()?
* If and only if: * If and only if:
* This body is NON-STATIC and the other body is STATIC * This body is NON-STATIC and the other body is STATIC
*/ */
applyNormalForce() displaceByCCD()
adjustHit()
setHorizontalFriction() setHorizontalFriction()
if (isPlayerNoClip) setVerticalFriction() if (isPlayerNoClip) // or hanging on the rope, etc.
//} setVerticalFriction()
// apply our compensation to actual hitbox // apply our compensation to actual hitbox
updateHitboxX() updateHitbox()
updateHitboxY()
// make sure the actor does not go out of the map // make sure the actor does not go out of the map
clampNextHitbox()
clampHitbox() clampHitbox()
} }
walledLeft = isColliding(CONTACT_AREA_LEFT, -1, 0) walledLeft = isColliding(COLLIDING_LEFT)
walledRight = isColliding(CONTACT_AREA_RIGHT, 1, 0) walledRight = isColliding(COLLIDING_RIGHT)
} }
} }
/** /**
* Apply gravitation to the every falling body (unless not levitating) * Apply gravitation to the every falling body (unless not levitating)
* *
* Apply only if not grounded; normal force is not implemented (and redundant) * Apply only if not grounded; normal force is precessed separately.
* so we manually reset G to zero (not applying G. force) if grounded.
*/ */
private fun applyGravitation() { private fun applyGravitation() {
if (!grounded) { if (!grounded) {//(!isColliding(COLLIDING_BOTTOM)) { // or !grounded
/** /**
* weight; gravitational force in action * weight; gravitational force in action
* W = mass * G (9.8 [m/s^2]) * W = mass * G (9.8 [m/s^2])
@@ -333,9 +327,9 @@ open class ActorWithBody : Actor(), Visible {
val A: Double = scale * scale val A: Double = scale * scale
/** /**
* Drag of atmosphere * Drag of atmosphere
* D = Cd (drag coefficient) * 0.5 * rho (density) * V^2 (velocity) * A (area) * D = Cd (drag coefficient) * 0.5 * rho (density) * V^2 (velocity sqr) * A (area)
*/ */
val D: Vector2 = velocity * DRAG_COEFF * 0.5 * A * tileDensityFluid.toDouble() val D: Vector2 = velocity * DRAG_COEFF * 0.5 * A// * tileDensityFluid.toDouble()
val V: Vector2 = (W - D) / mass * SI_TO_GAME_ACC val V: Vector2 = (W - D) / mass * SI_TO_GAME_ACC
@@ -343,8 +337,218 @@ open class ActorWithBody : Actor(), Visible {
} }
} }
private fun applyNormalForce() {
if (!isNoCollideWorld) {
// axis Y
if (moveDelta.y >= 0.0) { // check downward
//FIXME "isColliding" (likely the newer one) is the perkeleen vittupää
if (isColliding(COLLIDING_UD)) {
// the actor is hitting the ground
hitAndReflectY()
grounded = true
}
else { // the actor is not grounded at all
grounded = false
}
}
else if (moveDelta.y < 0.0) { // check upward
grounded = false
if (isColliding(COLLIDING_UD)) {
// the actor is hitting the ceiling
hitAndReflectY()
}
else { // the actor is not grounded at all
}
}
// axis X
if (isColliding(COLLIDING_LR) && moveDelta.x != 0.0) { // check right and left
// the actor is hitting the wall
hitAndReflectX()
}
}
}
/**
* nextHitbox must NOT be altered before this method is called!
*/
private fun displaceByCCD() {
if (!isNoCollideWorld){
// vector, toward the previous position; implies linear interpolation
val deltaNegative = Vector2(hitbox.toVector() - nextHitbox.toVector()) // we need to traverse back, so may as well negate at the first place
val ccdDelta = if (deltaNegative.magnitudeSquared > CCD_TICK.sqr())
deltaNegative.setMagnitude(CCD_TICK)
else
deltaNegative
var ccdCount = 0
// Q&D fix: quantise hitbox by direction
/*if (deltaNegative.y < 0) {
if (deltaNegative.x > 0)
nextHitbox.setPosition(
nextHitbox.posX.floor(),
nextHitbox.endPointY.ceil().minus(nextHitbox.height)
)
else if (deltaNegative.x < 0)
nextHitbox.setPosition(
nextHitbox.endPointX.ceil().minus(nextHitbox.width),
nextHitbox.endPointY.ceil().minus(nextHitbox.height)
)
}
else if (deltaNegative.y > 0) {
if (deltaNegative.x > 0)
nextHitbox.setPosition(
nextHitbox.posX.floor(),
nextHitbox.posY.floor()
)
else if (deltaNegative.x < 0)
nextHitbox.setPosition(
nextHitbox.endPointX.ceil().minus(nextHitbox.width),
nextHitbox.posY.floor()
)
}*/
while (isColliding(COLLIDING_ALLSIDE) && ccdCount < CCD_TRY_MAX) { // no thresholding?
nextHitbox.translate(ccdDelta)
ccdCount += 1
}
if (ccdCount > 0) {
println("Displacement: ${ccdDelta.magnitude * ccdCount} ($ccdCount times)")
}
}
}
private fun hitAndReflectX() {
if ((veloX * elasticity).abs() > Epsilon.E) {
veloX *= -elasticity
walkX *= -elasticity
}
else {
veloX = 0.0
walkX = 0.0
}
}
private fun hitAndReflectY() {
if ((veloY * elasticity).abs() > Epsilon.E) {
veloY *= -elasticity
walkY *= -elasticity
}
else {
veloY = 0.0
walkY *= 0.0
}
}
private fun isColliding() = isColliding(0)
private fun isColliding(option: Int): Boolean {
if (isNoCollideWorld) return false
// offsets will stretch and shrink detection box according to the argument
val x1: Double; val x2: Double; val y1: Double; val y2: Double
if (option == COLLIDING_LR || option == COLLIDING_UD) {
val offsetX = if (option == COLLIDING_LR) A_PIXEL else 0.0
val offsetY = if (option == COLLIDING_UD) A_PIXEL else 0.0
x1 = nextHitbox.posX - offsetX + offsetY
x2 = nextHitbox.posX + offsetX - offsetY + nextHitbox.width
y1 = nextHitbox.posY + offsetX - offsetY
y2 = nextHitbox.posY - offsetX + offsetY + nextHitbox.height
}
else {
if (option == COLLIDING_LEFT) {
x1 = nextHitbox.posX - A_PIXEL
x2 = nextHitbox.posX - A_PIXEL + nextHitbox.width
y1 = nextHitbox.posY + A_PIXEL
y2 = nextHitbox.posY - A_PIXEL + nextHitbox.height
}
else if (option == COLLIDING_RIGHT) {
x1 = nextHitbox.posX + A_PIXEL
x2 = nextHitbox.posX + A_PIXEL + nextHitbox.width
y1 = nextHitbox.posY + A_PIXEL
y2 = nextHitbox.posY - A_PIXEL + nextHitbox.height
}
else if (option == COLLIDING_TOP) {
x1 = nextHitbox.posX + A_PIXEL
x2 = nextHitbox.posX - A_PIXEL + nextHitbox.width
y1 = nextHitbox.posY - A_PIXEL
y2 = nextHitbox.posY - A_PIXEL + nextHitbox.height
}
else if (option == COLLIDING_BOTTOM) {
x1 = nextHitbox.posX + A_PIXEL
x2 = nextHitbox.posX - A_PIXEL + nextHitbox.width
y1 = nextHitbox.posY + A_PIXEL
y2 = nextHitbox.posY + A_PIXEL + nextHitbox.height
}
else {
x1 = nextHitbox.posX
x2 = nextHitbox.posX + nextHitbox.width
y1 = nextHitbox.posY
y2 = nextHitbox.posY + nextHitbox.height
}
}
val txStart = x1.div(TSIZE).floorInt()
val txEnd = x2.div(TSIZE).floorInt()
val tyStart = y1.div(TSIZE).floorInt()
val tyEnd = y2.div(TSIZE).floorInt()
for (y in tyStart..tyEnd) {
for (x in txStart..txEnd) {
val tile = map.getTileFromTerrain(x, y)
if (TilePropCodex.getProp(tile).isSolid)
return true
}
}
return false
}
private fun getContactingAreaFluid(side: Int, translateX: Int = 0, translateY: Int = 0): Int {
var contactAreaCounter = 0
for (i in 0..(if (side % 2 == 0) nextHitbox.width else nextHitbox.height).roundInt() - 1) {
// set tile positions
val tileX: Int
val tileY: Int
if (side == COLLIDING_LEFT) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxStart.x.roundInt()
+ i + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxEnd.y.roundInt() + translateY)
}
else if (side == COLLIDING_TOP) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxStart.x.roundInt()
+ i + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxStart.y.roundInt() + translateY)
}
else if (side == COLLIDING_RIGHT) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxEnd.x.roundInt() + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxStart.y.roundInt()
+ i + translateY)
}
else if (side == COLLIDING_LEFT) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxStart.x.roundInt() + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxStart.y.roundInt()
+ i + translateY)
}
else {
throw IllegalArgumentException(side.toString() + ": Wrong side input")
}
// evaluate
if (TilePropCodex.getProp(map.getTileFromTerrain(tileX, tileY)).isFluid) {
contactAreaCounter += 1
}
}
return contactAreaCounter
}
private fun setHorizontalFriction() { private fun setHorizontalFriction() {
val friction = BASE_FRICTION * tileFriction.tileFrictionToMult() val friction = if (isPlayerNoClip)
BASE_FRICTION * TilePropCodex.getProp(TileNameCode.STONE).friction.tileFrictionToMult()
else
BASE_FRICTION * tileFriction.tileFrictionToMult()
if (veloX < 0) { if (veloX < 0) {
veloX += friction veloX += friction
@@ -366,7 +570,10 @@ open class ActorWithBody : Actor(), Visible {
} }
private fun setVerticalFriction() { private fun setVerticalFriction() {
val friction = BASE_FRICTION * tileFriction.tileFrictionToMult() val friction = if (isPlayerNoClip)
BASE_FRICTION * TilePropCodex.getProp(TileNameCode.STONE).friction.tileFrictionToMult()
else
BASE_FRICTION * tileFriction.tileFrictionToMult()
if (veloY < 0) { if (veloY < 0) {
veloY += friction veloY += friction
@@ -387,351 +594,6 @@ open class ActorWithBody : Actor(), Visible {
} }
} }
private fun updateVerticalCollision() {
if (!isNoCollideWorld) {
if (veloY >= 0) { // check downward
if (isColliding(CONTACT_AREA_BOTTOM)) { // the ground has dug into the body
adjustHitBottom()
veloY = 0.0 // reset veloY, simulating normal force
hitAndReflectY()
grounded = true
}
else if (isColliding(CONTACT_AREA_BOTTOM, 0, 1)) { // the actor is standing ON the ground
veloY = 0.0 // reset veloY, simulating normal force
hitAndReflectY()
grounded = true
}
else { // the actor is not grounded at all
grounded = false
}
}
else if (veloY < 0) { // check upward
grounded = false
if (isColliding(CONTACT_AREA_TOP)) { // the ceiling has dug into the body
adjustHitTop()
veloY = 0.0 // reset veloY, simulating normal force
hitAndReflectY()
}
else if (isColliding(CONTACT_AREA_TOP, 0, -1)) { // the actor is touching the ceiling
veloY = 0.0 // reset veloY, simulating normal force
hitAndReflectY() // reflect on ceiling, for reversed gravity
}
else { // the actor is not grounded at all
}
}
}
}
private fun adjustHitBottom() {
val newX = nextHitbox.pointedX // look carefully, getPos or getPointed
// int-ify posY of nextHitbox
nextHitbox.setPositionYFromPoint(Math.floor(nextHitbox.pointedY).toDouble())
var newYOff = 0 // always positive
// count up Y offset until the actor is not touching the ground
var colliding: Boolean
do {
newYOff += 1
colliding = isColliding(CONTACT_AREA_BOTTOM, 0, -newYOff)
} while (colliding)
posAdjustY = -newYOff
val newY = nextHitbox.pointedY - newYOff
nextHitbox.setPositionFromPoint(newX, newY)
}
private fun adjustHitTop() {
val newX = nextHitbox.posX
// int-ify posY of nextHitbox
nextHitbox.setPositionY(Math.ceil(nextHitbox.posY).toDouble())
var newYOff = 0 // always positive
// count up Y offset until the actor is not touching the ceiling
var colliding: Boolean
do {
newYOff += 1
colliding = isColliding(CONTACT_AREA_TOP, 0, newYOff)
} while (colliding)
posAdjustY = newYOff
val newY = nextHitbox.posY + newYOff
nextHitbox.setPosition(newX, newY)
}
private fun updateHorizontalCollision() {
if (!isNoCollideWorld) {
if (veloX >= 0.5) { // check right
if (isColliding(CONTACT_AREA_RIGHT) && !isColliding(CONTACT_AREA_LEFT)) {
// the actor is embedded to the wall
adjustHitRight()
veloX = 0.0 // reset veloX, simulating normal force
hitAndReflectX()
}
else if (isColliding(CONTACT_AREA_RIGHT, 2, 0) && !isColliding(CONTACT_AREA_LEFT, 0, 0)) { // offset by +1, to fix directional quarks
// the actor is touching the wall
veloX = 0.0 // reset veloX, simulating normal force
hitAndReflectX()
}
else {
}
}
else if (veloX <= -0.5) { // check left
// System.out.println("collidingleft");
if (isColliding(CONTACT_AREA_LEFT) && !isColliding(CONTACT_AREA_RIGHT)) {
// the actor is embedded to the wall
adjustHitLeft()
veloX = 0.0 // reset veloX, simulating normal force
hitAndReflectX()
}
else if (isColliding(CONTACT_AREA_LEFT, -1, 0) && !isColliding(CONTACT_AREA_RIGHT, 1, 0)) {
// the actor is touching the wall
veloX = 0.0 // reset veloX, simulating normal force
hitAndReflectX()
}
else {
}
}
else { // check both sides?
// System.out.println("updatehorizontal - |velo| < 0.5");
//if (isColliding(CONTACT_AREA_LEFT) || isColliding(CONTACT_AREA_RIGHT)) {
// veloX = 0.0 // reset veloX, simulating normal force
// elasticReflectX()
//}
}
}
}
private fun adjustHitRight() {
val newY = nextHitbox.posY // look carefully, posY or pointedY
// int-ify posY of nextHitbox
nextHitbox.setPositionX(Math.floor(nextHitbox.posX + nextHitbox.width) - nextHitbox.width)
var newXOff = 0 // always positive
// count up Y offset until the actor is not touching the wall
var colliding: Boolean
do {
newXOff += 1
colliding = isColliding(CONTACT_AREA_BOTTOM, -newXOff + 1, 0) // offset by +1, to fix directional quarks
} while (newXOff < TSIZE && colliding)
val newX = nextHitbox.posX - newXOff // -1: Q&D way to prevent the actor sticking to the wall and won't detach
nextHitbox.setPosition(newX, newY)
}
private fun adjustHitLeft() {
val newY = nextHitbox.posY
// int-ify posY of nextHitbox
nextHitbox.setPositionX(Math.ceil(nextHitbox.posX).toDouble())
var newXOff = 0 // always positive
// count up Y offset until the actor is not touching the wall
var colliding: Boolean
do {
newXOff += 1
colliding = isColliding(CONTACT_AREA_TOP, newXOff, 0)
} while (newXOff < TSIZE && colliding)
posAdjustX = newXOff
val newX = nextHitbox.posX + newXOff // +1: Q&D way to prevent the actor sticking to the wall and won't detach
nextHitbox.setPosition(newX, newY)
}
/**
* nextHitbox must NOT altered before this method is called!
*/
private fun adjustHit() {
if (!isNoCollideWorld){
val delta: Vector2 = Vector2(hitbox.toVector() - nextHitbox.toVector()) // we need to traverse back, so may as well negate at the first place
val ccdDelta = delta.setMagnitude(CCD_TICK)
val ccdTryMax = 400
var ccdCount = 0
while (((ccdDelta.x.abs() >= SLEEP_THRE) || (ccdDelta.y.abs() >= SLEEP_THRE))
&& (isColliding(CONTACT_AREA_LEFT) || isColliding(CONTACT_AREA_RIGHT)
|| isColliding(CONTACT_AREA_TOP) || isColliding(CONTACT_AREA_BOTTOM))
&& ccdCount < ccdTryMax
) {
nextHitbox.translate(ccdDelta)
ccdCount += 1
}
//}
/*else { // stuck while standing still
// CCD upward
var upwardDelta = 0.0
while (isColliding(CONTACT_AREA_LEFT) || isColliding(CONTACT_AREA_RIGHT)
|| isColliding(CONTACT_AREA_TOP) || isColliding(CONTACT_AREA_BOTTOM)
) {
nextHitbox.translate(0.0, -CCD_TICK)
upwardDelta += CCD_TICK
if (upwardDelta >= TSIZE) break
/* TODO CCD in order of:
.. 10 11 12 13 14 ..
.. 08 03 04 05 09 ..
.. 06 01 [] 02 07 ..
until the stucking is resolved
*/
}
}*/
}
}
private fun applyNormalForce() {
if (!isNoCollideWorld) {
// axis Y
if (moveDelta.y > SLEEP_THRE) { // check downward
if (isColliding(CONTACT_AREA_BOTTOM) || isColliding(CONTACT_AREA_BOTTOM, 0, 1)) {
// the actor is hitting the ground
hitAndReflectY()
grounded = true
}
else { // the actor is not grounded at all
grounded = false
}
}
else if (moveDelta.y < SLEEP_THRE) { // check upward
grounded = false
if (isColliding(CONTACT_AREA_TOP) || isColliding(CONTACT_AREA_TOP, 0, -1)) {
// the actor is hitting the ceiling
hitAndReflectY()
}
else { // the actor is not grounded at all
}
}
// axis X
if (moveDelta.x > SLEEP_THRE) { // check right
if ((isColliding(CONTACT_AREA_RIGHT) && !isColliding(CONTACT_AREA_LEFT))
|| (isColliding(CONTACT_AREA_RIGHT, 1, 0) && !isColliding(CONTACT_AREA_LEFT, 0, -1))) {
// the actor is hitting the right wall
hitAndReflectX()
}
else {
}
}
else if (moveDelta.x < SLEEP_THRE) { // check left
// System.out.println("collidingleft");
if ((isColliding(CONTACT_AREA_LEFT) && !isColliding(CONTACT_AREA_RIGHT))
|| (isColliding(CONTACT_AREA_LEFT, -1, 0) && !isColliding(CONTACT_AREA_RIGHT, 1, 0))) {
// the actor is hitting the left wall
hitAndReflectX()
}
else {
}
}
else {
}
}
}
private fun hitAndReflectX() {
if ((veloX * elasticity).abs() > SLEEP_THRE) {
veloX *= -elasticity
walkX *= -elasticity
}
else {
veloX = 0.0
walkX = 0.0
}
}
private fun hitAndReflectY() {
if ((veloY * elasticity).abs() > SLEEP_THRE) {
veloY *= -elasticity
walkY *= -elasticity
}
else {
veloY = 0.0
walkY *= 0.0
}
}
private fun isColliding(side: Int, tx: Int = 0, ty: Int = 0): Boolean = getContactingArea(side, tx, ty) > 1
private fun getContactingArea(side: Int, translateX: Int = 0, translateY: Int = 0): Int {
var contactAreaCounter = 0
for (i in 0..(if (side % 2 == 0) nextHitbox.width else nextHitbox.height).roundToInt() - 1) {
// set tile positions
val tileX: Int
val tileY: Int
if (side == CONTACT_AREA_BOTTOM) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxStart.x.roundToInt()
+ i + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxEnd.y.roundToInt() + translateY)
}
else if (side == CONTACT_AREA_TOP) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxStart.x.roundToInt()
+ i + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxStart.y.roundToInt() + translateY)
}
else if (side == CONTACT_AREA_RIGHT) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxEnd.x.roundToInt() + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxStart.y.roundToInt()
+ i + translateY)
}
else if (side == CONTACT_AREA_LEFT) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxStart.x.roundToInt() + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxStart.y.roundToInt()
+ i + translateY)
}
else {
throw IllegalArgumentException(side.toString() + ": Wrong side input")
}
// evaluate
if (TilePropCodex.getProp(map.getTileFromTerrain(tileX, tileY) ?: TileNameCode.STONE).isSolid) {
contactAreaCounter += 1
}
}
return contactAreaCounter
}
private fun getContactingAreaFluid(side: Int, translateX: Int = 0, translateY: Int = 0): Int {
var contactAreaCounter = 0
for (i in 0..(if (side % 2 == 0) nextHitbox.width else nextHitbox.height).roundToInt() - 1) {
// set tile positions
val tileX: Int
val tileY: Int
if (side == CONTACT_AREA_BOTTOM) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxStart.x.roundToInt()
+ i + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxEnd.y.roundToInt() + translateY)
}
else if (side == CONTACT_AREA_TOP) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxStart.x.roundToInt()
+ i + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxStart.y.roundToInt() + translateY)
}
else if (side == CONTACT_AREA_RIGHT) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxEnd.x.roundToInt() + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxStart.y.roundToInt()
+ i + translateY)
}
else if (side == CONTACT_AREA_LEFT) {
tileX = div16TruncateToMapWidth(nextHitbox.hitboxStart.x.roundToInt() + translateX)
tileY = div16TruncateToMapHeight(nextHitbox.hitboxStart.y.roundToInt()
+ i + translateY)
}
else {
throw IllegalArgumentException(side.toString() + ": Wrong side input")
}
// evaluate
if (TilePropCodex.getProp(map.getTileFromTerrain(tileX, tileY)).isFluid) {
contactAreaCounter += 1
}
}
return contactAreaCounter
}
/** /**
* [N] = [kg * m / s^2] * [N] = [kg * m / s^2]
* F(bo) = density * submerged_volume * gravitational_acceleration [N] * F(bo) = density * submerged_volume * gravitational_acceleration [N]
@@ -754,8 +616,8 @@ open class ActorWithBody : Actor(), Visible {
private val submergedHeight: Double private val submergedHeight: Double
get() = Math.max( get() = Math.max(
getContactingAreaFluid(CONTACT_AREA_LEFT), getContactingAreaFluid(COLLIDING_LEFT),
getContactingAreaFluid(CONTACT_AREA_RIGHT) getContactingAreaFluid(COLLIDING_RIGHT)
).toDouble()*/ ).toDouble()*/
@@ -768,9 +630,9 @@ open class ActorWithBody : Actor(), Visible {
var friction = 0 var friction = 0
// take highest value // take highest value
val tilePosXStart = (hitbox.posX / TSIZE).roundToInt() val tilePosXStart = (hitbox.posX / TSIZE).roundInt()
val tilePosXEnd = (hitbox.hitboxEnd.x / TSIZE).roundToInt() val tilePosXEnd = (hitbox.hitboxEnd.x / TSIZE).roundInt()
val tilePosY = (hitbox.pointedY.plus(1) / TSIZE).roundToInt() val tilePosY = (hitbox.pointedY.plus(1) / TSIZE).roundInt()
for (x in tilePosXStart..tilePosXEnd) { for (x in tilePosXStart..tilePosXEnd) {
val tile = map.getTileFromTerrain(x, tilePosY) val tile = map.getTileFromTerrain(x, tilePosY)
val thisFriction = TilePropCodex.getProp(tile).friction val thisFriction = TilePropCodex.getProp(tile).friction
@@ -790,10 +652,10 @@ open class ActorWithBody : Actor(), Visible {
var density = 0 var density = 0
// take highest value // take highest value
val tilePosXStart = (hitbox.posX / TSIZE).roundToInt() val tilePosXStart = (hitbox.posX / TSIZE).roundInt()
val tilePosXEnd = (hitbox.hitboxEnd.x / TSIZE).roundToInt() val tilePosXEnd = (hitbox.hitboxEnd.x / TSIZE).roundInt()
val tilePosYStart = (hitbox.posY / TSIZE).roundToInt() val tilePosYStart = (hitbox.posY / TSIZE).roundInt()
val tilePosYEnd = (hitbox.hitboxEnd.y / TSIZE).roundToInt() val tilePosYEnd = (hitbox.hitboxEnd.y / TSIZE).roundInt()
for (y in tilePosXStart..tilePosYEnd) { for (y in tilePosXStart..tilePosYEnd) {
for (x in tilePosXStart..tilePosXEnd) { for (x in tilePosXStart..tilePosXEnd) {
val tile = map.getTileFromTerrain(x, y) val tile = map.getTileFromTerrain(x, y)
@@ -815,10 +677,10 @@ open class ActorWithBody : Actor(), Visible {
var density = 0 var density = 0
//get highest fluid density //get highest fluid density
val tilePosXStart = (nextHitbox.posX / TSIZE).roundToInt() val tilePosXStart = (nextHitbox.posX / TSIZE).roundInt()
val tilePosYStart = (nextHitbox.posY / TSIZE).roundToInt() val tilePosYStart = (nextHitbox.posY / TSIZE).roundInt()
val tilePosXEnd = (nextHitbox.hitboxEnd.x / TSIZE).roundToInt() val tilePosXEnd = (nextHitbox.hitboxEnd.x / TSIZE).roundInt()
val tilePosYEnd = (nextHitbox.hitboxEnd.y / TSIZE).roundToInt() val tilePosYEnd = (nextHitbox.hitboxEnd.y / TSIZE).roundInt()
for (y in tilePosYStart..tilePosYEnd) { for (y in tilePosYStart..tilePosYEnd) {
for (x in tilePosXStart..tilePosXEnd) { for (x in tilePosXStart..tilePosXEnd) {
val tile = map.getTileFromTerrain(x, y) val tile = map.getTileFromTerrain(x, y)
@@ -836,34 +698,16 @@ open class ActorWithBody : Actor(), Visible {
clampW(hitbox.pointedX), clampH(hitbox.pointedY)) clampW(hitbox.pointedX), clampH(hitbox.pointedY))
} }
private fun clampNextHitbox() {
nextHitbox.setPositionFromPoint(
clampW(nextHitbox.pointedX), clampH(nextHitbox.pointedY))
}
private fun setNewNextHitbox() { private fun setNewNextHitbox() {
nextHitbox.set( nextHitbox.set(
(hitbox.posX + moveDelta.x) hitbox.posX + moveDelta.x,
, (hitbox.posY + moveDelta.y) hitbox.posY + moveDelta.y,
, (baseHitboxW * scale) baseHitboxW * scale,
, (baseHitboxH * scale) baseHitboxH * scale
) )
} }
private fun updateHitboxX() { private fun updateHitbox() = hitbox.set(nextHitbox)
hitbox.setDimension(
nextHitbox.width, nextHitbox.height)
//if (nextHitbox.posX - hitbox.posX > SLEEP_THRE.abs())
hitbox.setPositionX(nextHitbox.posX)
}
private fun updateHitboxY() {
hitbox.setDimension(
nextHitbox.width, nextHitbox.height)
//if (nextHitbox.posY - hitbox.posY > SLEEP_THRE.abs())
hitbox.setPositionY(nextHitbox.posY)
}
override fun drawGlow(gc: GameContainer, g: Graphics) { override fun drawGlow(gc: GameContainer, g: Graphics) {
if (isVisible && spriteGlow != null) { if (isVisible && spriteGlow != null) {
@@ -958,12 +802,28 @@ open class ActorWithBody : Actor(), Visible {
private val AUTO_CLIMB_RATE: Int private val AUTO_CLIMB_RATE: Int
get() = Math.min(TSIZE / 8 * Math.sqrt(scale), TSIZE.toDouble()).toInt() get() = Math.min(TSIZE / 8 * Math.sqrt(scale), TSIZE.toDouble()).toInt()
fun Double.floorInt() = Math.floor(this).toInt()
fun Double.round() = Math.round(this).toDouble() fun Double.round() = Math.round(this).toDouble()
fun Double.roundToInt(): Int = Math.round(this).toInt() fun Double.floor() = Math.floor(this)
fun Double.ceil() = this.floor() + 1.0
fun Double.roundInt(): Int = Math.round(this).toInt()
fun Double.abs() = Math.abs(this) fun Double.abs() = Math.abs(this)
fun Double.sqr() = this * this fun Double.sqr() = this * this
fun Int.abs() = if (this < 0) -this else this fun Int.abs() = if (this < 0) -this else this
private fun assertInit() {
// errors
if (baseHitboxW == 0 || baseHitboxH == 0)
throw RuntimeException("Hitbox dimension was not set.")
if (sprite == null && isVisible)
throw RuntimeException("Actor ${this.javaClass.canonicalName} is visible but the sprite was not set.")
// warnings
if (!isVisible && sprite != null)
println("[ActorWithBody] Caution: actor ${this.javaClass.canonicalName} is invisible but the sprite was given.")
assertPrinted = true
}
companion object { companion object {
@Transient private val TSIZE = MapDrawer.TILE_SIZE @Transient private val TSIZE = MapDrawer.TILE_SIZE

View File

@@ -30,6 +30,7 @@ class FixturesTikiTorch : FixturesBase(), Luminous {
sprite!!.setAsVisible() sprite!!.setAsVisible()
actorValue[AVKey.BASEMASS] = 1.0 actorValue[AVKey.BASEMASS] = 1.0
actorValue[AVKey.LUMINOSITY] = TilePropCodex.getProp(TileNameCode.TORCH).luminosity
luminosity = TilePropCodex.getProp(TileNameCode.TORCH).luminosity
} }
} }

View File

@@ -7,15 +7,15 @@ import org.dyn4j.geometry.Vector2
/** /**
* Created by minjaesong on 16-01-15. * Created by minjaesong on 16-01-15.
*/ */
class Hitbox(x1: Double, y1: Double, width: Double, height: Double) { class Hitbox(x1: Double, y1: Double, width: Double, height: Double) : Cloneable {
@Volatile var hitboxStart: Point2d @Volatile var hitboxStart: Point2d
private set private set
@Volatile var hitboxEnd: Point2d @Volatile var hitboxEnd: Point2d
private set private set
var width: Double = 0.toDouble() var width: Double = 0.0
private set private set
var height: Double = 0.toDouble() var height: Double = 0.0
private set private set
init { init {
@@ -39,14 +39,16 @@ class Hitbox(x1: Double, y1: Double, width: Double, height: Double) {
val pointedY: Double val pointedY: Double
get() = hitboxEnd.y get() = hitboxEnd.y
val endPointX: Double
get() = hitboxEnd.x
val endPointY: Double
get() = hitboxEnd.y
/** /**
* Set to the point top left * Set to the point top left
* @param x1 * @param x1
* *
* @param y1 * @param y1
* *
* @param width * @param width
* *
* @param height * @param height
*/ */
fun set(x1: Double, y1: Double, width: Double, height: Double) { fun set(x1: Double, y1: Double, width: Double, height: Double) {
@@ -56,6 +58,13 @@ class Hitbox(x1: Double, y1: Double, width: Double, height: Double) {
this.height = height this.height = height
} }
/**
* Set this hitbox from other
*/
fun set(other: Hitbox) {
set(other.posX, other.posY, other.width, other.height)
}
fun translate(x: Double, y: Double) { fun translate(x: Double, y: Double) {
setPosition(posX + x, posY + y) setPosition(posX + x, posY + y)
} }

View File

@@ -75,7 +75,7 @@ class Player : ActorWithBody(), Controllable, Pocketed, Factionable, Luminous, L
get() = Hitbox(0.0, 0.0, hitbox.width, hitbox.height) // use getter; dimension of the player may change by time. get() = Hitbox(0.0, 0.0, hitbox.width, hitbox.height) // use getter; dimension of the player may change by time.
companion object { companion object {
@Transient internal const val ACCEL_MULT_IN_FLIGHT: Double = 0.21 // TODO air control still too 'slippery' with 0.31, lower the value! @Transient internal const val ACCEL_MULT_IN_FLIGHT: Double = 0.21
@Transient internal const val WALK_ACCEL_BASE: Double = 0.67 @Transient internal const val WALK_ACCEL_BASE: Double = 0.67
@Transient const val PLAYER_REF_ID: Int = 0x51621D @Transient const val PLAYER_REF_ID: Int = 0x51621D
@@ -93,6 +93,7 @@ class Player : ActorWithBody(), Controllable, Pocketed, Factionable, Luminous, L
isVisible = true isVisible = true
referenceID = PLAYER_REF_ID // forcibly set ID referenceID = PLAYER_REF_ID // forcibly set ID
super.setDensity(BASE_DENSITY) super.setDensity(BASE_DENSITY)
collisionType = KINEMATIC
} }
override fun update(gc: GameContainer, delta: Int) { override fun update(gc: GameContainer, delta: Int) {

View File

@@ -488,7 +488,7 @@ object LightmapRenderer {
* @param brighten (-1.0 - 1.0) negative means darkening * @param brighten (-1.0 - 1.0) negative means darkening
* @return processed colour * @return processed colour
*/ */
fun brightenUniform(data: Int, brighten: Float): Int { fun alterBrightnessUniform(data: Int, brighten: Float): Int {
val modifier = if (brighten < 0) val modifier = if (brighten < 0)
constructRGBFromFloat(-brighten, -brighten, -brighten) constructRGBFromFloat(-brighten, -brighten, -brighten)
else else

View File

@@ -10,46 +10,68 @@ import net.torvald.terrarum.mapdrawer.LightmapRenderer
* Created by minjaesong on 16-06-16. * Created by minjaesong on 16-06-16.
*/ */
object TilePropUtil { object TilePropUtil {
var flickerFuncX = 0 // in milliseconds; saves current status of func var flickerFuncX = 0 // in milliseconds; saves current status (time) of func
val flickerFuncDomain = 100 // time between two noise sample, in milliseconds val flickerFuncDomain = 100 // time between two noise sample, in milliseconds
val flickerFuncRange = 0.012f // intensity [0, 1] val flickerFuncRange = 0.012f // intensity [0, 1]
val random = HQRNG(); var breathFuncX = 0
var funcY = 0f val breathRange = 0.02f
val breathCycleDuration = 2000 // in milliseconds
var patternThis = getNewRandom() var pulsateFuncX = 0
var patternNext = getNewRandom() val pulsateRange = 0.034f
val pulsateCycleDuration = 500 // in milliseconds
val random = HQRNG();
var flickerPatternThis = getNewRandom()
var flickerPatternNext = getNewRandom()
init { init {
} }
private fun getTorchFlicker(baseLum: Int): Int { private fun getTorchFlicker(baseLum: Int): Int {
funcY = linearInterpolation1D(patternThis, patternNext, val funcY = linearInterpolation1D(flickerPatternThis, flickerPatternNext,
flickerFuncX.toFloat() / flickerFuncDomain flickerFuncX.toFloat() / flickerFuncDomain
) )
return LightmapRenderer.brightenUniform(baseLum, funcY) return LightmapRenderer.alterBrightnessUniform(baseLum, funcY)
} }
private fun getSlowBreath(baseLum: Int): Int { private fun getSlowBreath(baseLum: Int): Int {
return baseLum val funcY = FastMath.sin(FastMath.PI * breathFuncX / breathCycleDuration) * breathRange
return LightmapRenderer.alterBrightnessUniform(baseLum, funcY)
} }
private fun getPulsate(baseLum: Int): Int { private fun getPulsate(baseLum: Int): Int {
return baseLum val funcY = FastMath.sin(FastMath.PI * pulsateFuncX / pulsateCycleDuration) * pulsateRange
return LightmapRenderer.alterBrightnessUniform(baseLum, funcY)
} }
internal fun dynamicLumFuncTickClock() { internal fun dynamicLumFuncTickClock() {
if (Terrarum.appgc.fps > 0) // FPS-time compensation
if (Terrarum.appgc.fps > 0) {
flickerFuncX += 1000 / Terrarum.appgc.fps flickerFuncX += 1000 / Terrarum.appgc.fps
breathFuncX += 1000 / Terrarum.appgc.fps
pulsateFuncX += 1000 / Terrarum.appgc.fps
}
// flicker-related vars
if (flickerFuncX > flickerFuncDomain) { if (flickerFuncX > flickerFuncDomain) {
flickerFuncX -= flickerFuncDomain flickerFuncX -= flickerFuncDomain
patternThis = patternNext flickerPatternThis = flickerPatternNext
patternNext = getNewRandom() flickerPatternNext = getNewRandom()
} }
// breath-related vars
if (breathFuncX > breathCycleDuration) breathFuncX -= breathCycleDuration
// pulsate-related vars
if (pulsateFuncX > pulsateCycleDuration) pulsateFuncX -= pulsateCycleDuration
} }
private fun getNewRandom() = random.nextFloat().times(2).minus(1f) * flickerFuncRange private fun getNewRandom() = random.nextFloat().times(2).minus(1f) * flickerFuncRange

View File

@@ -123,7 +123,7 @@
## Notes ## ## Notes ##
# Friction: 0: frictionless, <16: slippery, 16: regular, >16: sticky # Friction: 0: frictionless, <16: slippery, 16: regular, >16: sticky
# Opacity/Lumcolor: 30-bit RGB # Opacity/Lumcolor: 30-bit RGB. Light diffusers have a value of ZERO.
# Solid: whether the tile has full collision # Solid: whether the tile has full collision
# movr: Movement resistance, (walkspeedmax) / (1 + (n/16)), 16 halves movement speed # movr: Movement resistance, (walkspeedmax) / (1 + (n/16)), 16 halves movement speed
# dsty: density. As we are putting water an 1000, it is identical to specific gravity. [g/l] # dsty: density. As we are putting water an 1000, it is identical to specific gravity. [g/l]
Can't render this file because it contains an unexpected character in line 1 and column 18.

View File

@@ -66,10 +66,10 @@ import org.dyn4j.Epsilon
class Vector2 { class Vector2 {
/** The magnitude of the x component of this [Vector2] */ /** The magnitude of the x component of this [Vector2] */
var x: Double = 0.toDouble() var x: Double = 0.0
/** The magnitude of the y component of this [Vector2] */ /** The magnitude of the y component of this [Vector2] */
var y: Double = 0.toDouble() var y: Double = 0.0
/** Default constructor. */ /** Default constructor. */
constructor() { constructor() {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 503 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 497 KiB