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("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("org.classdump.luna:luna-all-shaded:0.4.1")
|
||||
|
@ -3,7 +3,7 @@ package ru.dbotthepony.kstarbound
|
||||
import org.apache.logging.log4j.LogManager
|
||||
import ru.dbotthepony.kstarbound.defs.ClientConfigParameters
|
||||
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 java.util.concurrent.ForkJoinPool
|
||||
import java.util.concurrent.ForkJoinTask
|
||||
|
@ -129,7 +129,7 @@ fun main() {
|
||||
|
||||
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)
|
||||
|
||||
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.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)
|
||||
interface BaseMovementParameters {
|
||||
val mass: KOptional<Double>
|
||||
val gravityMultiplier: KOptional<Double>
|
||||
val liquidBuoyancy: KOptional<Double>
|
||||
val airBuoyancy: KOptional<Double>
|
||||
val bounceFactor: KOptional<Double>
|
||||
sealed interface BaseMovementParameters {
|
||||
val mass: Double?
|
||||
val gravityMultiplier: Double?
|
||||
val liquidBuoyancy: Double?
|
||||
val airBuoyancy: Double?
|
||||
val bounceFactor: Double?
|
||||
|
||||
// If set to true, during an update that has more than one internal movement
|
||||
// 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
|
||||
// other directions (within a set limit). Allows smooth sliding along
|
||||
// horizontal ground without losing horizontal speed.
|
||||
val enableSurfaceSlopeCorrection: KOptional<Boolean>
|
||||
val slopeSlidingFactor: KOptional<Double>
|
||||
val enableSurfaceSlopeCorrection: Boolean?
|
||||
val slopeSlidingFactor: Double?
|
||||
|
||||
// ignored
|
||||
val maxMovementPerStep: KOptional<Double>
|
||||
val maximumCorrection: KOptional<Double>
|
||||
val speedLimit: KOptional<Double>
|
||||
val maxMovementPerStep: Double?
|
||||
val maximumCorrection: Double?
|
||||
val speedLimit: Double?
|
||||
|
||||
val stickyCollision: KOptional<Boolean>
|
||||
val stickyForce: KOptional<Double>
|
||||
val stickyCollision: Boolean?
|
||||
val stickyForce: Double?
|
||||
|
||||
val airFriction: KOptional<Double>
|
||||
val liquidFriction: KOptional<Double>
|
||||
val groundFriction: KOptional<Double>
|
||||
val airFriction: Double?
|
||||
val liquidFriction: Double?
|
||||
val groundFriction: Double?
|
||||
|
||||
val collisionEnabled: KOptional<Boolean>
|
||||
val frictionEnabled: KOptional<Boolean>
|
||||
val gravityEnabled: KOptional<Boolean>
|
||||
val collisionEnabled: Boolean?
|
||||
val frictionEnabled: Boolean?
|
||||
val gravityEnabled: Boolean?
|
||||
|
||||
val maximumPlatformCorrection: KOptional<Double>
|
||||
val maximumPlatformCorrectionVelocityFactor: KOptional<Double>
|
||||
val maximumPlatformCorrection: Double?
|
||||
val maximumPlatformCorrectionVelocityFactor: Double?
|
||||
|
||||
val physicsEffectCategories: KOptional<ImmutableSet<String>>
|
||||
val physicsEffectCategories: ImmutableSet<String>?
|
||||
|
||||
@JsonFactory
|
||||
data class Impl(
|
||||
override val mass: KOptional<Double> = KOptional.empty(),
|
||||
override val gravityMultiplier: KOptional<Double> = KOptional.empty(),
|
||||
override val liquidBuoyancy: KOptional<Double> = KOptional.empty(),
|
||||
override val airBuoyancy: KOptional<Double> = KOptional.empty(),
|
||||
override val bounceFactor: KOptional<Double> = KOptional.empty(),
|
||||
override val stopOnFirstBounce: KOptional<Boolean> = KOptional.empty(),
|
||||
override val enableSurfaceSlopeCorrection: KOptional<Boolean> = KOptional.empty(),
|
||||
override val slopeSlidingFactor: KOptional<Double> = KOptional.empty(),
|
||||
override val maxMovementPerStep: KOptional<Double> = KOptional.empty(),
|
||||
override val maximumCorrection: KOptional<Double> = KOptional.empty(),
|
||||
override val speedLimit: KOptional<Double> = KOptional.empty(),
|
||||
override val stickyCollision: KOptional<Boolean> = KOptional.empty(),
|
||||
override val stickyForce: KOptional<Double> = KOptional.empty(),
|
||||
override val airFriction: KOptional<Double> = KOptional.empty(),
|
||||
override val liquidFriction: KOptional<Double> = KOptional.empty(),
|
||||
override val groundFriction: KOptional<Double> = KOptional.empty(),
|
||||
override val collisionEnabled: KOptional<Boolean> = KOptional.empty(),
|
||||
override val frictionEnabled: KOptional<Boolean> = KOptional.empty(),
|
||||
override val gravityEnabled: KOptional<Boolean> = KOptional.empty(),
|
||||
override val maximumPlatformCorrection: KOptional<Double> = KOptional.empty(),
|
||||
override val maximumPlatformCorrectionVelocityFactor: KOptional<Double> = KOptional.empty(),
|
||||
override val physicsEffectCategories: KOptional<ImmutableSet<String>> = KOptional.empty(),
|
||||
override val mass: Double? = null,
|
||||
override val gravityMultiplier: Double? = null,
|
||||
override val liquidBuoyancy: Double? = null,
|
||||
override val airBuoyancy: Double? = null,
|
||||
override val bounceFactor: Double? = null,
|
||||
override val stopOnFirstBounce: Boolean? = null,
|
||||
override val enableSurfaceSlopeCorrection: Boolean? = null,
|
||||
override val slopeSlidingFactor: Double? = null,
|
||||
override val maxMovementPerStep: Double? = null,
|
||||
override val maximumCorrection: Double? = null,
|
||||
override val speedLimit: Double? = null,
|
||||
override val stickyCollision: Boolean? = null,
|
||||
override val stickyForce: Double? = null,
|
||||
override val airFriction: Double? = null,
|
||||
override val liquidFriction: Double? = null,
|
||||
override val groundFriction: Double? = null,
|
||||
override val collisionEnabled: Boolean? = null,
|
||||
override val frictionEnabled: Boolean? = null,
|
||||
override val gravityEnabled: Boolean? = null,
|
||||
override val maximumPlatformCorrection: Double? = null,
|
||||
override val maximumPlatformCorrectionVelocityFactor: Double? = null,
|
||||
override val physicsEffectCategories: ImmutableSet<String>? = null,
|
||||
) : BaseMovementParameters {
|
||||
fun merge(other: Impl): Impl {
|
||||
return Impl(
|
||||
mass = mass.or(other.mass),
|
||||
gravityMultiplier = gravityMultiplier.or(other.gravityMultiplier),
|
||||
liquidBuoyancy = liquidBuoyancy.or(other.liquidBuoyancy),
|
||||
airBuoyancy = airBuoyancy.or(other.airBuoyancy),
|
||||
bounceFactor = bounceFactor.or(other.bounceFactor),
|
||||
stopOnFirstBounce = stopOnFirstBounce.or(other.stopOnFirstBounce),
|
||||
enableSurfaceSlopeCorrection = enableSurfaceSlopeCorrection.or(other.enableSurfaceSlopeCorrection),
|
||||
slopeSlidingFactor = slopeSlidingFactor.or(other.slopeSlidingFactor),
|
||||
maxMovementPerStep = maxMovementPerStep.or(other.maxMovementPerStep),
|
||||
maximumCorrection = maximumCorrection.or(other.maximumCorrection),
|
||||
speedLimit = speedLimit.or(other.speedLimit),
|
||||
stickyCollision = stickyCollision.or(other.stickyCollision),
|
||||
stickyForce = stickyForce.or(other.stickyForce),
|
||||
airFriction = airFriction.or(other.airFriction),
|
||||
liquidFriction = liquidFriction.or(other.liquidFriction),
|
||||
groundFriction = groundFriction.or(other.groundFriction),
|
||||
collisionEnabled = collisionEnabled.or(other.collisionEnabled),
|
||||
frictionEnabled = frictionEnabled.or(other.frictionEnabled),
|
||||
gravityEnabled = gravityEnabled.or(other.gravityEnabled),
|
||||
maximumPlatformCorrection = maximumPlatformCorrection.or(other.maximumPlatformCorrection),
|
||||
maximumPlatformCorrectionVelocityFactor = maximumPlatformCorrectionVelocityFactor.or(other.maximumPlatformCorrectionVelocityFactor),
|
||||
physicsEffectCategories = physicsEffectCategories.or(other.physicsEffectCategories),
|
||||
mass = mass ?: other.mass,
|
||||
gravityMultiplier = gravityMultiplier ?: other.gravityMultiplier,
|
||||
liquidBuoyancy = liquidBuoyancy ?: other.liquidBuoyancy,
|
||||
airBuoyancy = airBuoyancy ?: other.airBuoyancy,
|
||||
bounceFactor = bounceFactor ?: other.bounceFactor,
|
||||
stopOnFirstBounce = stopOnFirstBounce ?: other.stopOnFirstBounce,
|
||||
enableSurfaceSlopeCorrection = enableSurfaceSlopeCorrection ?: other.enableSurfaceSlopeCorrection,
|
||||
slopeSlidingFactor = slopeSlidingFactor ?: other.slopeSlidingFactor,
|
||||
maxMovementPerStep = maxMovementPerStep ?: other.maxMovementPerStep,
|
||||
maximumCorrection = maximumCorrection ?: other.maximumCorrection,
|
||||
speedLimit = speedLimit ?: other.speedLimit,
|
||||
stickyCollision = stickyCollision ?: other.stickyCollision,
|
||||
stickyForce = stickyForce ?: other.stickyForce,
|
||||
airFriction = airFriction ?: other.airFriction,
|
||||
liquidFriction = liquidFriction ?: other.liquidFriction,
|
||||
groundFriction = groundFriction ?: other.groundFriction,
|
||||
collisionEnabled = collisionEnabled ?: other.collisionEnabled,
|
||||
frictionEnabled = frictionEnabled ?: other.frictionEnabled,
|
||||
gravityEnabled = gravityEnabled ?: other.gravityEnabled,
|
||||
maximumPlatformCorrection = maximumPlatformCorrection ?: other.maximumPlatformCorrection,
|
||||
maximumPlatformCorrectionVelocityFactor = maximumPlatformCorrectionVelocityFactor ?: other.maximumPlatformCorrectionVelocityFactor,
|
||||
physicsEffectCategories = physicsEffectCategories ?: other.physicsEffectCategories,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -10,18 +10,18 @@ data class MovementParameters(
|
||||
@JsonFlat
|
||||
val base: BaseMovementParameters.Impl = BaseMovementParameters.Impl(),
|
||||
|
||||
val discontinuityThreshold: KOptional<Float> = KOptional.empty(),
|
||||
val collisionPoly: KOptional<Poly> = KOptional.empty(),
|
||||
val ignorePlatformCollision: KOptional<Boolean> = KOptional.empty(),
|
||||
val restDuration: KOptional<Int> = KOptional.empty(),
|
||||
val discontinuityThreshold: Float? = null,
|
||||
val collisionPoly: Poly? = null,
|
||||
val ignorePlatformCollision: Boolean? = null,
|
||||
val restDuration: Int? = null,
|
||||
) : BaseMovementParameters by base {
|
||||
fun merge(other: MovementParameters): MovementParameters {
|
||||
return MovementParameters(
|
||||
base = base.merge(other.base),
|
||||
discontinuityThreshold = discontinuityThreshold.or(other.discontinuityThreshold),
|
||||
collisionPoly = collisionPoly.or(other.collisionPoly),
|
||||
ignorePlatformCollision = ignorePlatformCollision.or(other.ignorePlatformCollision),
|
||||
restDuration = restDuration.or(other.restDuration),
|
||||
discontinuityThreshold = discontinuityThreshold ?: other.discontinuityThreshold,
|
||||
collisionPoly = collisionPoly ?: other.collisionPoly,
|
||||
ignorePlatformCollision = ignorePlatformCollision ?: other.ignorePlatformCollision,
|
||||
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.IScriptable
|
||||
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.item.TreasurePoolDefinition
|
||||
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
|
||||
|
@ -6,6 +6,7 @@ import com.google.common.collect.ImmutableSet
|
||||
import com.google.gson.JsonObject
|
||||
import ru.dbotthepony.kstarbound.Registry
|
||||
import ru.dbotthepony.kstarbound.defs.AssetReference
|
||||
import ru.dbotthepony.kstarbound.defs.PlayerMovementParameters
|
||||
import ru.dbotthepony.kstarbound.defs.Species
|
||||
import ru.dbotthepony.kstarbound.util.SBPattern
|
||||
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
|
||||
}
|
||||
|
||||
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.client.StarboundClient
|
||||
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.world.Chunk
|
||||
import ru.dbotthepony.kstarbound.world.World
|
||||
import ru.dbotthepony.kstarbound.world.physics.CollisionPoly
|
||||
import ru.dbotthepony.kstarbound.world.physics.CollisionType
|
||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||
import ru.dbotthepony.kvector.util.linearInterpolation
|
||||
import ru.dbotthepony.kvector.util2d.AABB
|
||||
import ru.dbotthepony.kvector.vector.RGBAColor
|
||||
import ru.dbotthepony.kvector.vector.Vector2d
|
||||
import ru.dbotthepony.kvector.vector.times
|
||||
import java.util.EnumSet
|
||||
import kotlin.concurrent.withLock
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.acos
|
||||
|
||||
abstract class Entity(val world: World<*, *>) {
|
||||
var chunk: Chunk<*, *>? = null
|
||||
@ -76,6 +81,41 @@ abstract class Entity(val world: World<*, *>) {
|
||||
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
|
||||
val mailbox = MailboxExecutorService(world.mailbox.thread)
|
||||
|
||||
@ -87,8 +127,6 @@ abstract class Entity(val world: World<*, *>) {
|
||||
*/
|
||||
protected var collisionFilterMode = false
|
||||
|
||||
open var movementParameters: BaseMovementParameters = GlobalDefaults.movementParameters
|
||||
|
||||
/**
|
||||
* Whenever is this entity spawned in world ([spawn] called).
|
||||
* 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
|
||||
*/
|
||||
// TODO: Ghost collisions occur, where objects trip on edges
|
||||
open fun move() {
|
||||
if (physicsSleepTicks > PHYSICS_TICKS_UNTIL_SLEEP) return
|
||||
var physicsSleepTicks = physicsSleepTicks
|
||||
velocity += world.gravity * Starbound.TICK_TIME_ADVANCE
|
||||
isZeroGravity = isGravityDisabled || gravityMultiplier == 0.0 || determineGravity().lengthSquared == 0.0
|
||||
|
||||
movementParameters.speedLimit.ifPresent {
|
||||
if (velocity.length > it) {
|
||||
if (!isZeroGravity)
|
||||
velocity += world.gravity * Starbound.TICK_TIME_ADVANCE
|
||||
|
||||
movementParameters.speedLimit?.let {
|
||||
if (velocity.length > 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
|
||||
surfaceSlope = Vector2d.POSITIVE_Y
|
||||
surfaceVelocity = Vector2d.ZERO
|
||||
isOnGround = false
|
||||
stickingDirection = null
|
||||
isColliding = false
|
||||
isCollidingWithNull = false
|
||||
isCollisionStuck = false
|
||||
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
|
||||
|
||||
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 localHitboxes = hitboxes.map { it + position }
|
||||
val ignorePlatforms = movementParameters.ignorePlatformCollision || relativeVelocity.y > 0.0
|
||||
val maximumCorrection = movementParameters.maximumCorrection ?: 0.0
|
||||
val maximumPlatformCorrection = (movementParameters.maximumPlatformCorrection ?: Double.POSITIVE_INFINITY) +
|
||||
(movementParameters.maximumPlatformCorrectionVelocityFactor ?: 0.0) * velocityMagnitude
|
||||
|
||||
val polies = world.queryCollisions(
|
||||
localHitboxes.stream().map { it.aabb }.reduce(AABB::combine).get().enlarge(2.0, 2.0)
|
||||
).filter {
|
||||
if (collisionFilterMode)
|
||||
it.type in collisionFilter
|
||||
else
|
||||
it.type !in collisionFilter
|
||||
}
|
||||
val localHitboxes = hitboxes.map { it + position }
|
||||
val aabb = localHitboxes.stream().map { it.aabb }.reduce(AABB::combine).get()
|
||||
var queryBounds = aabb.enlarge(maximumCorrection, maximumCorrection)
|
||||
queryBounds = queryBounds.combine(queryBounds + movement)
|
||||
|
||||
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 ->
|
||||
polies.forEach { poly -> hitbox.intersect(poly.poly)?.let { intersects.add(it to poly) } }
|
||||
}
|
||||
for (hitbox in localHitboxes) {
|
||||
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
|
||||
} else {
|
||||
val (max, data) = intersects.maxByOrNull { it.first }!!
|
||||
// resolve collision
|
||||
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
|
||||
stickingDirection = null
|
||||
val correctionDirection = correction.unitVector
|
||||
|
||||
val gravityDot = world.gravity.unitVector.dot(max.axis)
|
||||
// impulse?
|
||||
velocity += data.velocity * gravityDot * dt
|
||||
// friction
|
||||
velocity *= 1.0 - gravityDot.absoluteValue * 0.08
|
||||
if (movementParameters.bounceFactor != null && movementParameters.bounceFactor != 0.0) {
|
||||
val adjustment = correctionDirection * (velocityMagnitude * (correctionDirection * -velocityDirection))
|
||||
relativeVelocity += adjustment + movementParameters.bounceFactor!! * adjustment
|
||||
|
||||
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) {
|
||||
physicsSleepTicks++
|
||||
var newVelocity = relativeVelocity
|
||||
|
||||
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) {
|
||||
@ -249,5 +611,7 @@ abstract class Entity(val world: World<*, *>) {
|
||||
companion object {
|
||||
const val PHYSICS_TICKS_UNTIL_SLEEP = 16
|
||||
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.Starbound
|
||||
import ru.dbotthepony.kstarbound.defs.BaseMovementParameters
|
||||
import ru.dbotthepony.kstarbound.defs.PlayerMovementParameters
|
||||
import ru.dbotthepony.kstarbound.world.World
|
||||
import ru.dbotthepony.kstarbound.world.physics.Poly
|
||||
|
||||
class PlayerEntity(world: World<*, *>) : Entity(world) {
|
||||
init {
|
||||
movementParameters = GlobalDefaults.playerMovementParameters
|
||||
override val movementParameters: PlayerMovementParameters = GlobalDefaults.playerMovementParameters
|
||||
|
||||
GlobalDefaults.playerMovementParameters.standingPoly.ifPresent {
|
||||
init {
|
||||
GlobalDefaults.playerMovementParameters.standingPoly?.let {
|
||||
//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))
|
||||
}.ifNotPresent {
|
||||
throw IllegalStateException("No player collision poly")
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,4 +10,13 @@ enum class CollisionType(val isEmpty: Boolean) {
|
||||
DYNAMIC(false),
|
||||
SLIPPERY(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.stream.JsonReader
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList
|
||||
import it.unimi.dsi.fastutil.objects.ObjectOpenHashSet
|
||||
import org.lwjgl.opengl.GL11.GL_LINES
|
||||
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.Vector2d
|
||||
import kotlin.math.absoluteValue
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.sin
|
||||
|
||||
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)" }
|
||||
@ -51,6 +52,13 @@ private fun calculateEdges(points: List<Vector2d>): Pair<ImmutableList<Poly.Edge
|
||||
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
|
||||
*
|
||||
@ -142,6 +150,24 @@ class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: Imm
|
||||
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
|
||||
fun project(normal: Vector2d): IStruct2d {
|
||||
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)
|
||||
}
|
||||
|
||||
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))
|
||||
return null
|
||||
|
||||
@ -165,6 +194,10 @@ class Poly private constructor(val edges: ImmutableList<Edge>, val vertices: Imm
|
||||
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>()
|
||||
|
||||
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()) {
|
||||
// other's min point is within this
|
||||
// push to right
|
||||
intersections.add(Penetration(normal, projectOther.component1() - projectThis.component2()))
|
||||
} else {
|
||||
// other's max point in within this
|
||||
// push to left
|
||||
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) {
|
||||
return null
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user