// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.core.modules.physics;

import com.hypixel.hytale.server.core.asset.type.projectile.config.Projectile;
import com.hypixel.hytale.math.vector.Vector3f;
import com.hypixel.hytale.server.core.modules.collision.EntityContactData;
import com.hypixel.hytale.server.core.entity.EntityUtils;
import com.hypixel.hytale.server.core.modules.collision.IBlockTracker;
import com.hypixel.hytale.server.core.entity.Entity;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.modules.physics.component.Velocity;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.server.core.modules.physics.util.PhysicsMath;
import com.hypixel.hytale.math.util.NearestBlockUtil;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.protocol.BlockMaterial;
import com.hypixel.hytale.math.shape.Box;
import com.hypixel.hytale.server.core.modules.collision.BlockData;
import com.hypixel.hytale.server.core.modules.collision.BlockContactData;
import com.hypixel.hytale.server.core.modules.physics.util.PhysicsBodyStateUpdaterSymplecticEuler;
import com.hypixel.hytale.server.core.modules.entity.component.BoundingBox;
import com.hypixel.hytale.server.core.modules.physics.util.ForceProviderStandardState;
import com.hypixel.hytale.server.core.modules.physics.util.ForceProvider;
import com.hypixel.hytale.server.core.modules.physics.util.ForceProviderEntity;
import com.hypixel.hytale.server.core.modules.physics.util.PhysicsBodyState;
import com.hypixel.hytale.server.core.modules.physics.util.PhysicsBodyStateUpdater;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.function.consumer.QuadConsumer;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.ComponentAccessor;
import java.util.function.BiConsumer;
import java.util.UUID;
import com.hypixel.hytale.math.vector.Vector3d;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.modules.collision.BlockTracker;
import com.hypixel.hytale.server.core.modules.collision.EntityCollisionProvider;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.modules.collision.BlockCollisionProvider;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.modules.collision.IBlockCollisionConsumer;

@Deprecated
public class SimplePhysicsProvider implements IBlockCollisionConsumer
{
    protected static final double HIT_WATER_IMPULSE_LOSS = 0.2;
    protected static final double ROTATION_FORCE = 3.0;
    protected static final float SPEED_ROTATION_FACTOR = 2.0f;
    protected static final double SWIMMING_DAMPING_FACTOR = 1.0;
    protected static final double DEFAULT_MOVE_OUT_OF_SOLID_SPEED = 5.0;
    protected static final int WATER_DETECTION_EXTREMA_COUNT = 2;
    protected static final HytaleLogger LOGGER;
    @Nonnull
    protected final BlockCollisionProvider blockCollisionProvider;
    @Nonnull
    protected final EntityCollisionProvider entityCollisionProvider;
    @Nonnull
    protected final BlockTracker triggerTracker;
    @Nonnull
    protected final RestingSupport restingSupport;
    @Nullable
    protected World world;
    @Nonnull
    protected final Vector3d velocity;
    @Nonnull
    protected final Vector3d position;
    @Nonnull
    protected final Vector3d movement;
    protected boolean bounced;
    protected boolean onGround;
    protected boolean provideCharacterCollisions;
    protected double gravity;
    protected double bounciness;
    protected boolean sticksVertically;
    protected boolean computeYaw;
    protected boolean computePitch;
    protected ROTATION_MODE rotationMode;
    protected UUID creatorUuid;
    protected static final double minBounceEpsilon = 0.4;
    protected static final double minBounceEpsilonSquared = 0.16000000000000003;
    protected final Vector3d tempVector;
    protected BiConsumer<Vector3d, ComponentAccessor<EntityStore>> bounceConsumer;
    protected QuadConsumer<Ref<EntityStore>, Vector3d, Ref<EntityStore>, ComponentAccessor<EntityStore>> impactConsumer;
    protected double moveOutOfSolidSpeed;
    protected boolean movedInsideSolid;
    protected final Vector3d moveOutOfSolidVelocity;
    protected final Vector3d contactPosition;
    protected final Vector3d contactNormal;
    protected double collisionStart;
    protected final PhysicsBodyStateUpdater stateUpdater;
    protected final PhysicsBodyState stateBefore;
    protected final PhysicsBodyState stateAfter;
    protected double displacedMass;
    protected double subSurfaceVolume;
    protected double enterFluid;
    protected double leaveFluid;
    protected boolean inFluid;
    protected int velocityExtremaCount;
    @Nonnull
    protected STATE state;
    protected ForceProviderEntity forceProviderEntity;
    protected ForceProvider[] forceProviders;
    protected final ForceProviderStandardState forceProviderStandardState;
    protected double terminalVelocity1;
    protected double density1;
    protected double terminalVelocity2;
    protected double density2;
    protected double dragMultiplier;
    protected double dragOffset;
    protected final BlockTracker fluidTracker;
    protected double hitWaterImpulseLoss;
    protected double rotationForce;
    protected float speedRotationFactor;
    protected double swimmingDampingFactor;
    @Deprecated(forRemoval = true)
    protected BoundingBox boundingBox;
    
