Regular movement code port from original engine

This commit is contained in:
DBotThePony 2023-12-01 13:28:25 +07:00
parent 2b94bfd41f
commit 4db69db0cd
Signed by: DBot
GPG Key ID: DCC23B5715498507
14 changed files with 627 additions and 199 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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