Regular movement code port from original engine
This commit is contained in:
parent
2b94bfd41f
commit
4db69db0cd
@ -82,7 +82,7 @@ dependencies {
|
|||||||
implementation("net.java.dev.jna:jna:5.13.0")
|
implementation("net.java.dev.jna:jna:5.13.0")
|
||||||
implementation("com.github.jnr:jnr-ffi:2.2.13")
|
implementation("com.github.jnr:jnr-ffi:2.2.13")
|
||||||
|
|
||||||
implementation("ru.dbotthepony:kvector:2.11.1")
|
implementation("ru.dbotthepony:kvector:2.12.0")
|
||||||
|
|
||||||
implementation("com.github.ben-manes.caffeine:caffeine:3.1.5")
|
implementation("com.github.ben-manes.caffeine:caffeine:3.1.5")
|
||||||
implementation("org.classdump.luna:luna-all-shaded:0.4.1")
|
implementation("org.classdump.luna:luna-all-shaded:0.4.1")
|
||||||
|
@ -3,7 +3,7 @@ package ru.dbotthepony.kstarbound
|
|||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
import ru.dbotthepony.kstarbound.defs.ClientConfigParameters
|
import ru.dbotthepony.kstarbound.defs.ClientConfigParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
import ru.dbotthepony.kstarbound.defs.MovementParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.player.PlayerMovementParameters
|
import ru.dbotthepony.kstarbound.defs.PlayerMovementParameters
|
||||||
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
import ru.dbotthepony.kstarbound.util.AssetPathStack
|
||||||
import java.util.concurrent.ForkJoinPool
|
import java.util.concurrent.ForkJoinPool
|
||||||
import java.util.concurrent.ForkJoinTask
|
import java.util.concurrent.ForkJoinTask
|
||||||
|
@ -129,7 +129,7 @@ fun main() {
|
|||||||
|
|
||||||
val rand = Random()
|
val rand = Random()
|
||||||
|
|
||||||
for (i in 0 until 0) {
|
for (i in 0 until 128) {
|
||||||
val item = ItemEntity(client.world!!, Registries.items.keys.values.random().value)
|
val item = ItemEntity(client.world!!, Registries.items.keys.values.random().value)
|
||||||
|
|
||||||
item.position = Vector2d(225.0 - i, 785.0)
|
item.position = Vector2d(225.0 - i, 785.0)
|
||||||
|
@ -5,94 +5,97 @@ import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
|||||||
import ru.dbotthepony.kstarbound.json.builder.JsonImplementation
|
import ru.dbotthepony.kstarbound.json.builder.JsonImplementation
|
||||||
import ru.dbotthepony.kstarbound.util.KOptional
|
import ru.dbotthepony.kstarbound.util.KOptional
|
||||||
|
|
||||||
|
val BaseMovementParameters.ignorePlatformCollision get() = if (this is MovementParameters) this.ignorePlatformCollision ?: false else false
|
||||||
|
val BaseMovementParameters.restDuration get() = if (this is MovementParameters) this.restDuration ?: 0 else 0
|
||||||
|
|
||||||
@JsonImplementation(BaseMovementParameters.Impl::class)
|
@JsonImplementation(BaseMovementParameters.Impl::class)
|
||||||
interface BaseMovementParameters {
|
sealed interface BaseMovementParameters {
|
||||||
val mass: KOptional<Double>
|
val mass: Double?
|
||||||
val gravityMultiplier: KOptional<Double>
|
val gravityMultiplier: Double?
|
||||||
val liquidBuoyancy: KOptional<Double>
|
val liquidBuoyancy: Double?
|
||||||
val airBuoyancy: KOptional<Double>
|
val airBuoyancy: Double?
|
||||||
val bounceFactor: KOptional<Double>
|
val bounceFactor: Double?
|
||||||
|
|
||||||
// If set to true, during an update that has more than one internal movement
|
// If set to true, during an update that has more than one internal movement
|
||||||
// step, the movement will stop on the first bounce.
|
// step, the movement will stop on the first bounce.
|
||||||
val stopOnFirstBounce: KOptional<Boolean>
|
val stopOnFirstBounce: Boolean?
|
||||||
|
|
||||||
// Cheat when sliding on the ground, by trying to correct upwards before
|
// Cheat when sliding on the ground, by trying to correct upwards before
|
||||||
// other directions (within a set limit). Allows smooth sliding along
|
// other directions (within a set limit). Allows smooth sliding along
|
||||||
// horizontal ground without losing horizontal speed.
|
// horizontal ground without losing horizontal speed.
|
||||||
val enableSurfaceSlopeCorrection: KOptional<Boolean>
|
val enableSurfaceSlopeCorrection: Boolean?
|
||||||
val slopeSlidingFactor: KOptional<Double>
|
val slopeSlidingFactor: Double?
|
||||||
|
|
||||||
// ignored
|
// ignored
|
||||||
val maxMovementPerStep: KOptional<Double>
|
val maxMovementPerStep: Double?
|
||||||
val maximumCorrection: KOptional<Double>
|
val maximumCorrection: Double?
|
||||||
val speedLimit: KOptional<Double>
|
val speedLimit: Double?
|
||||||
|
|
||||||
val stickyCollision: KOptional<Boolean>
|
val stickyCollision: Boolean?
|
||||||
val stickyForce: KOptional<Double>
|
val stickyForce: Double?
|
||||||
|
|
||||||
val airFriction: KOptional<Double>
|
val airFriction: Double?
|
||||||
val liquidFriction: KOptional<Double>
|
val liquidFriction: Double?
|
||||||
val groundFriction: KOptional<Double>
|
val groundFriction: Double?
|
||||||
|
|
||||||
val collisionEnabled: KOptional<Boolean>
|
val collisionEnabled: Boolean?
|
||||||
val frictionEnabled: KOptional<Boolean>
|
val frictionEnabled: Boolean?
|
||||||
val gravityEnabled: KOptional<Boolean>
|
val gravityEnabled: Boolean?
|
||||||
|
|
||||||
val maximumPlatformCorrection: KOptional<Double>
|
val maximumPlatformCorrection: Double?
|
||||||
val maximumPlatformCorrectionVelocityFactor: KOptional<Double>
|
val maximumPlatformCorrectionVelocityFactor: Double?
|
||||||
|
|
||||||
val physicsEffectCategories: KOptional<ImmutableSet<String>>
|
val physicsEffectCategories: ImmutableSet<String>?
|
||||||
|
|
||||||
@JsonFactory
|
@JsonFactory
|
||||||
data class Impl(
|
data class Impl(
|
||||||
override val mass: KOptional<Double> = KOptional.empty(),
|
override val mass: Double? = null,
|
||||||
override val gravityMultiplier: KOptional<Double> = KOptional.empty(),
|
override val gravityMultiplier: Double? = null,
|
||||||
override val liquidBuoyancy: KOptional<Double> = KOptional.empty(),
|
override val liquidBuoyancy: Double? = null,
|
||||||
override val airBuoyancy: KOptional<Double> = KOptional.empty(),
|
override val airBuoyancy: Double? = null,
|
||||||
override val bounceFactor: KOptional<Double> = KOptional.empty(),
|
override val bounceFactor: Double? = null,
|
||||||
override val stopOnFirstBounce: KOptional<Boolean> = KOptional.empty(),
|
override val stopOnFirstBounce: Boolean? = null,
|
||||||
override val enableSurfaceSlopeCorrection: KOptional<Boolean> = KOptional.empty(),
|
override val enableSurfaceSlopeCorrection: Boolean? = null,
|
||||||
override val slopeSlidingFactor: KOptional<Double> = KOptional.empty(),
|
override val slopeSlidingFactor: Double? = null,
|
||||||
override val maxMovementPerStep: KOptional<Double> = KOptional.empty(),
|
override val maxMovementPerStep: Double? = null,
|
||||||
override val maximumCorrection: KOptional<Double> = KOptional.empty(),
|
override val maximumCorrection: Double? = null,
|
||||||
override val speedLimit: KOptional<Double> = KOptional.empty(),
|
override val speedLimit: Double? = null,
|
||||||
override val stickyCollision: KOptional<Boolean> = KOptional.empty(),
|
override val stickyCollision: Boolean? = null,
|
||||||
override val stickyForce: KOptional<Double> = KOptional.empty(),
|
override val stickyForce: Double? = null,
|
||||||
override val airFriction: KOptional<Double> = KOptional.empty(),
|
override val airFriction: Double? = null,
|
||||||
override val liquidFriction: KOptional<Double> = KOptional.empty(),
|
override val liquidFriction: Double? = null,
|
||||||
override val groundFriction: KOptional<Double> = KOptional.empty(),
|
override val groundFriction: Double? = null,
|
||||||
override val collisionEnabled: KOptional<Boolean> = KOptional.empty(),
|
override val collisionEnabled: Boolean? = null,
|
||||||
override val frictionEnabled: KOptional<Boolean> = KOptional.empty(),
|
override val frictionEnabled: Boolean? = null,
|
||||||
override val gravityEnabled: KOptional<Boolean> = KOptional.empty(),
|
override val gravityEnabled: Boolean? = null,
|
||||||
override val maximumPlatformCorrection: KOptional<Double> = KOptional.empty(),
|
override val maximumPlatformCorrection: Double? = null,
|
||||||
override val maximumPlatformCorrectionVelocityFactor: KOptional<Double> = KOptional.empty(),
|
override val maximumPlatformCorrectionVelocityFactor: Double? = null,
|
||||||
override val physicsEffectCategories: KOptional<ImmutableSet<String>> = KOptional.empty(),
|
override val physicsEffectCategories: ImmutableSet<String>? = null,
|
||||||
) : BaseMovementParameters {
|
) : BaseMovementParameters {
|
||||||
fun merge(other: Impl): Impl {
|
fun merge(other: Impl): Impl {
|
||||||
return Impl(
|
return Impl(
|
||||||
mass = mass.or(other.mass),
|
mass = mass ?: other.mass,
|
||||||
gravityMultiplier = gravityMultiplier.or(other.gravityMultiplier),
|
gravityMultiplier = gravityMultiplier ?: other.gravityMultiplier,
|
||||||
liquidBuoyancy = liquidBuoyancy.or(other.liquidBuoyancy),
|
liquidBuoyancy = liquidBuoyancy ?: other.liquidBuoyancy,
|
||||||
airBuoyancy = airBuoyancy.or(other.airBuoyancy),
|
airBuoyancy = airBuoyancy ?: other.airBuoyancy,
|
||||||
bounceFactor = bounceFactor.or(other.bounceFactor),
|
bounceFactor = bounceFactor ?: other.bounceFactor,
|
||||||
stopOnFirstBounce = stopOnFirstBounce.or(other.stopOnFirstBounce),
|
stopOnFirstBounce = stopOnFirstBounce ?: other.stopOnFirstBounce,
|
||||||
enableSurfaceSlopeCorrection = enableSurfaceSlopeCorrection.or(other.enableSurfaceSlopeCorrection),
|
enableSurfaceSlopeCorrection = enableSurfaceSlopeCorrection ?: other.enableSurfaceSlopeCorrection,
|
||||||
slopeSlidingFactor = slopeSlidingFactor.or(other.slopeSlidingFactor),
|
slopeSlidingFactor = slopeSlidingFactor ?: other.slopeSlidingFactor,
|
||||||
maxMovementPerStep = maxMovementPerStep.or(other.maxMovementPerStep),
|
maxMovementPerStep = maxMovementPerStep ?: other.maxMovementPerStep,
|
||||||
maximumCorrection = maximumCorrection.or(other.maximumCorrection),
|
maximumCorrection = maximumCorrection ?: other.maximumCorrection,
|
||||||
speedLimit = speedLimit.or(other.speedLimit),
|
speedLimit = speedLimit ?: other.speedLimit,
|
||||||
stickyCollision = stickyCollision.or(other.stickyCollision),
|
stickyCollision = stickyCollision ?: other.stickyCollision,
|
||||||
stickyForce = stickyForce.or(other.stickyForce),
|
stickyForce = stickyForce ?: other.stickyForce,
|
||||||
airFriction = airFriction.or(other.airFriction),
|
airFriction = airFriction ?: other.airFriction,
|
||||||
liquidFriction = liquidFriction.or(other.liquidFriction),
|
liquidFriction = liquidFriction ?: other.liquidFriction,
|
||||||
groundFriction = groundFriction.or(other.groundFriction),
|
groundFriction = groundFriction ?: other.groundFriction,
|
||||||
collisionEnabled = collisionEnabled.or(other.collisionEnabled),
|
collisionEnabled = collisionEnabled ?: other.collisionEnabled,
|
||||||
frictionEnabled = frictionEnabled.or(other.frictionEnabled),
|
frictionEnabled = frictionEnabled ?: other.frictionEnabled,
|
||||||
gravityEnabled = gravityEnabled.or(other.gravityEnabled),
|
gravityEnabled = gravityEnabled ?: other.gravityEnabled,
|
||||||
maximumPlatformCorrection = maximumPlatformCorrection.or(other.maximumPlatformCorrection),
|
maximumPlatformCorrection = maximumPlatformCorrection ?: other.maximumPlatformCorrection,
|
||||||
maximumPlatformCorrectionVelocityFactor = maximumPlatformCorrectionVelocityFactor.or(other.maximumPlatformCorrectionVelocityFactor),
|
maximumPlatformCorrectionVelocityFactor = maximumPlatformCorrectionVelocityFactor ?: other.maximumPlatformCorrectionVelocityFactor,
|
||||||
physicsEffectCategories = physicsEffectCategories.or(other.physicsEffectCategories),
|
physicsEffectCategories = physicsEffectCategories ?: other.physicsEffectCategories,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,18 +10,18 @@ data class MovementParameters(
|
|||||||
@JsonFlat
|
@JsonFlat
|
||||||
val base: BaseMovementParameters.Impl = BaseMovementParameters.Impl(),
|
val base: BaseMovementParameters.Impl = BaseMovementParameters.Impl(),
|
||||||
|
|
||||||
val discontinuityThreshold: KOptional<Float> = KOptional.empty(),
|
val discontinuityThreshold: Float? = null,
|
||||||
val collisionPoly: KOptional<Poly> = KOptional.empty(),
|
val collisionPoly: Poly? = null,
|
||||||
val ignorePlatformCollision: KOptional<Boolean> = KOptional.empty(),
|
val ignorePlatformCollision: Boolean? = null,
|
||||||
val restDuration: KOptional<Int> = KOptional.empty(),
|
val restDuration: Int? = null,
|
||||||
) : BaseMovementParameters by base {
|
) : BaseMovementParameters by base {
|
||||||
fun merge(other: MovementParameters): MovementParameters {
|
fun merge(other: MovementParameters): MovementParameters {
|
||||||
return MovementParameters(
|
return MovementParameters(
|
||||||
base = base.merge(other.base),
|
base = base.merge(other.base),
|
||||||
discontinuityThreshold = discontinuityThreshold.or(other.discontinuityThreshold),
|
discontinuityThreshold = discontinuityThreshold ?: other.discontinuityThreshold,
|
||||||
collisionPoly = collisionPoly.or(other.collisionPoly),
|
collisionPoly = collisionPoly ?: other.collisionPoly,
|
||||||
ignorePlatformCollision = ignorePlatformCollision.or(other.ignorePlatformCollision),
|
ignorePlatformCollision = ignorePlatformCollision ?: other.ignorePlatformCollision,
|
||||||
restDuration = restDuration.or(other.restDuration),
|
restDuration = restDuration ?: other.restDuration,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
package ru.dbotthepony.kstarbound.defs
|
||||||
|
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonAlias
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
import ru.dbotthepony.kstarbound.json.builder.JsonFlat
|
||||||
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
|
|
||||||
|
@JsonFactory
|
||||||
|
data class PlayerMovementParameters(
|
||||||
|
@JsonFlat
|
||||||
|
val base: BaseMovementParameters.Impl = BaseMovementParameters.Impl(),
|
||||||
|
|
||||||
|
@JsonAlias("collisionPoly")
|
||||||
|
val standingPoly: Poly? = null,
|
||||||
|
@JsonAlias("collisionPoly")
|
||||||
|
val crouchingPoly: Poly? = null,
|
||||||
|
|
||||||
|
val walkSpeed: Double? = null,
|
||||||
|
val runSpeed: Double? = null,
|
||||||
|
val flySpeed: Double? = null,
|
||||||
|
|
||||||
|
val minimumLiquidPercentage: Double? = null,
|
||||||
|
val liquidImpedance: Double? = null,
|
||||||
|
val normalGroundFriction: Double? = null,
|
||||||
|
val ambulatingGroundFriction: Double? = null,
|
||||||
|
val groundForce: Double? = null,
|
||||||
|
val airForce: Double? = null,
|
||||||
|
val liquidForce: Double? = null,
|
||||||
|
|
||||||
|
val airJumpProfile: JumpProfile = JumpProfile(),
|
||||||
|
val liquidJumpProfile: JumpProfile = JumpProfile(),
|
||||||
|
|
||||||
|
val fallStatusSpeedMin: Double? = null,
|
||||||
|
val fallThroughSustainFrames: Int? = null,
|
||||||
|
|
||||||
|
val groundMovementMinimumSustain: Double? = null,
|
||||||
|
val groundMovementMaximumSustain: Double? = null,
|
||||||
|
val groundMovementCheckDistance: Double? = null,
|
||||||
|
|
||||||
|
val pathExploreRate: Double? = null,
|
||||||
|
) : BaseMovementParameters by base {
|
||||||
|
fun merge(other: PlayerMovementParameters): PlayerMovementParameters {
|
||||||
|
return PlayerMovementParameters(
|
||||||
|
base = base.merge(other.base),
|
||||||
|
standingPoly = standingPoly ?: other.standingPoly,
|
||||||
|
crouchingPoly = crouchingPoly ?: other.crouchingPoly,
|
||||||
|
walkSpeed = walkSpeed ?: other.walkSpeed,
|
||||||
|
runSpeed = runSpeed ?: other.runSpeed,
|
||||||
|
flySpeed = flySpeed ?: other.flySpeed,
|
||||||
|
minimumLiquidPercentage = minimumLiquidPercentage ?: other.minimumLiquidPercentage,
|
||||||
|
liquidImpedance = liquidImpedance ?: other.liquidImpedance,
|
||||||
|
normalGroundFriction = normalGroundFriction ?: other.normalGroundFriction,
|
||||||
|
ambulatingGroundFriction = ambulatingGroundFriction ?: other.ambulatingGroundFriction,
|
||||||
|
groundForce = groundForce ?: other.groundForce,
|
||||||
|
airForce = airForce ?: other.airForce,
|
||||||
|
liquidForce = liquidForce ?: other.liquidForce,
|
||||||
|
airJumpProfile = airJumpProfile.merge(other.airJumpProfile),
|
||||||
|
liquidJumpProfile = liquidJumpProfile.merge(other.liquidJumpProfile),
|
||||||
|
fallStatusSpeedMin = fallStatusSpeedMin ?: other.fallStatusSpeedMin,
|
||||||
|
fallThroughSustainFrames = fallThroughSustainFrames ?: other.fallThroughSustainFrames,
|
||||||
|
groundMovementMinimumSustain = groundMovementMinimumSustain ?: other.groundMovementMinimumSustain,
|
||||||
|
groundMovementMaximumSustain = groundMovementMaximumSustain ?: other.groundMovementMaximumSustain,
|
||||||
|
groundMovementCheckDistance = groundMovementCheckDistance ?: other.groundMovementCheckDistance,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@ import ru.dbotthepony.kstarbound.Registry
|
|||||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||||
import ru.dbotthepony.kstarbound.defs.IScriptable
|
import ru.dbotthepony.kstarbound.defs.IScriptable
|
||||||
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
import ru.dbotthepony.kstarbound.defs.IThingWithDescription
|
||||||
import ru.dbotthepony.kstarbound.defs.player.PlayerMovementParameters
|
import ru.dbotthepony.kstarbound.defs.PlayerMovementParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||||
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
import ru.dbotthepony.kstarbound.defs.item.TreasurePoolDefinition
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||||
|
@ -6,6 +6,7 @@ import com.google.common.collect.ImmutableSet
|
|||||||
import com.google.gson.JsonObject
|
import com.google.gson.JsonObject
|
||||||
import ru.dbotthepony.kstarbound.Registry
|
import ru.dbotthepony.kstarbound.Registry
|
||||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||||
|
import ru.dbotthepony.kstarbound.defs.PlayerMovementParameters
|
||||||
import ru.dbotthepony.kstarbound.defs.Species
|
import ru.dbotthepony.kstarbound.defs.Species
|
||||||
import ru.dbotthepony.kstarbound.util.SBPattern
|
import ru.dbotthepony.kstarbound.util.SBPattern
|
||||||
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
import ru.dbotthepony.kstarbound.defs.animation.AnimationDefinition
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
package ru.dbotthepony.kstarbound.defs.player
|
|
||||||
|
|
||||||
import ru.dbotthepony.kstarbound.defs.BaseMovementParameters
|
|
||||||
import ru.dbotthepony.kstarbound.defs.JumpProfile
|
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonAlias
|
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
|
||||||
import ru.dbotthepony.kstarbound.json.builder.JsonFlat
|
|
||||||
import ru.dbotthepony.kstarbound.util.KOptional
|
|
||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
|
||||||
|
|
||||||
@JsonFactory
|
|
||||||
data class PlayerMovementParameters(
|
|
||||||
@JsonFlat
|
|
||||||
val base: BaseMovementParameters.Impl = BaseMovementParameters.Impl(),
|
|
||||||
|
|
||||||
@JsonAlias("collisionPoly")
|
|
||||||
val standingPoly: KOptional<Poly> = KOptional.empty(),
|
|
||||||
@JsonAlias("collisionPoly")
|
|
||||||
val crouchingPoly: KOptional<Poly> = KOptional.empty(),
|
|
||||||
|
|
||||||
val walkSpeed: KOptional<Double> = KOptional.empty(),
|
|
||||||
val runSpeed: KOptional<Double> = KOptional.empty(),
|
|
||||||
val flySpeed: KOptional<Double> = KOptional.empty(),
|
|
||||||
|
|
||||||
val minimumLiquidPercentage: KOptional<Double> = KOptional.empty(),
|
|
||||||
val liquidImpedance: KOptional<Double> = KOptional.empty(),
|
|
||||||
val normalGroundFriction: KOptional<Double> = KOptional.empty(),
|
|
||||||
val ambulatingGroundFriction: KOptional<Double> = KOptional.empty(),
|
|
||||||
val groundForce: KOptional<Double> = KOptional.empty(),
|
|
||||||
val airForce: KOptional<Double> = KOptional.empty(),
|
|
||||||
val liquidForce: KOptional<Double> = KOptional.empty(),
|
|
||||||
|
|
||||||
val airJumpProfile: JumpProfile = JumpProfile(),
|
|
||||||
val liquidJumpProfile: JumpProfile = JumpProfile(),
|
|
||||||
|
|
||||||
val fallStatusSpeedMin: KOptional<Double> = KOptional.empty(),
|
|
||||||
val fallThroughSustainFrames: KOptional<Int> = KOptional.empty(),
|
|
||||||
|
|
||||||
val groundMovementMinimumSustain: KOptional<Double> = KOptional.empty(),
|
|
||||||
val groundMovementMaximumSustain: KOptional<Double> = KOptional.empty(),
|
|
||||||
val groundMovementCheckDistance: KOptional<Double> = KOptional.empty(),
|
|
||||||
|
|
||||||
val pathExploreRate: KOptional<Double> = KOptional.empty(),
|
|
||||||
) : BaseMovementParameters by base {
|
|
||||||
fun merge(other: PlayerMovementParameters): PlayerMovementParameters {
|
|
||||||
return PlayerMovementParameters(
|
|
||||||
base = base.merge(other.base),
|
|
||||||
standingPoly = standingPoly.or(other.standingPoly),
|
|
||||||
crouchingPoly = crouchingPoly.or(other.crouchingPoly),
|
|
||||||
walkSpeed = walkSpeed.or(other.walkSpeed),
|
|
||||||
runSpeed = runSpeed.or(other.runSpeed),
|
|
||||||
flySpeed = flySpeed.or(other.flySpeed),
|
|
||||||
minimumLiquidPercentage = minimumLiquidPercentage.or(other.minimumLiquidPercentage),
|
|
||||||
liquidImpedance = liquidImpedance.or(other.liquidImpedance),
|
|
||||||
normalGroundFriction = normalGroundFriction.or(other.normalGroundFriction),
|
|
||||||
ambulatingGroundFriction = ambulatingGroundFriction.or(other.ambulatingGroundFriction),
|
|
||||||
groundForce = groundForce.or(other.groundForce),
|
|
||||||
airForce = airForce.or(other.airForce),
|
|
||||||
liquidForce = liquidForce.or(other.liquidForce),
|
|
||||||
airJumpProfile = airJumpProfile.merge(other.airJumpProfile),
|
|
||||||
liquidJumpProfile = liquidJumpProfile.merge(other.liquidJumpProfile),
|
|
||||||
fallStatusSpeedMin = fallStatusSpeedMin.or(other.fallStatusSpeedMin),
|
|
||||||
fallThroughSustainFrames = fallThroughSustainFrames.or(other.fallThroughSustainFrames),
|
|
||||||
groundMovementMinimumSustain = groundMovementMinimumSustain.or(other.groundMovementMinimumSustain),
|
|
||||||
groundMovementMaximumSustain = groundMovementMaximumSustain.or(other.groundMovementMaximumSustain),
|
|
||||||
groundMovementCheckDistance = groundMovementCheckDistance.or(other.groundMovementCheckDistance),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -246,4 +246,12 @@ abstract class World<This : World<This, ChunkType>, ChunkType : Chunk<This, Chun
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun gravityAt(pos: IStruct2i): Vector2d {
|
||||||
|
return gravity
|
||||||
|
}
|
||||||
|
|
||||||
|
fun gravityAt(pos: IStruct2d): Vector2d {
|
||||||
|
return gravity
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,19 +4,24 @@ import ru.dbotthepony.kstarbound.GlobalDefaults
|
|||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
import ru.dbotthepony.kstarbound.defs.BaseMovementParameters
|
import ru.dbotthepony.kstarbound.defs.BaseMovementParameters
|
||||||
import ru.dbotthepony.kstarbound.math.roundTowardsPositiveInfinity
|
import ru.dbotthepony.kstarbound.defs.ignorePlatformCollision
|
||||||
|
import ru.dbotthepony.kstarbound.defs.restDuration
|
||||||
import ru.dbotthepony.kstarbound.util.MailboxExecutorService
|
import ru.dbotthepony.kstarbound.util.MailboxExecutorService
|
||||||
import ru.dbotthepony.kstarbound.world.Chunk
|
import ru.dbotthepony.kstarbound.world.Chunk
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
import ru.dbotthepony.kstarbound.world.physics.CollisionPoly
|
import ru.dbotthepony.kstarbound.world.physics.CollisionPoly
|
||||||
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
|
import ru.dbotthepony.kvector.util.linearInterpolation
|
||||||
import ru.dbotthepony.kvector.util2d.AABB
|
import ru.dbotthepony.kvector.util2d.AABB
|
||||||
import ru.dbotthepony.kvector.vector.RGBAColor
|
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||||
import ru.dbotthepony.kvector.vector.Vector2d
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
|
import ru.dbotthepony.kvector.vector.times
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
import kotlin.concurrent.withLock
|
import kotlin.concurrent.withLock
|
||||||
|
import kotlin.math.PI
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.math.acos
|
||||||
|
|
||||||
abstract class Entity(val world: World<*, *>) {
|
abstract class Entity(val world: World<*, *>) {
|
||||||
var chunk: Chunk<*, *>? = null
|
var chunk: Chunk<*, *>? = null
|
||||||
@ -76,6 +81,41 @@ abstract class Entity(val world: World<*, *>) {
|
|||||||
physicsSleepTicks = 0
|
physicsSleepTicks = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Movement variables
|
||||||
|
var isZeroGravity = false
|
||||||
|
private set
|
||||||
|
|
||||||
|
var isOnGround = false
|
||||||
|
private set
|
||||||
|
var isColliding = false
|
||||||
|
private set
|
||||||
|
var isCollisionStuck = false
|
||||||
|
private set
|
||||||
|
var isCollidingWithNull = false
|
||||||
|
private set
|
||||||
|
var stickingDirection: Double? = null
|
||||||
|
private set
|
||||||
|
var surfaceSlope = Vector2d.ZERO
|
||||||
|
private set
|
||||||
|
var surfaceVelocity = Vector2d.ZERO
|
||||||
|
private set
|
||||||
|
var collisionCorrection = Vector2d.ZERO
|
||||||
|
private set
|
||||||
|
var liquidPercentage = 0.0
|
||||||
|
private set
|
||||||
|
|
||||||
|
// Movement parameters
|
||||||
|
open val movementParameters: BaseMovementParameters = GlobalDefaults.movementParameters
|
||||||
|
|
||||||
|
var gravityMultiplier = 1.0
|
||||||
|
var isGravityDisabled = false
|
||||||
|
|
||||||
|
var mass = 1.0
|
||||||
|
set(value) {
|
||||||
|
require(value > 0.0) { "Invalid mass: $value" }
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
|
||||||
var physicsSleepTicks = 0
|
var physicsSleepTicks = 0
|
||||||
val mailbox = MailboxExecutorService(world.mailbox.thread)
|
val mailbox = MailboxExecutorService(world.mailbox.thread)
|
||||||
|
|
||||||
@ -87,8 +127,6 @@ abstract class Entity(val world: World<*, *>) {
|
|||||||
*/
|
*/
|
||||||
protected var collisionFilterMode = false
|
protected var collisionFilterMode = false
|
||||||
|
|
||||||
open var movementParameters: BaseMovementParameters = GlobalDefaults.movementParameters
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whenever is this entity spawned in world ([spawn] called).
|
* Whenever is this entity spawned in world ([spawn] called).
|
||||||
* Doesn't mean entity still exists in world, check it with [isRemoved]
|
* Doesn't mean entity still exists in world, check it with [isRemoved]
|
||||||
@ -148,78 +186,402 @@ abstract class Entity(val world: World<*, *>) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateLiquidPercentage() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateForceRegions() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun determineGravity(): Vector2d {
|
||||||
|
if (isZeroGravity || isGravityDisabled)
|
||||||
|
return Vector2d.ZERO
|
||||||
|
|
||||||
|
return world.gravityAt(position)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* this function is executed in parallel
|
* this function is executed in parallel
|
||||||
*/
|
*/
|
||||||
// TODO: Ghost collisions occur, where objects trip on edges
|
// TODO: Ghost collisions occur, where objects trip on edges
|
||||||
open fun move() {
|
open fun move() {
|
||||||
if (physicsSleepTicks > PHYSICS_TICKS_UNTIL_SLEEP) return
|
isZeroGravity = isGravityDisabled || gravityMultiplier == 0.0 || determineGravity().lengthSquared == 0.0
|
||||||
var physicsSleepTicks = physicsSleepTicks
|
|
||||||
velocity += world.gravity * Starbound.TICK_TIME_ADVANCE
|
|
||||||
|
|
||||||
movementParameters.speedLimit.ifPresent {
|
if (!isZeroGravity)
|
||||||
if (velocity.length > it) {
|
velocity += world.gravity * Starbound.TICK_TIME_ADVANCE
|
||||||
|
|
||||||
|
movementParameters.speedLimit?.let {
|
||||||
|
if (velocity.length > it)
|
||||||
velocity = velocity.unitVector * it
|
velocity = velocity.unitVector * it
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hitboxes.isEmpty()) {
|
// TODO: Here: moving platforms sticky code
|
||||||
|
|
||||||
|
if (hitboxes.isEmpty() || movementParameters.collisionEnabled != true) {
|
||||||
position += velocity * Starbound.TICK_TIME_ADVANCE
|
position += velocity * Starbound.TICK_TIME_ADVANCE
|
||||||
|
surfaceSlope = Vector2d.POSITIVE_Y
|
||||||
|
surfaceVelocity = Vector2d.ZERO
|
||||||
|
isOnGround = false
|
||||||
|
stickingDirection = null
|
||||||
|
isColliding = false
|
||||||
|
isCollidingWithNull = false
|
||||||
|
isCollisionStuck = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
val steps = roundTowardsPositiveInfinity(velocity.length / 10.0 / hitboxes.stream().map { it.aabb }.reduce(AABB::combine).get().let { it.width.coerceAtLeast(it.height).coerceAtLeast(0.1) })
|
var steps = 1
|
||||||
|
|
||||||
|
movementParameters.maxMovementPerStep?.let {
|
||||||
|
steps = (velocity.length * Starbound.TICK_TIME_ADVANCE / it).toInt() + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var relativeVelocity = if (physicsSleepTicks > 0) {
|
||||||
|
physicsSleepTicks--
|
||||||
|
Vector2d.ZERO
|
||||||
|
} else {
|
||||||
|
velocity
|
||||||
|
}
|
||||||
|
|
||||||
|
val originalMovement = relativeVelocity * Starbound.TICK_TIME_ADVANCE
|
||||||
|
surfaceSlope = Vector2d.POSITIVE_Y
|
||||||
|
// TODO: Here: moving platforms sticky code
|
||||||
|
|
||||||
val dt = Starbound.TICK_TIME_ADVANCE / steps
|
val dt = Starbound.TICK_TIME_ADVANCE / steps
|
||||||
|
|
||||||
for (step in 0 until steps) {
|
for (step in 0 until steps) {
|
||||||
position += velocity * dt
|
val velocityMagnitude = relativeVelocity.length
|
||||||
|
val velocityDirection = relativeVelocity / velocityMagnitude
|
||||||
|
val movement = relativeVelocity * dt
|
||||||
|
|
||||||
for (i in 0 until 10) {
|
val ignorePlatforms = movementParameters.ignorePlatformCollision || relativeVelocity.y > 0.0
|
||||||
val localHitboxes = hitboxes.map { it + position }
|
val maximumCorrection = movementParameters.maximumCorrection ?: 0.0
|
||||||
|
val maximumPlatformCorrection = (movementParameters.maximumPlatformCorrection ?: Double.POSITIVE_INFINITY) +
|
||||||
|
(movementParameters.maximumPlatformCorrectionVelocityFactor ?: 0.0) * velocityMagnitude
|
||||||
|
|
||||||
val polies = world.queryCollisions(
|
val localHitboxes = hitboxes.map { it + position }
|
||||||
localHitboxes.stream().map { it.aabb }.reduce(AABB::combine).get().enlarge(2.0, 2.0)
|
val aabb = localHitboxes.stream().map { it.aabb }.reduce(AABB::combine).get()
|
||||||
).filter {
|
var queryBounds = aabb.enlarge(maximumCorrection, maximumCorrection)
|
||||||
if (collisionFilterMode)
|
queryBounds = queryBounds.combine(queryBounds + movement)
|
||||||
it.type in collisionFilter
|
|
||||||
else
|
|
||||||
it.type !in collisionFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
if (polies.isEmpty()) break
|
val polies = world.queryCollisions(queryBounds).filter {
|
||||||
|
if (collisionFilterMode)
|
||||||
|
it.type in collisionFilter
|
||||||
|
else
|
||||||
|
it.type !in collisionFilter
|
||||||
|
}
|
||||||
|
|
||||||
val intersects = ArrayList<Pair<Poly.Penetration, CollisionPoly>>()
|
val results = ArrayList<CollisionResult>(localHitboxes.size)
|
||||||
|
|
||||||
localHitboxes.forEach { hitbox ->
|
for (hitbox in localHitboxes) {
|
||||||
polies.forEach { poly -> hitbox.intersect(poly.poly)?.let { intersects.add(it to poly) } }
|
results.add(collisionSweep(hitbox, polies, movement, ignorePlatforms, (movementParameters.enableSurfaceSlopeCorrection ?: false) && !isZeroGravity, maximumCorrection, maximumPlatformCorrection, aabb.centre))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intersects.isEmpty()) {
|
val result = results.minOrNull()!!
|
||||||
|
position += result.movement
|
||||||
|
|
||||||
|
if (result.collisionType == CollisionType.NULL) {
|
||||||
|
isCollidingWithNull = true
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
isCollidingWithNull = false
|
||||||
|
}
|
||||||
|
|
||||||
|
val correction = result.correction
|
||||||
|
val normCorrection = correction.unitVector
|
||||||
|
surfaceSlope = result.groundSlope
|
||||||
|
collisionCorrection = result.correction
|
||||||
|
isColliding = correction != Vector2d.ZERO || result.isStuck
|
||||||
|
isOnGround = !isZeroGravity && result.isOnGround
|
||||||
|
isCollisionStuck = result.isStuck
|
||||||
|
|
||||||
|
// If we have collided, apply either sticky or normal (bouncing) collision physics
|
||||||
|
if (correction != Vector2d.ZERO) {
|
||||||
|
if (movementParameters.stickyCollision == true && result.collisionType !== CollisionType.SLIPPERY) {
|
||||||
|
// When sticking, cancel all velocity and apply stickyForce in the
|
||||||
|
// opposite of the direction of collision correction.
|
||||||
|
relativeVelocity = -normCorrection * (movementParameters.stickyForce ?: 0.0) / mass * dt
|
||||||
|
stickingDirection = -normCorrection.toAngle()
|
||||||
break
|
break
|
||||||
} else {
|
} else {
|
||||||
val (max, data) = intersects.maxByOrNull { it.first }!!
|
stickingDirection = null
|
||||||
// resolve collision
|
val correctionDirection = correction.unitVector
|
||||||
position += max.axis * max.penetration
|
|
||||||
// collision response
|
|
||||||
val response = max.axis * velocity.dot(max.axis * (1.0 + data.bounceFactor) * (1.0 + movementParameters.bounceFactor.orElse(0.0)))
|
|
||||||
velocity -= response
|
|
||||||
|
|
||||||
val gravityDot = world.gravity.unitVector.dot(max.axis)
|
if (movementParameters.bounceFactor != null && movementParameters.bounceFactor != 0.0) {
|
||||||
// impulse?
|
val adjustment = correctionDirection * (velocityMagnitude * (correctionDirection * -velocityDirection))
|
||||||
velocity += data.velocity * gravityDot * dt
|
relativeVelocity += adjustment + movementParameters.bounceFactor!! * adjustment
|
||||||
// friction
|
|
||||||
velocity *= 1.0 - gravityDot.absoluteValue * 0.08
|
|
||||||
|
|
||||||
onTouch(response, max.axis, data)
|
if (movementParameters.stopOnFirstBounce == true) {
|
||||||
|
// When bouncing, stop integrating at the moment of bounce. This
|
||||||
|
// prevents the frame of contact from being missed due to multiple
|
||||||
|
// iterations per frame.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Only adjust the velocity to the extent that the collision was
|
||||||
|
// caused by the velocity in each axis, to eliminate collision
|
||||||
|
// induced velocity in a platformery way (each axis considered
|
||||||
|
// independently).
|
||||||
|
|
||||||
|
if (relativeVelocity.x < 0.0 && correction.x > 0.0)
|
||||||
|
relativeVelocity = relativeVelocity.copy(x = (relativeVelocity.x + correction.x / Starbound.TICK_TIME_ADVANCE).coerceAtMost(0.0))
|
||||||
|
else if (relativeVelocity.x > 0.0 && correction.x < 0.0)
|
||||||
|
relativeVelocity = relativeVelocity.copy(x = (relativeVelocity.x + correction.x / Starbound.TICK_TIME_ADVANCE).coerceAtLeast(0.0))
|
||||||
|
|
||||||
|
if (relativeVelocity.y < 0.0 && correction.y > 0.0)
|
||||||
|
relativeVelocity = relativeVelocity.copy(y = (relativeVelocity.y + correction.y / Starbound.TICK_TIME_ADVANCE).coerceAtMost(0.0))
|
||||||
|
else if (relativeVelocity.y > 0.0 && correction.y < 0.0)
|
||||||
|
relativeVelocity = relativeVelocity.copy(y = (relativeVelocity.y + correction.y / Starbound.TICK_TIME_ADVANCE).coerceAtLeast(0.0))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (velocity.lengthSquared < 0.25) {
|
var newVelocity = relativeVelocity
|
||||||
physicsSleepTicks++
|
|
||||||
|
updateLiquidPercentage()
|
||||||
|
|
||||||
|
// TODO: sticky collision update
|
||||||
|
|
||||||
|
// In order to make control work accurately, passive forces need to be
|
||||||
|
// applied to velocity *after* integrating. This prevents control from
|
||||||
|
// having to account for one timestep of passive forces in order to result
|
||||||
|
// in the correct controlled movement.
|
||||||
|
if (!isZeroGravity && stickingDirection == null) {
|
||||||
|
val buoyancy = (movementParameters.liquidBuoyancy ?: 0.0).coerceIn(0.0, 1.0) + liquidPercentage + (movementParameters.airBuoyancy ?: 0.0).coerceIn(0.0, 1.0) * (1.0 - liquidPercentage)
|
||||||
|
val gravity = determineGravity() * (movementParameters.gravityMultiplier ?: 1.0) * (1.0 - buoyancy)
|
||||||
|
var environmentVelocity = gravity * Starbound.TICK_TIME_ADVANCE
|
||||||
|
|
||||||
|
if (isOnGround && (movementParameters.slopeSlidingFactor ?: 0.0) != 0.0 && surfaceSlope != Vector2d.ZERO)
|
||||||
|
environmentVelocity += surfaceSlope * (surfaceSlope.x * surfaceSlope.y) * (movementParameters.slopeSlidingFactor ?: 0.0)
|
||||||
|
|
||||||
|
newVelocity += environmentVelocity
|
||||||
}
|
}
|
||||||
|
|
||||||
this.physicsSleepTicks = physicsSleepTicks
|
// If original movement was entirely (almost) in the direction of gravity
|
||||||
|
// and was entirely (almost) cancelled by collision correction, put the
|
||||||
|
// entity into rest for restDuration
|
||||||
|
if (
|
||||||
|
physicsSleepTicks == 0 &&
|
||||||
|
originalMovement.dot(determineGravity()) in 0.99 .. 1.01 &&
|
||||||
|
collisionCorrection.dot(determineGravity()) in -1.01 .. -0.99
|
||||||
|
) {
|
||||||
|
physicsSleepTicks = movementParameters.restDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
if (movementParameters.frictionEnabled == true) {
|
||||||
|
var refVel = Vector2d.ZERO
|
||||||
|
var friction = liquidPercentage * (movementParameters.liquidFriction ?: 0.0).coerceIn(0.0, 1.0) + (1.0 - liquidPercentage) * (movementParameters.airFriction ?: 0.0).coerceIn(0.0, 1.0)
|
||||||
|
|
||||||
|
if (isOnGround) {
|
||||||
|
friction = friction.coerceAtLeast(movementParameters.groundFriction ?: 0.0)
|
||||||
|
refVel = surfaceVelocity
|
||||||
|
}
|
||||||
|
|
||||||
|
// The equation for friction here is effectively:
|
||||||
|
// frictionForce = friction * (refVel - velocity)
|
||||||
|
// but it is applied here as a multiplicative factor from [0, 1] so it does
|
||||||
|
// not induce oscillation at very high friction and so it cannot be
|
||||||
|
// negative.
|
||||||
|
val frictionFactor = (friction / mass * Starbound.TICK_TIME_ADVANCE).coerceIn(0.0, 1.0)
|
||||||
|
newVelocity = linearInterpolation(frictionFactor, newVelocity, refVel)
|
||||||
|
}
|
||||||
|
|
||||||
|
velocity = newVelocity
|
||||||
|
updateForceRegions()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected data class CollisionSeparation(
|
||||||
|
var correction: Vector2d = Vector2d.ZERO,
|
||||||
|
var solutionFound: Boolean = false,
|
||||||
|
var collisionType: CollisionType = CollisionType.NONE,
|
||||||
|
var axis: Vector2d = Vector2d.POSITIVE_Y,
|
||||||
|
var movingCollisionId: Int? = null, // TODO
|
||||||
|
)
|
||||||
|
|
||||||
|
protected data class CollisionResult(
|
||||||
|
val movement: Vector2d = Vector2d.ZERO,
|
||||||
|
val correction: Vector2d = Vector2d.ZERO,
|
||||||
|
var movingCollisionId: Int? = null, // TODO
|
||||||
|
var isStuck: Boolean = false,
|
||||||
|
var isOnGround: Boolean = false,
|
||||||
|
var groundSlope: Vector2d = Vector2d.ZERO,
|
||||||
|
var collisionType: CollisionType = CollisionType.NULL,
|
||||||
|
) : Comparable<CollisionResult> {
|
||||||
|
override fun compareTo(other: CollisionResult): Int {
|
||||||
|
return movement.lengthSquared.compareTo(other.movement.lengthSquared)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun collisionSweep(
|
||||||
|
body: Poly, staticBodies: List<CollisionPoly>,
|
||||||
|
movement: Vector2d, ignorePlatforms: Boolean,
|
||||||
|
slopeCorrection: Boolean, maximumCorrection: Double,
|
||||||
|
maximumPlatformCorrection: Double, sortCenter: Vector2d
|
||||||
|
): CollisionResult {
|
||||||
|
val translatedBody = body + movement
|
||||||
|
var checkBody = translatedBody
|
||||||
|
var maxCollided = CollisionType.NONE
|
||||||
|
|
||||||
|
var separation = CollisionSeparation()
|
||||||
|
var totalCorrection = Vector2d.ZERO
|
||||||
|
var movingCollisionId: Int? = null
|
||||||
|
|
||||||
|
val sorted = staticBodies.stream()
|
||||||
|
.map { it to (it.poly.aabb.centre - sortCenter).lengthSquared }
|
||||||
|
.sorted { o1, o2 -> o1.second.compareTo(o2.second) }
|
||||||
|
.map { it.first }
|
||||||
|
.toList()
|
||||||
|
|
||||||
|
if (slopeCorrection) {
|
||||||
|
// Starbound: First try separating with our ground sliding cheat.
|
||||||
|
separation = collisionSeparate(checkBody, sorted, ignorePlatforms, maximumPlatformCorrection, true, SEPARATION_TOLERANCE)
|
||||||
|
totalCorrection += separation.correction
|
||||||
|
checkBody += separation.correction
|
||||||
|
maxCollided = maxCollided.maxOf(separation.collisionType)
|
||||||
|
movingCollisionId = separation.movingCollisionId
|
||||||
|
|
||||||
|
val upwardResult = movement + separation.correction
|
||||||
|
val upwardMagnitude = upwardResult.length
|
||||||
|
val upwardUnit = upwardResult / upwardMagnitude
|
||||||
|
// Starbound: Angle off of horizontal (minimum of either direction)
|
||||||
|
val horizontalAngle = acos(Vector2d.POSITIVE_X.dot(upwardUnit)).coerceAtMost(acos(Vector2d.NEGATIVE_X.dot(upwardUnit)))
|
||||||
|
|
||||||
|
// Starbound: We need to make sure that even if we found a solution with the sliding
|
||||||
|
// Starbound: cheat, we are not beyond the angle and correction limits for the ground
|
||||||
|
// Starbound: cheat correction.
|
||||||
|
separation.solutionFound = separation.solutionFound && (upwardMagnitude < 0.2 || horizontalAngle < PI / 3.0) && totalCorrection.length <= maximumCorrection
|
||||||
|
|
||||||
|
// KStarbound: if we got pushed into world geometry, then consider slide cheat didn't find a solution
|
||||||
|
if (separation.solutionFound) {
|
||||||
|
separation.solutionFound = staticBodies.all { it.poly.intersect(checkBody).let { it == null || it.penetration.absoluteValue <= SEPARATION_TOLERANCE } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!separation.solutionFound) {
|
||||||
|
checkBody = translatedBody
|
||||||
|
totalCorrection = Vector2d.ZERO
|
||||||
|
movingCollisionId = null
|
||||||
|
|
||||||
|
for (i in 0 until SEPARATION_STEPS) {
|
||||||
|
separation = collisionSeparate(checkBody, sorted, ignorePlatforms, maximumPlatformCorrection, false, SEPARATION_TOLERANCE)
|
||||||
|
totalCorrection += separation.correction
|
||||||
|
checkBody += separation.correction
|
||||||
|
maxCollided = maxCollided.maxOf(separation.collisionType)
|
||||||
|
|
||||||
|
if (totalCorrection.length >= maximumCorrection) {
|
||||||
|
separation.solutionFound = false
|
||||||
|
break
|
||||||
|
} else if (separation.solutionFound) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!separation.solutionFound && movement != Vector2d.ZERO) {
|
||||||
|
checkBody = body
|
||||||
|
totalCorrection = -movement
|
||||||
|
|
||||||
|
for (i in 0 until SEPARATION_STEPS) {
|
||||||
|
separation = collisionSeparate(checkBody, sorted, true, maximumPlatformCorrection, false, SEPARATION_TOLERANCE)
|
||||||
|
totalCorrection += separation.correction
|
||||||
|
checkBody += separation.correction
|
||||||
|
maxCollided = maxCollided.maxOf(separation.collisionType)
|
||||||
|
|
||||||
|
if (totalCorrection.length >= maximumCorrection) {
|
||||||
|
separation.solutionFound = false
|
||||||
|
break
|
||||||
|
} else if (separation.solutionFound) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (separation.solutionFound) {
|
||||||
|
val result = CollisionResult(
|
||||||
|
movement = movement + totalCorrection,
|
||||||
|
correction = totalCorrection,
|
||||||
|
isStuck = false,
|
||||||
|
isOnGround = -totalCorrection.dot(determineGravity()) > SEPARATION_TOLERANCE,
|
||||||
|
movingCollisionId = movingCollisionId,
|
||||||
|
collisionType = maxCollided,
|
||||||
|
// groundSlope = Vector2d.POSITIVE_Y,
|
||||||
|
groundSlope = separation.axis
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: what they wanted to achieve with this?
|
||||||
|
/*if (result.isOnGround) {
|
||||||
|
// If we are on the ground and need to find the ground slope, look for a
|
||||||
|
// vertex on the body being moved that is touching an edge of one of the
|
||||||
|
// collision polys. We only want a slope to be produced from an edge of
|
||||||
|
// colision geometry, not an edge of the colliding body. Pick the
|
||||||
|
// touching edge that is the most horizontally overlapped with the
|
||||||
|
// geometry, rather than off to the side.
|
||||||
|
var maxSideHorizontalOverlap = 0.0
|
||||||
|
var touchingBounds = checkBody.aabb.enlarge(SEPARATION_TOLERANCE, SEPARATION_TOLERANCE)
|
||||||
|
|
||||||
|
for (poly in staticBodies) {
|
||||||
|
for (edge in poly.poly.edges) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
return CollisionResult(Vector2d.ZERO, -movement, null, true, true, Vector2d.POSITIVE_Y, maxCollided)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun collisionSeparate(
|
||||||
|
poly: Poly, staticBodies: List<CollisionPoly>,
|
||||||
|
ignorePlatforms: Boolean, maximumPlatformCorrection: Double,
|
||||||
|
upward: Boolean, separationTolerance: Double
|
||||||
|
): CollisionSeparation {
|
||||||
|
val separation = CollisionSeparation()
|
||||||
|
var intersects = false
|
||||||
|
var correctedPoly = poly
|
||||||
|
|
||||||
|
for (body in staticBodies) {
|
||||||
|
if (ignorePlatforms && body.type === CollisionType.PLATFORM)
|
||||||
|
continue
|
||||||
|
|
||||||
|
var result = if (upward)
|
||||||
|
correctedPoly.intersect(body.poly, Vector2d.POSITIVE_Y, false)
|
||||||
|
else if (body.type == CollisionType.PLATFORM)
|
||||||
|
correctedPoly.intersect(body.poly, Vector2d.POSITIVE_Y, true)
|
||||||
|
else
|
||||||
|
correctedPoly.intersect(body.poly)
|
||||||
|
|
||||||
|
if (body.type === CollisionType.PLATFORM && result != null && (result.penetration <= 0.0 || result.penetration > maximumPlatformCorrection))
|
||||||
|
result = null
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
intersects = true
|
||||||
|
correctedPoly += result.vector
|
||||||
|
separation.correction += result.vector
|
||||||
|
separation.collisionType = separation.collisionType.maxOf(body.type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
separation.solutionFound = true
|
||||||
|
|
||||||
|
if (intersects) {
|
||||||
|
for (body in staticBodies) {
|
||||||
|
if (body.type === CollisionType.PLATFORM)
|
||||||
|
continue
|
||||||
|
|
||||||
|
val result = correctedPoly.intersect(body.poly)
|
||||||
|
|
||||||
|
if (result != null && result.penetration > separationTolerance) {
|
||||||
|
separation.collisionType = separation.collisionType.maxOf(body.type)
|
||||||
|
separation.solutionFound = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return separation
|
||||||
}
|
}
|
||||||
|
|
||||||
protected open fun onTouch(velocity: Vector2d, normal: Vector2d, poly: CollisionPoly) {
|
protected open fun onTouch(velocity: Vector2d, normal: Vector2d, poly: CollisionPoly) {
|
||||||
@ -249,5 +611,7 @@ abstract class Entity(val world: World<*, *>) {
|
|||||||
companion object {
|
companion object {
|
||||||
const val PHYSICS_TICKS_UNTIL_SLEEP = 16
|
const val PHYSICS_TICKS_UNTIL_SLEEP = 16
|
||||||
val BLOCK_COLLISION_COLOR = RGBAColor(65, 179, 217)
|
val BLOCK_COLLISION_COLOR = RGBAColor(65, 179, 217)
|
||||||
|
const val SEPARATION_STEPS = 3
|
||||||
|
const val SEPARATION_TOLERANCE = 0.001
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,18 +2,18 @@ package ru.dbotthepony.kstarbound.world.entities
|
|||||||
|
|
||||||
import ru.dbotthepony.kstarbound.GlobalDefaults
|
import ru.dbotthepony.kstarbound.GlobalDefaults
|
||||||
import ru.dbotthepony.kstarbound.Starbound
|
import ru.dbotthepony.kstarbound.Starbound
|
||||||
|
import ru.dbotthepony.kstarbound.defs.BaseMovementParameters
|
||||||
|
import ru.dbotthepony.kstarbound.defs.PlayerMovementParameters
|
||||||
import ru.dbotthepony.kstarbound.world.World
|
import ru.dbotthepony.kstarbound.world.World
|
||||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||||
|
|
||||||
class PlayerEntity(world: World<*, *>) : Entity(world) {
|
class PlayerEntity(world: World<*, *>) : Entity(world) {
|
||||||
init {
|
override val movementParameters: PlayerMovementParameters = GlobalDefaults.playerMovementParameters
|
||||||
movementParameters = GlobalDefaults.playerMovementParameters
|
|
||||||
|
|
||||||
GlobalDefaults.playerMovementParameters.standingPoly.ifPresent {
|
init {
|
||||||
|
GlobalDefaults.playerMovementParameters.standingPoly?.let {
|
||||||
//hitboxes.add(it)
|
//hitboxes.add(it)
|
||||||
hitboxes.add(Starbound.gson.fromJson("""[ [0.5625, 1.9375], [1.0625, 1.4375], [1.0625, -2.5625], [0.5625, -3.0625], [-0.5625, -3.0625], [-1.0625, -2.5625], [-1.0625, 1.4375], [-0.5625, 1.9375] ]""", Poly::class.java))
|
hitboxes.add(Starbound.gson.fromJson("""[ [-0.75, -2.0], [-0.35, -2.5], [0.35, -2.5], [0.75, -2.0], [0.75, 0.65], [0.35, 1.22], [-0.35, 1.22], [-0.75, 0.65] ]""", Poly::class.java))
|
||||||
}.ifNotPresent {
|
|
||||||
throw IllegalStateException("No player collision poly")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,4 +10,13 @@ enum class CollisionType(val isEmpty: Boolean) {
|
|||||||
DYNAMIC(false),
|
DYNAMIC(false),
|
||||||
SLIPPERY(false),
|
SLIPPERY(false),
|
||||||
BLOCK(false);
|
BLOCK(false);
|
||||||
|
|
||||||
|
fun maxOf(other: CollisionType): CollisionType {
|
||||||
|
if (this === NULL || other === NULL)
|
||||||
|
return NULL
|
||||||
|
else if (this > other)
|
||||||
|
return this
|
||||||
|
else
|
||||||
|
return other
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import com.google.gson.TypeAdapterFactory
|
|||||||
import com.google.gson.reflect.TypeToken
|
import com.google.gson.reflect.TypeToken
|
||||||
import com.google.gson.stream.JsonReader
|
import com.google.gson.stream.JsonReader
|
||||||
import com.google.gson.stream.JsonWriter
|
import com.google.gson.stream.JsonWriter
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
|
||||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||||
import org.lwjgl.opengl.GL11.GL_LINES
|
import org.lwjgl.opengl.GL11.GL_LINES
|
||||||
import ru.dbotthepony.kstarbound.client.StarboundClient
|
import ru.dbotthepony.kstarbound.client.StarboundClient
|
||||||
@ -21,6 +20,8 @@ import ru.dbotthepony.kvector.util2d.intersectSegments
|
|||||||
import ru.dbotthepony.kvector.vector.RGBAColor
|
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||||
import ru.dbotthepony.kvector.vector.Vector2d
|
import ru.dbotthepony.kvector.vector.Vector2d
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
|
|
||||||
private fun calculateEdges(points: List<Vector2d>): Pair<ImmutableList<Poly.Edge>, ImmutableList<Vector2d>> {
|
private fun calculateEdges(points: List<Vector2d>): Pair<ImmutableList<Poly.Edge>, ImmutableList<Vector2d>> {
|
||||||
require(points.size >= 2) { "Provided poly is invalid (only ${points.size} points are defined)" }
|
require(points.size >= 2) { "Provided poly is invalid (only ${points.size} points are defined)" }
|
||||||
@ -51,6 +52,13 @@ private fun calculateEdges(points: List<Vector2d>): Pair<ImmutableList<Poly.Edge
|
|||||||
return edges.build() to ImmutableList.copyOf(points)
|
return edges.build() to ImmutableList.copyOf(points)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun rotate(point: Vector2d, sin: Double, cos: Double): Vector2d {
|
||||||
|
return Vector2d(
|
||||||
|
point.x * cos + point.y * sin,
|
||||||
|
point.x * sin + point.y * cos
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* edges are built in clockwise winding
|
* edges are built in clockwise winding
|
||||||
*
|
*
|
||||||
@ -142,6 +150,24 @@ class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: Imm
|
|||||||
return Poly(edges.build(), vertices.build())
|
return Poly(edges.build(), vertices.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun rotate(radians: Double): Poly {
|
||||||
|
val sin = sin(radians)
|
||||||
|
val cos = cos(radians)
|
||||||
|
|
||||||
|
val edges = ImmutableList.Builder<Edge>()
|
||||||
|
val vertices = ImmutableList.Builder<Vector2d>()
|
||||||
|
|
||||||
|
for (edge in this.edges) {
|
||||||
|
edges.add(Edge(rotate(edge.p0, sin, cos), rotate(edge.p1, sin, cos), rotate(edge.normal, sin, cos)))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (vertex in this.vertices) {
|
||||||
|
vertices.add(rotate(vertex, sin, cos))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Poly(edges.build(), vertices.build())
|
||||||
|
}
|
||||||
|
|
||||||
// min / max
|
// min / max
|
||||||
fun project(normal: Vector2d): IStruct2d {
|
fun project(normal: Vector2d): IStruct2d {
|
||||||
var min = vertices.first().dot(normal)
|
var min = vertices.first().dot(normal)
|
||||||
@ -157,7 +183,10 @@ class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: Imm
|
|||||||
return Vector2d(min, max)
|
return Vector2d(min, max)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun intersect(other: Poly): Penetration? {
|
/**
|
||||||
|
* @param axis separate ONLY along specified axis, that said, if axis is positive Y and we have collision, and closest separation axis is to up right, we instead separate only up until we no longer collide.
|
||||||
|
*/
|
||||||
|
fun intersect(other: Poly, axis: Vector2d? = null, strictAxis: Boolean = false): Penetration? {
|
||||||
if (!aabb.intersectWeak(other.aabb))
|
if (!aabb.intersectWeak(other.aabb))
|
||||||
return null
|
return null
|
||||||
|
|
||||||
@ -165,6 +194,10 @@ class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: Imm
|
|||||||
edges.forEach { normals.add(it.normal) }
|
edges.forEach { normals.add(it.normal) }
|
||||||
other.edges.forEach { normals.add(it.normal) }
|
other.edges.forEach { normals.add(it.normal) }
|
||||||
|
|
||||||
|
if (axis != null) {
|
||||||
|
normals.removeIf { it.dot(axis) == 0.0 }
|
||||||
|
}
|
||||||
|
|
||||||
val intersections = ArrayList<Penetration>()
|
val intersections = ArrayList<Penetration>()
|
||||||
|
|
||||||
for (normal in normals) {
|
for (normal in normals) {
|
||||||
@ -208,12 +241,25 @@ class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: Imm
|
|||||||
}
|
}
|
||||||
} else if (projectOther.component1() in projectThis.component1() .. projectThis.component2()) {
|
} else if (projectOther.component1() in projectThis.component1() .. projectThis.component2()) {
|
||||||
// other's min point is within this
|
// other's min point is within this
|
||||||
|
// push to right
|
||||||
intersections.add(Penetration(normal, projectOther.component1() - projectThis.component2()))
|
intersections.add(Penetration(normal, projectOther.component1() - projectThis.component2()))
|
||||||
} else {
|
} else {
|
||||||
// other's max point in within this
|
// other's max point in within this
|
||||||
|
// push to left
|
||||||
intersections.add(Penetration(normal, projectOther.component2() - projectThis.component1()))
|
intersections.add(Penetration(normal, projectOther.component2() - projectThis.component1()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (axis != null) {
|
||||||
|
val last = intersections.removeLast()
|
||||||
|
|
||||||
|
if (strictAxis) {
|
||||||
|
// TODO: NYI
|
||||||
|
intersections.add(Penetration(axis, last.penetration / axis.dot(last.axis)))
|
||||||
|
} else {
|
||||||
|
intersections.add(Penetration(axis, last.penetration / axis.dot(last.axis)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (intersections.last().penetration == 0.0) {
|
if (intersections.last().penetration == 0.0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user