    public SimplePhysicsProvider() {
        this.computeYaw = true;
        this.computePitch = true;
        this.rotationMode = ROTATION_MODE.VelocityDamped;
        this.tempVector = new Vector3d();
        this.moveOutOfSolidVelocity = new Vector3d();
        this.contactPosition = new Vector3d();
        this.contactNormal = new Vector3d();
        this.stateUpdater = new PhysicsBodyStateUpdaterSymplecticEuler();
        this.stateBefore = new PhysicsBodyState();
        this.stateAfter = new PhysicsBodyState();
        this.velocityExtremaCount = Integer.MAX_VALUE;
        this.state = STATE.Active;
        this.forceProviderStandardState = new ForceProviderStandardState();
        this.fluidTracker = new BlockTracker();
        this.hitWaterImpulseLoss = 0.2;
        this.rotationForce = 3.0;
        this.speedRotationFactor = 2.0f;
        this.swimmingDampingFactor = 1.0;
        (this.blockCollisionProvider = new BlockCollisionProvider()).setRequestedCollisionMaterials(6);
        this.blockCollisionProvider.setReportOverlaps(true);
        this.entityCollisionProvider = new EntityCollisionProvider();
        this.triggerTracker = new BlockTracker();
        this.restingSupport = new RestingSupport();
        this.velocity = new Vector3d();
        this.position = new Vector3d();
        this.movement = new Vector3d();
    }
    
    public SimplePhysicsProvider(@Nonnull final BiConsumer<Vector3d, ComponentAccessor<EntityStore>> bounceConsumer, @Nonnull final QuadConsumer<Ref<EntityStore>, Vector3d, Ref<EntityStore>, ComponentAccessor<EntityStore>> impactConsumer) {
        this();
        this.bounceConsumer = bounceConsumer;
        this.impactConsumer = impactConsumer;
    }
    
    public void setImpacted(final boolean impacted) {
        this.state = (impacted ? STATE.Inactive : STATE.Active);
    }
    
    public boolean isImpacted() {
        return this.state == STATE.Inactive;
    }
    
    public void setResting(final boolean resting) {
        if (this.state != STATE.Inactive) {
            this.state = (resting ? STATE.Resting : STATE.Active);
        }
    }
    
    public boolean isResting() {
        return this.state == STATE.Resting;
    }
    
