package com.hypixel.hytale.server.npc.movement.controllers;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.util.TrigMathUtil;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.server.core.modules.collision.BlockCollisionData;
import com.hypixel.hytale.server.core.modules.collision.CollisionModule;
import com.hypixel.hytale.server.core.modules.collision.WorldUtil;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk;
import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport;
import com.hypixel.hytale.server.npc.movement.MotionKind;
import com.hypixel.hytale.server.npc.movement.Steering;
import com.hypixel.hytale.server.npc.movement.controllers.builders.BuilderMotionControllerFly;
import com.hypixel.hytale.server.npc.role.Role;
import com.hypixel.hytale.server.npc.util.NPCPhysicsMath;
import com.hypixel.hytale.server.npc.util.PositionProbeAir;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
public class MotionControllerFly extends MotionControllerBase {
public static final String TYPE = "Fly";
public static final double DAMPING_FACTOR = 20.0;
public static final int COLLISION_MATERIALS_PASSIVE = 4;
public static final int COLLISION_MATERIALS_ACTIVE = 6;
protected final double minAirSpeed;
protected final double maxClimbSpeed;
protected final double maxSinkSpeed;
protected final double maxFallSpeed;
protected final double maxSinkSpeedFluid;
protected final float maxClimbAngle;
protected final float maxSinkAngle;
protected final double acceleration;
protected final double deceleration;
protected final double sinkRatio = 0.5;
protected final double desiredAltitudeWeight;
protected final float maxTurnSpeed;
protected final float maxRollAngle;
protected final float maxRollSpeed;
protected final float rollDamping;
protected final double fastFlyThreshold;
protected final double minHeightOverGround;
protected final double maxHeightOverGround;
protected final boolean autoLevel;
protected final double sinMaxClimbAngle;
protected final double sinMaxSinkAngle;
protected final MotionController.VerticalRange verticalRange = new MotionController.VerticalRange();
protected final PositionProbeAir moveProbe = new PositionProbeAir();
protected final PositionProbeAir probeMoveProbe = new PositionProbeAir();
protected int lastVerticalPositionX = -2147483648;
protected int lastVerticalPositionZ = -2147483648;
protected final Vector3d lastVelocity = new Vector3d();
protected double lastSpeed;
protected float lastRoll;
protected double currentRelativeSpeed;
protected double minSpeedAfterForceSquared;
@Nullable
protected double[] desiredAltitudeOverride;
public MotionControllerFly(@Nonnull BuilderSupport builderSupport, @Nonnull BuilderMotionControllerFly builder) {
super(builderSupport, builder);
this.setGravity(builder.getGravity());
this.componentSelector.assign(1.0, 1.0, 1.0);
this.minAirSpeed = builder.getMinAirSpeed();
this.maxClimbSpeed = builder.getMaxClimbSpeed();
this.maxSinkSpeed = builder.getMaxSinkSpeed();
this.maxFallSpeed = builder.getMaxFallSpeed();
this.maxSinkSpeedFluid = builder.getMaxSinkSpeedFluid();
this.maxClimbAngle = builder.getMaxClimbAngle();
this.sinMaxClimbAngle = (double)TrigMathUtil.sin(this.maxClimbAngle);
this.maxSinkAngle = builder.getMaxSinkAngle();
this.sinMaxSinkAngle = (double)(-TrigMathUtil.sin(this.maxSinkAngle));
this.acceleration = builder.getAcceleration();
this.deceleration = builder.getDeceleration();
this.maxTurnSpeed = builder.getMaxTurnSpeed();
this.maxRollAngle = builder.getMaxRollAngle();
this.minHeightOverGround = builder.getMinHeightOverGround(builderSupport);
this.maxHeightOverGround = builder.getMaxHeightOverGround(builderSupport);
this.maxRollSpeed = builder.getMaxRollSpeed();
this.rollDamping = builder.getRollDamping();
this.fastFlyThreshold = builder.getFastFlyThreshold();
this.autoLevel = builder.isAutoLevel();
this.desiredAltitudeWeight = builder.getDesiredAltitudeWeight();
this.minSpeedAfterForceSquared = MathUtil.minValue(this.maxHorizontalSpeed, this.maxSinkSpeed, this.maxClimbSpeed);
this.minSpeedAfterForceSquared *= this.minSpeedAfterForceSquared;
}
@Nonnull
public String getType() {
return "Fly";
}
protected double computeMove(@Nonnull Ref<EntityStore> ref, @Nonnull Role role, @Nonnull Steering steering, double dt, @Nonnull Vector3d translation, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
this.saveMotionKind();
this.setMotionKind(this.inWater() ? MotionKind.MOVING : MotionKind.FLYING);
this.moveProbe.probePosition(ref, this.collisionBoundingBox, this.position, this.collisionResult, componentAccessor);
this.currentRelativeSpeed = steering.getSpeed();
if (!this.isAlive(ref, componentAccessor)) {
this.forceVelocity.assign(Vector3d.ZERO);
this.appliedVelocities.clear();
}
double maxFallSpeed = this.moveProbe.isInWater() ? this.maxSinkSpeedFluid : this.maxFallSpeed;
boolean onGround = this.onGround();
if (this.forceVelocity.equals(Vector3d.ZERO) && this.appliedVelocities.isEmpty()) {
if (NPCPhysicsMath.near(this.lastVelocity, Vector3d.ZERO)) {
PhysicsMath.vectorFromAngles(this.getYaw(), this.getPitch(), this.lastVelocity);
this.lastSpeed = 0.0;
}
if (this.canAct(ref, componentAccessor)) {
translation.assign(steering.getTranslation());
double steeringSpeed = steering.hasTranslation() ? translation.length() : 0.0;
float yaw = PhysicsMath.normalizeAngle(this.getYaw());
float pitch = PhysicsMath.normalizeTurnAngle(this.getPitch());
double dirX = translation.x;
double dirZ = translation.z;
double dotXZ = dirX * dirX + dirZ * dirZ;
float expYaw;
float expPitch;
if (dotXZ >= 1.0E-12) {
expYaw = PhysicsMath.headingFromDirection(dirX, dirZ);
expPitch = TrigMathUtil.atan2(translation.y, Math.sqrt(dotXZ));
} else {
expYaw = steering.hasYaw() ? steering.getYaw() : yaw;
expPitch = steering.hasPitch() ? steering.getPitch() : (this.autoLevel ? 0.0F : pitch);
}
steering.clearYaw();
steering.clearPitch();
expPitch = MathUtil.clamp(expPitch, -this.maxSinkAngle, this.maxClimbAngle);
float turnYaw = NPCPhysicsMath.turnAngle(yaw, expYaw);
float turnPitch = NPCPhysicsMath.turnAngle(pitch, expPitch);
float maxRotationAngle = (float)(this.getCurrentMaxBodyRotationSpeed() * dt);
turnYaw = NPCPhysicsMath.clampRotation(turnYaw, maxRotationAngle);
turnPitch = NPCPhysicsMath.clampRotation(turnPitch, maxRotationAngle);
float newYaw = PhysicsMath.normalizeAngle(yaw + turnYaw);
float newPitch = PhysicsMath.normalizeTurnAngle(pitch + turnPitch);
double speedLimit = this.computeMaxSpeedFromPitch((double)pitch);
steeringSpeed *= speedLimit;
double minSpeed = Math.max(this.minAirSpeed, this.lastSpeed - this.deceleration * dt);
double maxSpeed = this.lastSpeed + this.acceleration * dt;
steeringSpeed = maxSpeed < minSpeed ? minSpeed : MathUtil.clamp(steeringSpeed, minSpeed, maxSpeed);
PhysicsMath.vectorFromAngles(newYaw, newPitch, translation);
translation.normalize();
double mX = this.lastVelocity.z;
double mZ = -this.lastVelocity.x;
double mL = Math.sqrt(mX * mX + mZ * mZ);
float rollTurnCosine = (float)(NPCPhysicsMath.dotProduct(mX, 0.0, mZ, translation.x, translation.y, translation.z) / mL);
float maxRollTurnAngle = (float)((double)this.maxTurnSpeed * dt);
float maxRollTurnCosine = TrigMathUtil.sin(maxRollTurnAngle);
float rollTurnStrength = rollTurnCosine / maxRollTurnCosine;
double speedFactor = steeringSpeed / speedLimit;
float desiredRoll = this.maxRollAngle * MathUtil.clamp(rollTurnStrength, -1.0F, 1.0F) * MathUtil.clamp((float)speedFactor, 0.0F, 1.0F);
float dampedRoll = MathUtil.clamp(this.rollDamping * this.lastRoll + (1.0F - this.rollDamping) * desiredRoll, -this.maxRollAngle, this.maxRollAngle);
float deltaRoll = (float)((double)this.maxRollSpeed * dt);
float constrainedRoll = MathUtil.clamp(dampedRoll, this.lastRoll - deltaRoll, this.lastRoll + deltaRoll);
this.lastRoll = constrainedRoll;
steering.setYaw(newYaw);
steering.setPitch(newPitch);
steering.setRoll(constrainedRoll);
if (steeringSpeed == 0.0) {
translation.assign(Vector3d.ZERO);
} else {
translation.scale(steeringSpeed * this.effectHorizontalSpeedMultiplier);
}
this.lastVelocity.assign(translation);
this.lastSpeed = steeringSpeed;
translation.scale(dt);
if (this.debugModeValidateMath && !NPCPhysicsMath.isValid(translation)) {
throw new IllegalArgumentException(String.valueOf(translation));
}
} else {
steering.setYaw(this.getYaw());
steering.setPitch(this.getPitch());
steering.setRoll(this.getRoll());
if (onGround) {
this.setMotionKind(MotionKind.STANDING);
this.lastVelocity.assign(Vector3d.ZERO);
this.lastSpeed = 0.0;
return dt;
}
this.setMotionKind(MotionKind.DROPPING);
translation.y = NPCPhysicsMath.gravityDrag(this.lastVelocity.y, this.gravity, dt, maxFallSpeed);
double diffSpeed = maxFallSpeed - translation.y;
if (!(diffSpeed <= 0.0) && !this.isObstructed()) {
double scale = translation.x * translation.x + translation.z * translation.z;
if (diffSpeed * diffSpeed < scale) {
scale = Math.sqrt(scale / diffSpeed);
translation.x = this.lastVelocity.x * scale;
translation.z = this.lastVelocity.z * scale;
} else {
translation.x = this.lastVelocity.x;
translation.z = this.lastVelocity.z;
}
} else {
translation.x = 0.0;
translation.z = 0.0;
}
this.lastVelocity.assign(translation);
this.lastSpeed = this.lastVelocity.length();
translation.scale(dt);
if (this.debugModeValidateMath && !NPCPhysicsMath.isValid(translation)) {
throw new IllegalArgumentException(String.valueOf(translation));
}
}
if (this.lastSpeed > 1.0E-6 && this.isAlive(ref, componentAccessor)) {
this.setDirectionFromTranslation(steering, translation);
}
return dt;
} else {
steering.setYaw(this.getYaw());
steering.setPitch(this.getPitch());
steering.setRoll(this.getRoll());
if (!this.isObstructed()) {
translation.assign(this.forceVelocity);
for(int i = 0; i < this.appliedVelocities.size(); ++i) {
MotionControllerBase.AppliedVelocity entry = (MotionControllerBase.AppliedVelocity)this.appliedVelocities.get(i);
if (entry.velocity.y + this.forceVelocity.y <= 0.0 || entry.velocity.y < 0.0) {
entry.canClear = true;
}
if (onGround && entry.canClear) {
entry.velocity.y = 0.0;
}
translation.add(entry.velocity);
}
} else {
translation.assign(Vector3d.ZERO);
this.appliedVelocities.clear();
this.forceVelocity.assign(Vector3d.ZERO);
}
if (!onGround) {
translation.y = NPCPhysicsMath.accelerateDrag(translation.y, -this.gravity, dt, maxFallSpeed);
}
this.lastVelocity.assign(translation);
this.lastSpeed = this.lastVelocity.length();
translation.scale(dt);
if (this.debugModeValidateMath && !NPCPhysicsMath.isValid(translation)) {
throw new IllegalArgumentException(String.valueOf(translation));
} else {
return dt;
}
}
}
private void setDirectionFromTranslation(@Nonnull Steering steering, @Nonnull Vector3d translation) {
if (!steering.hasYaw()) {
if (translation.x * translation.x + translation.z * translation.z > 1.0E-6) {
steering.setYaw(PhysicsMath.headingFromDirection(translation.x, translation.z));
} else {
steering.setYaw(this.getYaw());
}
}
if (!steering.hasPitch()) {
steering.setPitch(PhysicsMath.pitchFromDirection(translation.x, translation.y, translation.z));
}
}
public double probeMove(@Nonnull Ref<EntityStore> ref, @Nonnull ProbeMoveData probeMoveData, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
return probeMoveData.probeDirection.length() * this.doMove(ref, probeMoveData.probePosition, probeMoveData.probeDirection, this.probeMoveProbe, probeMoveData, componentAccessor);
}
public boolean isFastMotionKind(double speed) {
return this.lastVelocity.y < -1.0E-6 || this.currentRelativeSpeed > this.fastFlyThreshold && this.lastVelocity.y <= 1.0E-6;
}
public MotionController.VerticalRange getDesiredVerticalRange(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
TransformComponent transformComponent = (TransformComponent)componentAccessor.getComponent(ref, TransformComponent.getComponentType());
assert transformComponent != null;
Vector3d position = transformComponent.getPosition();
int ix = MathUtil.floor(position.getX());
int iz = MathUtil.floor(position.getZ());
if (ix == this.lastVerticalPositionX && iz == this.lastVerticalPositionZ) {
return this.verticalRange;
} else {
this.lastVerticalPositionX = ix;
this.lastVerticalPositionZ = iz;
double y = position.getY();
World world = ((EntityStore)componentAccessor.getExternalData()).getWorld();
ChunkStore chunkStore = world.getChunkStore();
long chunkIndex = ChunkUtil.indexChunkFromBlock(ix, iz);
Ref<ChunkStore> chunkRef = chunkStore.getChunkReference(chunkIndex);
if (chunkRef != null && chunkRef.isValid()) {
Store<ChunkStore> chunkStoreAccessor = chunkStore.getStore();
ChunkColumn chunkColumnComponent = (ChunkColumn)chunkStoreAccessor.getComponent(chunkRef, ChunkColumn.getComponentType());
BlockChunk blockChunkComponent = (BlockChunk)chunkStoreAccessor.getComponent(chunkRef, BlockChunk.getComponentType());
if (chunkColumnComponent != null && blockChunkComponent != null) {
double minHeightOverGround;
double maxHeightOverGround;
if (this.desiredAltitudeOverride != null) {
minHeightOverGround = this.desiredAltitudeOverride[0];
maxHeightOverGround = this.desiredAltitudeOverride[1];
} else {
minHeightOverGround = this.minHeightOverGround;
maxHeightOverGround = this.maxHeightOverGround;
}
int iy = MathUtil.floor(y);
double below = (double)WorldUtil.findFarthestEmptySpaceBelow(chunkStoreAccessor, chunkColumnComponent, blockChunkComponent, ix, iy, iz, iy) + this.collisionBoundingBox.min.y;
double above = (double)WorldUtil.findFarthestEmptySpaceAbove(chunkStoreAccessor, chunkColumnComponent, blockChunkComponent, ix, iy, iz, iy) - this.collisionBoundingBox.max.y + 1.0;
double minY = below + minHeightOverGround;
double maxY = Math.min(below + maxHeightOverGround, above);
if (minY > maxY) {
minY = y;
maxY = y;
}
this.verticalRange.assign(y, minY, maxY);
return this.verticalRange;
} else {
this.verticalRange.assign(y, y, y);
return this.verticalRange;
}
} else {
this.verticalRange.assign(y, y, y);
return this.verticalRange;
}
}
}
public double getWanderVerticalMovementRatio() {
return 0.5;
}
protected double doMove(@Nonnull Ref<EntityStore> ref, @Nonnull Vector3d position, @Nonnull Vector3d translation, @Nonnull PositionProbeAir moveProbe, @Nullable ProbeMoveData probeMoveData, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
boolean probeOnly = probeMoveData != null;
boolean saveSegments = probeOnly && probeMoveData.startProbing();
boolean canAct = probeOnly || this.canAct(ref, componentAccessor);
String debugPrefix = probeOnly ? "Probe" : "Move";
if (this.debugModeMove) {
LOGGER.at(Level.INFO).log("%s - Fly: Execute pos=%s vel=%s onGround=%s blocked=%s ", debugPrefix, Vector3d.formatShortString(position), Vector3d.formatShortString(translation), this.onGround(), this.isObstructed);
}
if (this.debugModeValidatePositions && !this.isValidPosition(position, this.collisionResult, componentAccessor)) {
throw new IllegalStateException("Invalid position");
} else {
if (saveSegments) {
probeMoveData.addStartSegment(position, false);
}
if (!probeOnly) {
this.isObstructed = false;
if (this.debugModeBlockCollisions) {
this.collisionResult.setLogger(LOGGER);
}
}
this.collisionResult.setCollisionByMaterial(canAct ? 6 : 4);
CollisionModule.get();
CollisionModule.findCollisions(this.collisionBoundingBox, position, translation, this.collisionResult, componentAccessor);
if (this.debugModeBlockCollisions) {
this.collisionResult.setLogger((HytaleLogger)null);
}
if (this.debugModeCollisions) {
this.dumpCollisionResults();
}
BlockCollisionData collision = this.collisionResult.getFirstBlockCollision();
this.lastValidPosition.assign(position);
double distanceFactor;
if (collision == null) {
position.add(translation);
distanceFactor = 1.0;
if (this.debugModeMove) {
LOGGER.at(Level.INFO).log("%s - Fly: No collision pos=%s vel=%s onGround=%s blocked=%s ", debugPrefix, Vector3d.formatShortString(position), Vector3d.formatShortString(translation), this.onGround(), this.isObstructed);
}
if (this.debugModeValidatePositions && !this.isValidPosition(position, this.collisionResult, componentAccessor)) {
throw new IllegalStateException("Invalid position");
}
} else {
position.assign(collision.collisionPoint);
distanceFactor = collision.collisionStart;
if (!probeOnly) {
this.isObstructed = true;
}
if (this.debugModeMove) {
LOGGER.at(Level.INFO).log("%s - Fly: Collision pos=%s collStart=%s vel=%s onGround=%s blocked=%s ", debugPrefix, Vector3d.formatShortString(position), distanceFactor, Vector3d.formatShortString(translation), this.onGround(), this.isObstructed);
}
if (this.debugModeValidatePositions && !this.isValidPosition(position, this.collisionResult, componentAccessor)) {
throw new IllegalStateException("Invalid position");
}
}
if (!moveProbe.probePosition(ref, this.collisionBoundingBox, position, this.collisionResult, componentAccessor)) {
double adjust = this.bisect(this.lastValidPosition, position, this, (_this, newPosition) -> _this.moveProbe.probePosition(ref, _this.collisionBoundingBox, newPosition, _this.collisionResult, componentAccessor), position);
distanceFactor *= adjust;
if (this.debugModeMove) {
LOGGER.at(Level.INFO).log("%s - Fly: Bisect step pos=%s distanceFactor=%s adjust=%s", debugPrefix, Vector3d.formatShortString(position), distanceFactor, adjust);
}
}
if (!probeOnly) {
this.processTriggers(ref, this.collisionResult, distanceFactor, componentAccessor);
} else if (saveSegments) {
double distance = this.waypointDistance(probeMoveData.initialPosition, position);
if (collision == null) {
probeMoveData.addMoveSegment(position, false, distance);
} else if (this.getWorldNormal().equals(collision.collisionNormal)) {
probeMoveData.addHitGroundSegment(position, distance, collision.collisionNormal, collision.blockId);
} else {
probeMoveData.addHitWallSegment(position, false, distance, collision.collisionNormal, collision.blockId);
}
probeMoveData.addEndSegment(position, true, distance);
}
return distanceFactor;
}
}
protected double executeMove(@Nonnull Ref<EntityStore> ref, @Nonnull Role role, double dt, @Nonnull Vector3d translation, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
double scale = this.doMove(ref, this.position, translation, this.moveProbe, (ProbeMoveData)null, componentAccessor);
if (scale < 1.0) {
dt *= scale;
this.lastSpeed *= scale;
this.lastVelocity.scale(scale);
}
return dt;
}
public void constrainRotations(Role role, TransformComponent transform) {
}
public double getCurrentMaxBodyRotationSpeed() {
return (double)this.maxTurnSpeed * this.effectHorizontalSpeedMultiplier;
}
protected void dampForceVelocity(@Nonnull Vector3d forceVelocity, double forceVelocityDamping, double interval, ComponentAccessor<EntityStore> componentAccessor) {
if (forceVelocity.squaredLength() < this.minSpeedAfterForceSquared) {
forceVelocity.assign(Vector3d.ZERO);
} else {
NPCPhysicsMath.deccelerateToStop(forceVelocity, this.getDampingDeceleration(), interval);
}
}
protected boolean shouldDampenAppliedVelocitiesY() {
return true;
}
protected boolean shouldAlwaysUseGroundResistance() {
return true;
}
public void spawned() {
}
public boolean canAct(@Nonnull Ref<EntityStore> ref, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
return super.canAct(ref, componentAccessor) && this.moveProbe.isInAir() && this.effectHorizontalSpeedMultiplier != 0.0;
}
public boolean inAir() {
return !this.onGround();
}
public boolean onGround() {
return this.moveProbe.isOnGround();
}
public boolean inWater() {
return this.moveProbe.isInWater();
}
public double getCurrentSpeed() {
return 0.0;
}
public double getCurrentTurnRadius() {
return this.lastSpeed / (double)this.maxTurnSpeed;
}
public float getMaxClimbAngle() {
return this.maxClimbAngle;
}
public float getMaxSinkAngle() {
return this.maxSinkAngle;
}
public double getMaximumSpeed() {
return MathUtil.maxValue(this.maxClimbSpeed, this.maxHorizontalSpeed, this.maxSinkSpeed) * this.effectHorizontalSpeedMultiplier;
}
public boolean is2D() {
return false;
}
public boolean canRestAtPlace() {
return false;
}
public double getDesiredAltitudeWeight() {
return this.desiredAltitudeWeight;
}
public double getHeightOverGround() {
return (double)this.probeMoveProbe.getHeightOverGround();
}
public boolean isHorizontalIdle(double speed) {
return false;
}
public boolean estimateVelocity(Steering steering, @Nonnull Vector3d velocityOut) {
velocityOut.assign(Vector3d.ZERO);
return false;
}
public void clearOverrides() {
this.desiredAltitudeOverride = null;
}
public void setDesiredAltitudeOverride(double[] desiredAltitudeOverride) {
this.desiredAltitudeOverride = desiredAltitudeOverride;
}
public void takeOff(@Nonnull Ref<EntityStore> ref, double speed, @Nonnull ComponentAccessor<EntityStore> componentAccessor) {
TransformComponent transformComponent = (TransformComponent)componentAccessor.getComponent(ref, TransformComponent.getComponentType());
assert transformComponent != null;
PhysicsMath.vectorFromAngles(transformComponent.getRotation().getYaw(), 0.7853982F, this.lastVelocity);
this.lastSpeed = speed;
}
public double getMinSpeedAfterForceSquared() {
return this.minSpeedAfterForceSquared;
}
public double getDampingDeceleration() {
return this.forceVelocityDamping * 20.0;
}
protected double computeMaxSpeedFromPitch(double pitch) {
double sinePitch = (double)TrigMathUtil.sin(pitch);
double cosinePitch = Math.sqrt(1.0 - sinePitch * sinePitch);
double c = cosinePitch * this.maxHorizontalSpeed;
double s = sinePitch * (sinePitch > 0.0 ? this.maxClimbSpeed : this.maxSinkSpeed);
return Math.sqrt(c * c + s * s);
}
}