    @Nonnull
    @Override
    public Result onCollision(final int blockX, final int blockY, final int blockZ, @Nonnull final Vector3d direction, @Nonnull final BlockContactData contactData, @Nonnull final BlockData blockData, @Nonnull final Box collider) {
        final BlockMaterial blockMaterial = blockData.getBlockType().getMaterial();
        if (this.moveOutOfSolidSpeed > 0.0 && contactData.isOverlapping() && blockMaterial == BlockMaterial.Solid) {
            final Vector3i nearestBlock = NearestBlockUtil.findNearestBlock(this.position, (block, w) -> {
                final WorldChunk worldChunk = w.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(block.getX(), block.getZ()));
                return worldChunk != null && worldChunk.getBlockType(block).getMaterial() != BlockMaterial.Solid;
            }, this.world);
            if (nearestBlock != null) {
                this.tempVector.assign(nearestBlock.x, nearestBlock.y, nearestBlock.z);
                this.tempVector.add(0.5, 0.5, 0.5);
                this.tempVector.subtract(this.position);
                this.tempVector.setLength(this.moveOutOfSolidSpeed);
            }
            else {
                this.tempVector.assign(0.0, this.moveOutOfSolidSpeed, 0.0);
            }
            this.moveOutOfSolidVelocity.add(this.tempVector);
            this.movedInsideSolid = true;
            return Result.CONTINUE;
        }
        if (blockData.getFluidId() != 0 && !this.fluidTracker.isTracked(blockX, blockY, blockZ)) {
            final double collisionStart = contactData.getCollisionStart();
            final double collisionEnd = contactData.getCollisionEnd();
            if (collisionStart < this.enterFluid) {
                this.enterFluid = collisionStart;
            }
            if (collisionEnd > this.leaveFluid) {
                this.leaveFluid = collisionEnd;
            }
            if (collisionEnd <= collisionStart) {
                return Result.CONTINUE;
            }
            final double density = 1000.0;
            final double volume = PhysicsMath.volumeOfIntersection(this.boundingBox.getBoundingBox(), this.contactPosition, collider, blockX, blockY, blockZ);
            this.subSurfaceVolume += volume;
            this.displacedMass += volume * density;
            this.fluidTracker.trackNew(blockX, blockY, blockZ);
            return Result.CONTINUE;
        }
        else {
            if (contactData.isOverlapping()) {
                return Result.CONTINUE;
            }
            final double surfaceAlignment = direction.dot(contactData.getCollisionNormal());
            if (blockMaterial != BlockMaterial.Solid || surfaceAlignment == 0.0) {}
            if (surfaceAlignment >= 0.0) {
                return Result.CONTINUE;
            }
            this.contactPosition.assign(contactData.getCollisionPoint());
            this.contactNormal.assign(contactData.getCollisionNormal());
            this.collisionStart = contactData.getCollisionStart();
            this.bounced = true;
            return Result.STOP;
        }
    }
    
    @Nonnull
    @Override
    public Result probeCollisionDamage(final int blockX, final int blockY, final int blockZ, final Vector3d direction, final BlockContactData collisionData, final BlockData blockData) {
        return Result.CONTINUE;
    }
    
    @Override
    public void onCollisionDamage(final int blockX, final int blockY, final int blockZ, final Vector3d direction, final BlockContactData collisionData, final BlockData blockData) {
    }
    
    @Nonnull
    @Override
    public Result onCollisionSliceFinished() {
        return Result.CONTINUE;
    }
    
    @Override
    public void onCollisionFinished() {
    }
    
    @Nullable
    public Entity tick(final double dt, @Nonnull final Velocity entityVelocity, @Nonnull final World entityWorld, @Nonnull final TransformComponent entityTransform, final Ref<EntityStore> selfRef, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        if (this.state == STATE.Inactive) {
            entityVelocity.setZero();
            return null;
        }
        if (this.state == STATE.Resting) {
            if (this.forceProviderStandardState.externalForce.squaredLength() == 0.0 && !this.restingSupport.hasChanged(entityWorld)) {
                return null;
            }
            this.state = STATE.Active;
        }
        this.world = entityWorld;
        this.position.assign(entityTransform.getPosition());
        entityVelocity.assignVelocityTo(this.velocity);
        final double mass = this.forceProviderEntity.getMass(this.boundingBox.getBoundingBox().getVolume());
        this.forceProviderStandardState.convertToForces(dt, mass);
        this.forceProviderStandardState.updateVelocity(this.velocity);
        if (this.velocity.squaredLength() * dt * dt >= 1.0000000000000002E-10 || this.forceProviderStandardState.externalForce.squaredLength() >= 0.0) {
            this.state = STATE.Active;
        }
        else {
            this.velocity.assign(Vector3d.ZERO);
        }
        if (this.state == STATE.Resting && this.restingSupport.hasChanged(entityWorld)) {
            this.state = STATE.Active;
        }
        this.stateBefore.position.assign(this.position);
        this.stateBefore.velocity.assign(this.velocity);
        this.forceProviderEntity.setForceProviderStandardState(this.forceProviderStandardState);
        this.stateUpdater.update(this.stateBefore, this.stateAfter, mass, dt, this.onGround, this.forceProviders);
        this.velocity.assign(this.stateAfter.velocity);
        this.movement.assign(this.velocity).scale(dt);
        this.forceProviderStandardState.clear();
        if (this.velocity.squaredLength() * dt * dt >= 1.0000000000000002E-10) {
            this.state = STATE.Active;
        }
        else {
            this.velocity.assign(Vector3d.ZERO);
        }
        double maxRelativeDistance = 1.0;
        if (this.provideCharacterCollisions) {
            Ref<EntityStore> creatorReference = null;
            if (this.creatorUuid != null) {
                creatorReference = entityWorld.getEntityRef(this.creatorUuid);
            }
            maxRelativeDistance = this.entityCollisionProvider.computeNearest(this.boundingBox.getBoundingBox(), this.position, this.movement, selfRef, creatorReference, componentAccessor);
            if (maxRelativeDistance < 0.0 || maxRelativeDistance > 1.0) {
                maxRelativeDistance = 1.0;
            }
        }
        this.bounced = false;
        this.onGround = false;
        this.moveOutOfSolidVelocity.assign(Vector3d.ZERO);
        this.movedInsideSolid = false;
        this.displacedMass = 0.0;
        this.subSurfaceVolume = 0.0;
        this.enterFluid = Double.MAX_VALUE;
        this.leaveFluid = -1.7976931348623157E308;
        this.collisionStart = maxRelativeDistance;
        this.contactPosition.assign(this.position).addScaled(this.movement, this.collisionStart);
        this.contactNormal.assign(Vector3d.ZERO);
        this.blockCollisionProvider.cast(entityWorld, this.boundingBox.getBoundingBox(), this.position, this.movement, this, this.triggerTracker, maxRelativeDistance);
        this.fluidTracker.reset();
        final double density = (this.displacedMass > 0.0) ? (this.displacedMass / this.subSurfaceVolume) : 1.2;
        if (this.movedInsideSolid) {
            this.position.addScaled(this.moveOutOfSolidVelocity, dt);
            this.velocity.assign(this.moveOutOfSolidVelocity);
            this.forceProviderStandardState.dragCoefficient = this.getDragCoefficient(density);
            this.forceProviderStandardState.displacedMass = this.displacedMass;
            this.forceProviderStandardState.gravity = this.gravity;
            this.finishTick(entityTransform, entityVelocity);
            return null;
        }
        double velocityClip = this.bounced ? this.collisionStart : 1.0;
        boolean enteringWater = false;
        if (!this.inFluid && this.enterFluid < this.collisionStart) {
            this.inFluid = true;
            velocityClip = this.enterFluid;
            this.velocityExtremaCount = 2;
            enteringWater = true;
        }
        else if (this.inFluid && this.leaveFluid < this.collisionStart) {
            this.inFluid = false;
            velocityClip = this.leaveFluid;
            this.velocityExtremaCount = 2;
        }
        if (velocityClip > 0.0 && velocityClip < 1.0) {
            this.stateUpdater.update(this.stateBefore, this.stateAfter, mass, dt * velocityClip, this.onGround, this.forceProviders);
            this.velocity.assign(this.stateAfter.velocity);
        }
        if (this.inFluid && this.subSurfaceVolume < this.boundingBox.getBoundingBox().getVolume() && this.velocityExtremaCount > 0) {
            final double speedBefore = this.stateBefore.velocity.y;
            final double speedAfter = this.stateAfter.velocity.y;
            if (speedBefore * speedAfter <= 0.0) {
                --this.velocityExtremaCount;
            }
        }
        if (this.isSwimming()) {
            final Vector3d externalForce = this.forceProviderStandardState.externalForce;
            externalForce.y -= this.stateAfter.velocity.y * (this.swimmingDampingFactor / mass);
        }
        if (enteringWater) {
            this.forceProviderStandardState.externalImpulse.addScaled(this.stateAfter.velocity, -this.hitWaterImpulseLoss * mass);
        }
        this.forceProviderStandardState.displacedMass = this.displacedMass;
        this.forceProviderStandardState.dragCoefficient = this.getDragCoefficient(density);
        this.forceProviderStandardState.gravity = this.gravity;
        if (this.bounced) {
            this.position.assign(this.contactPosition);
            computeReflectedVector(this.velocity, this.contactNormal, this.velocity);
            this.velocity.scale(this.bounciness);
            if (this.velocity.squaredLength() * dt * dt < 0.16000000000000003) {
                final boolean hitGround = this.contactNormal.equals(Vector3d.UP);
                if (this.sticksVertically || hitGround) {
                    this.state = STATE.Resting;
                    this.restingSupport.rest(entityWorld, this.boundingBox.getBoundingBox(), this.position);
                    this.onGround = hitGround;
                    if (this.impactConsumer != null) {
                        this.impactConsumer.accept(selfRef, this.position, null, componentAccessor);
                    }
                }
                this.velocity.assign(Vector3d.ZERO);
            }
            else if (this.bounceConsumer != null) {
                this.bounceConsumer.accept(this.position, componentAccessor);
            }
            this.rotateBody(dt, entityTransform.getRotation());
            this.finishTick(entityTransform, entityVelocity);
            return null;
        }
        if (this.entityCollisionProvider.getCount() > 0) {
            final EntityContactData contact = this.entityCollisionProvider.getContact(0);
            final Ref<EntityStore> contactRef = contact.getEntityReference();
            final Entity target = EntityUtils.getEntity(contactRef, componentAccessor);
            this.position.assign(contact.getCollisionPoint());
            this.state = STATE.Inactive;
            if (this.impactConsumer != null) {
                this.impactConsumer.accept(selfRef, this.position, contactRef, componentAccessor);
            }
            this.rotateBody(dt, entityTransform.getRotation());
            this.finishTick(entityTransform, entityVelocity);
            return target;
        }
        this.position.add(this.movement);
        this.rotateBody(dt, entityTransform.getRotation());
        this.finishTick(entityTransform, entityVelocity);
        return null;
    }
    
    protected void finishTick(@Nonnull final TransformComponent position, @Nonnull final Velocity velocity) {
        position.setPosition(this.position);
        velocity.set(this.velocity);
        this.world = null;
        this.entityCollisionProvider.clear();
    }
    
    protected void rotateBody(final double dt, @Nonnull final Vector3f bodyRotation) {
        if (!this.isComputeYaw() && !this.isComputePitch()) {
            return;
        }
        final double vx = this.stateAfter.velocity.x;
        final double vz = this.stateAfter.velocity.z;
        if (vx * vx + vz * vz <= 1.0000000000000002E-10) {
            return;
        }
        switch (this.rotationMode.ordinal()) {
            case 1: {
                if (this.isComputeYaw()) {
                    bodyRotation.setYaw(PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(vx, vz)));
                }
                if (this.isComputePitch()) {
                    bodyRotation.setPitch(PhysicsMath.pitchFromDirection(vx, this.stateAfter.velocity.y, vz));
                    break;
                }
                break;
            }
            case 2: {
                if (this.isComputeYaw()) {
                    bodyRotation.setYaw(PhysicsMath.normalizeTurnAngle(PhysicsMath.headingFromDirection(vx, vz)));
                }
                if (this.isComputePitch()) {
                    final float pitch = bodyRotation.getPitch();
                    float targetPitch = PhysicsMath.pitchFromDirection(vx, this.velocity.y, vz);
                    float delta = PhysicsMath.normalizeTurnAngle(targetPitch - pitch);
                    final float maxDelta = (float)(this.velocity.squaredLength() * dt * this.speedRotationFactor);
                    if (delta > maxDelta) {
                        targetPitch = pitch + maxDelta;
                        delta = maxDelta;
                    }
                    else if (delta < -maxDelta) {
                        targetPitch = pitch - maxDelta;
                        delta = maxDelta;
                    }
                    bodyRotation.setPitch(targetPitch);
                    this.forceProviderStandardState.externalForce.addScaled(this.stateAfter.velocity, delta * -this.rotationForce);
                    break;
                }
                break;
            }
        }
    }
    
    public boolean isOnGround() {
        return this.onGround;
    }
    
    public boolean isSwimming() {
        return this.velocityExtremaCount <= 0;
    }
    
    public static void computeReflectedVector(@Nonnull final Vector3d vec, @Nonnull final Vector3d normal, @Nonnull final Vector3d result) {
        result.assign(vec);
        final double squaredLength = normal.squaredLength();
        if (squaredLength == 0.0) {
            return;
        }
        final double proj = vec.dot(normal) / squaredLength;
        result.addScaled(normal, -2.0 * proj);
    }
    
    public boolean isProvidingCharacterCollisions() {
        return this.provideCharacterCollisions;
    }
    
    public void setProvideCharacterCollisions(final boolean provideCharacterCollisions) {
        this.provideCharacterCollisions = provideCharacterCollisions;
    }
    
    public void setGravity(final double gravity, @Nonnull final BoundingBox boundingBox) {
        this.gravity = gravity;
        this.recomputeDragFactors(boundingBox);
    }
    
    public void setBounciness(final double bounciness) {
        this.bounciness = bounciness;
    }
    
    public void setTerminalVelocities(final double terminalVelocityAir, final double terminalVelocityWater, @Nonnull final BoundingBox boundingBox) {
        this.setTerminalVelocities(terminalVelocityAir, 1.2, terminalVelocityWater, 998.0, boundingBox);
    }
    
    public void setTerminalVelocities(final double terminalVelocity1, final double density1, final double terminalVelocity2, final double density2, @Nonnull final BoundingBox boundingBox) {
        this.terminalVelocity1 = terminalVelocity1;
        this.density1 = density1;
        this.terminalVelocity2 = terminalVelocity2;
        this.density2 = density2;
        this.recomputeDragFactors(boundingBox);
    }
    
    @Nonnull
    @Deprecated
    public SimplePhysicsProvider setImpactSlowdown(final double impactSlowdown) {
        return this;
    }
    
    public void setSticksVertically(final boolean sticksVertically) {
        this.sticksVertically = sticksVertically;
    }
    
    public boolean isComputeYaw() {
        return this.computeYaw;
    }
    
    public void setComputeYaw(final boolean computeYaw) {
        this.computeYaw = computeYaw;
    }
    
    public boolean isComputePitch() {
        return this.computePitch;
    }
    
    public void setComputePitch(final boolean computePitch) {
        this.computePitch = computePitch;
    }
    
    public void setCreatorId(final UUID creatorUuid) {
        this.creatorUuid = creatorUuid;
    }
    
    public void initialize(@Nullable final Projectile projectile, @Nonnull final BoundingBox boundingBox) {
        if (projectile == null) {
            return;
        }
        this.boundingBox = boundingBox;
        this.forceProviderEntity = new ForceProviderEntity(boundingBox);
        this.forceProviders = new ForceProvider[] { this.forceProviderEntity };
        this.setGravity(projectile.getGravity(), boundingBox);
        final double terminalVelocity = projectile.getTerminalVelocity();
        this.setTerminalVelocities(terminalVelocity, terminalVelocity * projectile.getWaterTerminalVelocityMultiplier(), boundingBox);
        this.hitWaterImpulseLoss = projectile.getWaterHitImpulseLoss();
        this.rotationForce = projectile.getDampingRotation();
        this.speedRotationFactor = (float)projectile.getRotationSpeedVelocityRatio();
        this.swimmingDampingFactor = projectile.getSwimmingDampingFactor();
        this.rotationMode = projectile.getRotationMode();
        this.setBounciness(projectile.getBounciness() * (1.0 - projectile.getImpactSlowdown()));
        this.setImpactSlowdown(projectile.getImpactSlowdown());
        this.setSticksVertically(projectile.isSticksVertically());
        this.setComputeYaw(projectile.isComputeYaw());
        this.setComputePitch(projectile.isComputePitch());
        this.forceProviderEntity.setDensity(projectile.getDensity());
    }
    
    @Nonnull
    public Vector3d getVelocity() {
        return this.velocity;
    }
    
    public void addVelocity(final float x, final float y, final float z) {
        this.forceProviderStandardState.externalVelocity.add(x, y, z);
    }
    
    public void setVelocity(@Nonnull final Vector3d velocity) {
        this.forceProviderStandardState.nextTickVelocity.assign(velocity);
    }
    
    public void setMoveOutOfSolid(final boolean moveOutOfSolid) {
        this.setMoveOutOfSolid(moveOutOfSolid ? 5.0 : 0.0);
    }
    
    public void setMoveOutOfSolid(final double speed) {
        this.moveOutOfSolidSpeed = Math.max(0.0, speed);
    }
    
    protected double getDragCoefficient(final double density) {
        return this.dragMultiplier * density + this.dragOffset;
    }
    
    protected void recomputeDragFactors(@Nonnull final BoundingBox boundingBoxComponent) {
        final Box boundingBox = boundingBoxComponent.getBoundingBox();
        final double area = boundingBox.width() * boundingBox.depth();
        final double mass = this.forceProviderEntity.getMass(boundingBox.getVolume());
        final double drag1 = PhysicsMath.computeDragCoefficient(this.terminalVelocity1, area, mass, this.gravity);
        final double drag2 = PhysicsMath.computeDragCoefficient(this.terminalVelocity2, area, mass, this.gravity);
        this.dragMultiplier = (drag2 - drag1) / (this.density2 - this.density1);
        this.dragOffset = drag1 - this.dragMultiplier * this.density1;
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
    }
    
    public enum STATE
    {
        Active, 
        Resting, 
        Inactive;
    }
    
    public enum ROTATION_MODE
    {
        None, 
        Velocity, 
        VelocityDamped;
    }
}
