package org.valkyrienskies.mod.common.physics; import java.util.LinkedList; import java.util.List; import java.util.PriorityQueue; import java.util.Queue; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import javax.vecmath.Matrix3d; import net.minecraft.block.Block; import net.minecraft.block.state.IBlockState; import net.minecraft.init.Blocks; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.util.math.BlockPos; import net.minecraft.world.World; import org.valkyrienskies.addon.control.block.torque.IRotationNodeWorld; import org.valkyrienskies.addon.control.block.torque.IRotationNodeWorldProvider; import org.valkyrienskies.addon.control.block.torque.ImplRotationNodeWorld; import org.valkyrienskies.addon.control.nodenetwork.INodeController; import org.valkyrienskies.mod.common.block.IBlockForceProvider; import org.valkyrienskies.mod.common.block.IBlockTorqueProvider; import org.valkyrienskies.mod.common.config.VSConfig; import org.valkyrienskies.mod.common.coordinates.ShipTransform; import org.valkyrienskies.mod.common.math.Quaternion; import org.valkyrienskies.mod.common.math.RotationMatrices; import org.valkyrienskies.mod.common.math.Vector; import org.valkyrienskies.mod.common.multithreaded.PhysicsShipTransform; import org.valkyrienskies.mod.common.physics.collision.WorldPhysicsCollider; import org.valkyrienskies.mod.common.physics.management.PhysicsObject; import org.valkyrienskies.mod.common.physics.management.ShipTransformationManager; import org.valkyrienskies.mod.common.util.ValkyrienNBTUtils; import valkyrienwarfare.api.TransformType; public class PhysicsCalculations implements IRotationNodeWorldProvider { public static final double DRAG_CONSTANT = .99D; public static final double INERTIA_OFFSET = .4D; public static final double EPSILON = .00000001; private final PhysicsObject parent; private final WorldPhysicsCollider worldCollision; private final PhysicsParticleManager particleManager; // CopyOnWrite to provide concurrency between threads. private final Set<BlockPos> activeForcePositions; private final IRotationNodeWorld physicsRotationNodeWorld; public org.valkyrienskies.mod.common.math.Vector gameTickCenterOfMass; public org.valkyrienskies.mod.common.math.Vector linearMomentum; public org.valkyrienskies.mod.common.math.Vector angularVelocity; public boolean actAsArchimedes; private org.valkyrienskies.mod.common.math.Vector physCenterOfMass; private org.valkyrienskies.mod.common.math.Vector torque; private double gameTickMass; // TODO: Get this in one day // private double physMass; // The time occurring on each PhysTick private double physTickTimeDelta; private double[] gameMoITensor; private double[] physMOITensor; private double[] physInvMOITensor; private double physRoll, physPitch, physYaw; private double physX, physY, physZ; public PhysicsCalculations(PhysicsObject toProcess) { parent = toProcess; worldCollision = new WorldPhysicsCollider(this); particleManager = new PhysicsParticleManager(this); gameMoITensor = RotationMatrices.getZeroMatrix(3); physMOITensor = RotationMatrices.getZeroMatrix(3); setPhysInvMOITensor(RotationMatrices.getZeroMatrix(3)); gameTickCenterOfMass = new org.valkyrienskies.mod.common.math.Vector( toProcess.getCenterCoord()); linearMomentum = new org.valkyrienskies.mod.common.math.Vector(); physCenterOfMass = new org.valkyrienskies.mod.common.math.Vector(); angularVelocity = new org.valkyrienskies.mod.common.math.Vector(); torque = new org.valkyrienskies.mod.common.math.Vector(); actAsArchimedes = false; // We need thread safe access to this. activeForcePositions = ConcurrentHashMap.newKeySet(); this.physicsRotationNodeWorld = new ImplRotationNodeWorld(parent); } public void onSetBlockState(IBlockState oldState, IBlockState newState, BlockPos pos) { World worldObj = getParent().world(); if (!newState.equals(oldState)) { if (BlockPhysicsDetails.isBlockProvidingForce(newState, pos, worldObj)) { activeForcePositions.add(pos); } else { activeForcePositions.remove(pos); } double oldMass = BlockPhysicsDetails.getMassFromState(oldState, pos, worldObj); double newMass = BlockPhysicsDetails.getMassFromState(newState, pos, worldObj); double deltaMass = newMass - oldMass; // Don't change anything if the mass is the same if (Math.abs(deltaMass) > EPSILON) { double x = pos.getX() + .5D; double y = pos.getY() + .5D; double z = pos.getZ() + .5D; deltaMass /= 9D; addMassAt(x, y, z, deltaMass); addMassAt(x + INERTIA_OFFSET, y + INERTIA_OFFSET, z + INERTIA_OFFSET, deltaMass); addMassAt(x + INERTIA_OFFSET, y + INERTIA_OFFSET, z - INERTIA_OFFSET, deltaMass); addMassAt(x + INERTIA_OFFSET, y - INERTIA_OFFSET, z + INERTIA_OFFSET, deltaMass); addMassAt(x + INERTIA_OFFSET, y - INERTIA_OFFSET, z - INERTIA_OFFSET, deltaMass); addMassAt(x - INERTIA_OFFSET, y + INERTIA_OFFSET, z + INERTIA_OFFSET, deltaMass); addMassAt(x - INERTIA_OFFSET, y + INERTIA_OFFSET, z - INERTIA_OFFSET, deltaMass); addMassAt(x - INERTIA_OFFSET, y - INERTIA_OFFSET, z + INERTIA_OFFSET, deltaMass); addMassAt(x - INERTIA_OFFSET, y - INERTIA_OFFSET, z - INERTIA_OFFSET, deltaMass); } } } private void addMassAt(double x, double y, double z, double addedMass) { org.valkyrienskies.mod.common.math.Vector prevCenterOfMass = new org.valkyrienskies.mod.common.math.Vector( gameTickCenterOfMass); if (gameTickMass > .0001D) { gameTickCenterOfMass.multiply(gameTickMass); gameTickCenterOfMass .add(new org.valkyrienskies.mod.common.math.Vector(x, y, z).getProduct(addedMass)); gameTickCenterOfMass.multiply(1.0D / (gameTickMass + addedMass)); } else { gameTickCenterOfMass = new org.valkyrienskies.mod.common.math.Vector(x, y, z); gameMoITensor = RotationMatrices.getZeroMatrix(3); } double cmShiftX = prevCenterOfMass.X - gameTickCenterOfMass.X; double cmShiftY = prevCenterOfMass.Y - gameTickCenterOfMass.Y; double cmShiftZ = prevCenterOfMass.Z - gameTickCenterOfMass.Z; double rx = x - gameTickCenterOfMass.X; double ry = y - gameTickCenterOfMass.Y; double rz = z - gameTickCenterOfMass.Z; gameMoITensor[0] = gameMoITensor[0] + (cmShiftY * cmShiftY + cmShiftZ * cmShiftZ) * gameTickMass + (ry * ry + rz * rz) * addedMass; gameMoITensor[1] = gameMoITensor[1] - cmShiftX * cmShiftY * gameTickMass - rx * ry * addedMass; gameMoITensor[2] = gameMoITensor[2] - cmShiftX * cmShiftZ * gameTickMass - rx * rz * addedMass; gameMoITensor[3] = gameMoITensor[1]; gameMoITensor[4] = gameMoITensor[4] + (cmShiftX * cmShiftX + cmShiftZ * cmShiftZ) * gameTickMass + (rx * rx + rz * rz) * addedMass; gameMoITensor[5] = gameMoITensor[5] - cmShiftY * cmShiftZ * gameTickMass - ry * rz * addedMass; gameMoITensor[6] = gameMoITensor[2]; gameMoITensor[7] = gameMoITensor[5]; gameMoITensor[8] = gameMoITensor[8] + (cmShiftX * cmShiftX + cmShiftY * cmShiftY) * gameTickMass + (rx * rx + ry * ry) * addedMass; // Do this to avoid a mass of zero, which runs the risk of dividing by zero and // crashing the program. if (gameTickMass + addedMass < .0001D) { gameTickMass = .0001D; getParent().setPhysicsEnabled(false); } else { gameTickMass += addedMass; } } public void generatePhysicsTransform() { // Create a new physics transform. physRoll = getParent().getWrapperEntity() .getRoll(); physPitch = getParent().getWrapperEntity() .getPitch(); physYaw = getParent().getWrapperEntity() .getYaw(); physX = getParent().getWrapperEntity().posX; physY = getParent().getWrapperEntity().posY; physZ = getParent().getWrapperEntity().posZ; physCenterOfMass.setValue(gameTickCenterOfMass); ShipTransform physicsTransform = new PhysicsShipTransform(physX, physY, physZ, physPitch, physYaw, physRoll, physCenterOfMass, getParent().getShipBoundingBox(), getParent().getShipTransformationManager() .getCurrentTickTransform()); getParent().getShipTransformationManager() .setCurrentPhysicsTransform(physicsTransform); // We're doing this afterwards to prevent from prevPhysicsTransform being null. getParent().getShipTransformationManager() .updatePreviousPhysicsTransform(); } public void rawPhysTickPreCol(double newPhysSpeed) { if (getParent().isPhysicsEnabled()) { updatePhysSpeedAndIters(newPhysSpeed); updateParentCenterOfMass(); calculateFramedMOITensor(); if (!parent.getShipAligningToGrid()) { // We are not marked for deconstruction, act normal. if (!actAsArchimedes) { calculateForces(); } else { calculateForcesArchimedes(); } } else { // We are trying to deconstruct, try to rotate the ship to grid to align with the grid. calculateForcesDeconstruction(); } } } public void rawPhysTickPostCol() { if (!isPhysicsBroken()) { if (getParent().isPhysicsEnabled()) { // This wasn't implemented very well at all! Maybe in the future I'll try again. // enforceStaticFriction(); if (VSConfig.doAirshipRotation) { integrateAngularVelocity(); } if (VSConfig.doAirshipMovement) { integrateLinearVelocity(); } } } else { getParent().setPhysicsEnabled(false); linearMomentum.zero(); angularVelocity.zero(); } PhysicsShipTransform finalPhysTransform = new PhysicsShipTransform(physX, physY, physZ, physPitch, physYaw, physRoll, physCenterOfMass, getParent().getShipBoundingBox(), getParent().getShipTransformationManager().getCurrentTickTransform()); getParent().getShipTransformationManager().updatePreviousPhysicsTransform(); getParent().getShipTransformationManager().setCurrentPhysicsTransform(finalPhysTransform); updatePhysCenterOfMass(); } // If the ship is moving at these speeds, its likely something in the physics // broke. This method helps detect that. private boolean isPhysicsBroken() { if (angularVelocity.lengthSq() > 50000 || linearMomentum.lengthSq() * getInvMass() * getInvMass() > 50000 || angularVelocity .isNaN() || linearMomentum.isNaN()) { System.out.println("Ship tried moving too fast; freezing it and reseting velocities"); return true; } return false; } // The x/y/z variables need to be updated when the centerOfMass location // changes. public void updateParentCenterOfMass() { org.valkyrienskies.mod.common.math.Vector parentCM = getParent().getCenterCoord(); if (!getParent().getCenterCoord().equals(gameTickCenterOfMass)) { org.valkyrienskies.mod.common.math.Vector CMDif = gameTickCenterOfMass .getSubtraction(parentCM); if (getParent().getShipTransformationManager() .getCurrentPhysicsTransform() != null) { getParent().getShipTransformationManager() .getCurrentPhysicsTransform() .rotate(CMDif, TransformType.SUBSPACE_TO_GLOBAL); } getParent().getWrapperEntity().posX -= CMDif.X; getParent().getWrapperEntity().posY -= CMDif.Y; getParent().getWrapperEntity().posZ -= CMDif.Z; getParent().getCenterCoord().setValue(gameTickCenterOfMass); } } /** * Updates the physics center of mass to the game center of mass; does not do any transformation * updates on its own. */ private void updatePhysCenterOfMass() { if (!physCenterOfMass.equals(gameTickCenterOfMass)) { org.valkyrienskies.mod.common.math.Vector CMDif = physCenterOfMass .getSubtraction(gameTickCenterOfMass); getParent().getShipTransformationManager().getCurrentPhysicsTransform() .rotate(CMDif, TransformType.SUBSPACE_TO_GLOBAL); physX += CMDif.X; physY += CMDif.Y; physZ += CMDif.Z; physCenterOfMass.setValue(gameTickCenterOfMass); } } /** * Generates the rotated moment of inertia tensor with the body; uses the following formula: I' * = R * I * R-transpose; where I' is the rotated inertia, I is un-rotated interim, and R is the * rotation matrix. */ private void calculateFramedMOITensor() { double[] framedMOI = RotationMatrices.getZeroMatrix(3); // Copy the rotation matrix, ignore the translation and scaling parts. Matrix3d rotationMatrix = getParent().getShipTransformationManager() .getCurrentPhysicsTransform().createRotationMatrix(TransformType.SUBSPACE_TO_GLOBAL); Matrix3d inertiaBodyFrame = new Matrix3d(gameMoITensor); // The product of the overall rotation matrix with the inertia tensor. inertiaBodyFrame.mul(rotationMatrix); rotationMatrix.transpose(); // The product of the inertia tensor multiplied with the transpose of the // rotation transpose. inertiaBodyFrame.mul(rotationMatrix); framedMOI[0] = inertiaBodyFrame.m00; framedMOI[1] = inertiaBodyFrame.m01; framedMOI[2] = inertiaBodyFrame.m02; framedMOI[3] = inertiaBodyFrame.m10; framedMOI[4] = inertiaBodyFrame.m11; framedMOI[5] = inertiaBodyFrame.m12; framedMOI[6] = inertiaBodyFrame.m20; framedMOI[7] = inertiaBodyFrame.m21; framedMOI[8] = inertiaBodyFrame.m22; physMOITensor = framedMOI; setPhysInvMOITensor(RotationMatrices.inverse3by3(framedMOI)); } protected void calculateForces() { applyAirDrag(); applyGravity(); // Collections.shuffle(activeForcePositions); org.valkyrienskies.mod.common.math.Vector blockForce = new org.valkyrienskies.mod.common.math.Vector(); org.valkyrienskies.mod.common.math.Vector inBodyWO = new org.valkyrienskies.mod.common.math.Vector(); org.valkyrienskies.mod.common.math.Vector crossVector = new org.valkyrienskies.mod.common.math.Vector(); World worldObj = getParent().world(); if (VSConfig.doPhysicsBlocks) { // We want to loop through all the physics nodes in a sorted order. Priority Queue handles that. Queue<INodeController> nodesPriorityQueue = new PriorityQueue<>( parent.getPhysicsControllersInShip()); while (nodesPriorityQueue.size() > 0) { INodeController controller = nodesPriorityQueue.poll(); controller.onPhysicsTick(parent, this, this.getPhysicsTimeDeltaPerPhysTick()); } this.physicsRotationNodeWorld.processTorquePhysics(getPhysicsTimeDeltaPerPhysTick()); SortedMap<IBlockTorqueProvider, List<BlockPos>> torqueProviders = new TreeMap<IBlockTorqueProvider, List<BlockPos>>(); for (BlockPos pos : activeForcePositions) { IBlockState state = getParent().getChunkAt(pos.getX() >> 4, pos.getZ() >> 4) .getBlockState(pos); Block blockAt = state.getBlock(); if (blockAt instanceof IBlockForceProvider) { try { BlockPhysicsDetails.getForceFromState(state, pos, worldObj, getPhysicsTimeDeltaPerPhysTick(), getParent(), blockForce); Vector otherPosition = ((IBlockForceProvider) blockAt) .getCustomBlockForcePosition(worldObj, pos, state, getParent(), getPhysicsTimeDeltaPerPhysTick()); if (otherPosition != null) { inBodyWO.setValue(otherPosition); inBodyWO.subtract(physCenterOfMass); getParent().getShipTransformationManager().getCurrentPhysicsTransform() .rotate(inBodyWO, TransformType.SUBSPACE_TO_GLOBAL); } else { inBodyWO.setValue(pos.getX() + .5, pos.getY() + .5, pos.getZ() + .5); inBodyWO.subtract(physCenterOfMass); getParent().getShipTransformationManager().getCurrentPhysicsTransform() .rotate(inBodyWO, TransformType.SUBSPACE_TO_GLOBAL); } addForceAtPoint(inBodyWO, blockForce, crossVector); // Add particles here. if (((IBlockForceProvider) blockAt).doesForceSpawnParticles()) { org.valkyrienskies.mod.common.math.Vector particlePos; if (otherPosition != null) { particlePos = new org.valkyrienskies.mod.common.math.Vector( otherPosition); } else { particlePos = new org.valkyrienskies.mod.common.math.Vector( pos.getX() + .5, pos.getY() + .5, pos.getZ() + .5); } parent.getShipTransformationManager().getCurrentPhysicsTransform() .transform(particlePos, TransformType.SUBSPACE_TO_GLOBAL); // System.out.println(particlePos); float posX = (float) particlePos.X; float posY = (float) particlePos.Y; float posZ = (float) particlePos.Z; float particleMass = 5f; float velX = (float) -(blockForce.X / particleMass); float velY = (float) -(blockForce.Y / particleMass); float velZ = (float) -(blockForce.Z / particleMass); // Half a second float particleLife = .5f; // System.out.println(blockForce); // System.out.println(posX + ":" + posY + ":" + posZ); // System.out.println(velX + ":" + velY + ":" + velZ); this.particleManager .spawnPhysicsParticle(posX, posY, posZ, velX, velY, velZ, particleMass, particleLife); } } catch (Exception e) { e.printStackTrace(); } } else if (blockAt instanceof IBlockTorqueProvider) { // Add it to the torque sorted map; we do this so the torque dampeners can run // after the gyroscope stabilizers. IBlockTorqueProvider torqueProviderBlock = (IBlockTorqueProvider) blockAt; if (!torqueProviders.containsKey(torqueProviderBlock)) { torqueProviders.put(torqueProviderBlock, new LinkedList<BlockPos>()); } torqueProviders.get(torqueProviderBlock).add(pos); } } // Now add the torque from the torque providers, in a sorted order! for (IBlockTorqueProvider torqueProviderBlock : torqueProviders.keySet()) { List<BlockPos> blockPositions = torqueProviders.get(torqueProviderBlock); for (BlockPos pos : blockPositions) { this.convertTorqueToVelocity(); org.valkyrienskies.mod.common.math.Vector torqueVector = torqueProviderBlock .getTorqueInGlobal(this, pos); if (torqueVector != null) { torque.add(torqueVector); } } } } particleManager.physicsTickAfterAllPreForces((float) getPhysicsTimeDeltaPerPhysTick()); convertTorqueToVelocity(); } private void applyGravity() { if (VSConfig.doGravity) { addForceAtPoint(new org.valkyrienskies.mod.common.math.Vector(0, 0, 0), VSConfig.gravity().getProduct(gameTickMass * getPhysicsTimeDeltaPerPhysTick())); } } private void calculateForcesArchimedes() { applyAirDrag(); } private void calculateForcesDeconstruction() { applyAirDrag(); Quaternion gridRotation = new Quaternion(0, 0, 0, 1); Quaternion inverseCurrentRotation = parent.getShipTransformationManager() .getCurrentPhysicsTransform() .createRotationQuaternion(TransformType.GLOBAL_TO_SUBSPACE); Quaternion r = gridRotation.crossProduct(inverseCurrentRotation); double theta = 2D * Math.acos(r.getW()); if (theta > Math.PI) { theta -= 2D * Math.PI; } org.valkyrienskies.mod.common.math.Vector idealAngularVelocity = new org.valkyrienskies.mod.common.math.Vector( r.getX(), r.getY(), r.getZ()); if (idealAngularVelocity.lengthSq() < EPSILON) { // We already have the perfect angular velocity, nothing left to do. return; } // Number of seconds we'd expect this angular velocity to convert us onto the grid orientation. double timeStep = 1D; double idealAngularVelocityMultiple = (-theta / (timeStep * idealAngularVelocity.length())); idealAngularVelocity.multiply(idealAngularVelocityMultiple); org.valkyrienskies.mod.common.math.Vector angularVelocityDif = idealAngularVelocity .getSubtraction(angularVelocity); // Larger values converge faster, but sacrifice collision accuracy angularVelocityDif.multiply(.01); angularVelocity.subtract(angularVelocityDif); } protected void applyAirDrag() { double drag = getDragForPhysTick(); linearMomentum.multiply(drag); angularVelocity.multiply(drag); } public void convertTorqueToVelocity() { if (!torque.isZero()) { angularVelocity .add(RotationMatrices.get3by3TransformedVec(getPhysInvMOITensor(), torque)); torque.zero(); } } public void addForceAtPoint(org.valkyrienskies.mod.common.math.Vector inBodyWO, org.valkyrienskies.mod.common.math.Vector forceToApply) { torque.add(inBodyWO.cross(forceToApply)); linearMomentum.add(forceToApply); } public void addForceAtPoint(org.valkyrienskies.mod.common.math.Vector inBodyWO, org.valkyrienskies.mod.common.math.Vector forceToApply, org.valkyrienskies.mod.common.math.Vector crossVector) { crossVector.setCross(inBodyWO, forceToApply); torque.add(crossVector); linearMomentum.add(forceToApply); } public void updatePhysSpeedAndIters(double newPhysSpeed) { physTickTimeDelta = newPhysSpeed; } /** * Only run ONCE per phys tick! */ private void integrateAngularVelocity() { ShipTransformationManager coordTrans = getParent().getShipTransformationManager(); double[] rotationChange = RotationMatrices .getRotationMatrix(angularVelocity.X, angularVelocity.Y, angularVelocity.Z, angularVelocity.length() * getPhysicsTimeDeltaPerPhysTick()); // Take the product of the current rotation with the change in rotation that results from // the angular velocity. Then change our pitch/yaw/roll based on the result. Quaternion rotationChangeQuat = Quaternion.QuaternionFromMatrix(rotationChange); Quaternion initialRotation = coordTrans.getCurrentPhysicsTransform() .createRotationQuaternion(TransformType.SUBSPACE_TO_GLOBAL); Quaternion finalRotation = initialRotation.crossProduct(rotationChangeQuat); // Update the pitch/yaw/roll angles. double[] radians = finalRotation.toRadians(); physPitch = Double.isNaN(radians[0]) ? 0.0f : (float) Math.toDegrees(radians[0]); physYaw = Double.isNaN(radians[1]) ? 0.0f : (float) Math.toDegrees(radians[1]); physRoll = Double.isNaN(radians[2]) ? 0.0f : (float) Math.toDegrees(radians[2]); } /** * Only run ONCE per phys tick! */ private void integrateLinearVelocity() { double momentMod = getPhysicsTimeDeltaPerPhysTick() * getInvMass(); physX += (linearMomentum.X * momentMod); physY += (linearMomentum.Y * momentMod); physZ += (linearMomentum.Z * momentMod); physY = Math.min(Math.max(physY, VSConfig.shipLowerLimit), VSConfig.shipUpperLimit); } public org.valkyrienskies.mod.common.math.Vector getVelocityAtPoint( org.valkyrienskies.mod.common.math.Vector inBodyWO) { org.valkyrienskies.mod.common.math.Vector speed = angularVelocity.cross(inBodyWO); double invMass = getInvMass(); speed.X += (linearMomentum.X * invMass); speed.Y += (linearMomentum.Y * invMass); speed.Z += (linearMomentum.Z * invMass); return speed; } public void writeToNBTTag(NBTTagCompound compound) { compound.setDouble("mass", gameTickMass); ValkyrienNBTUtils.writeVectorToNBT("linear", linearMomentum, compound); ValkyrienNBTUtils.writeVectorToNBT("angularVelocity", angularVelocity, compound); ValkyrienNBTUtils.writeVectorToNBT("CM", gameTickCenterOfMass, compound); ValkyrienNBTUtils.write3x3MatrixToNBT("MOI", gameMoITensor, compound); physicsRotationNodeWorld.writeToNBTTag(compound); compound.setString("block_mass_ver", BlockPhysicsDetails.BLOCK_MASS_VERSION); } public void readFromNBTTag(NBTTagCompound compound) { linearMomentum = ValkyrienNBTUtils.readVectorFromNBT("linear", compound); angularVelocity = ValkyrienNBTUtils.readVectorFromNBT("angularVelocity", compound); gameTickCenterOfMass = ValkyrienNBTUtils.readVectorFromNBT("CM", compound); gameTickMass = compound.getDouble("mass"); gameMoITensor = ValkyrienNBTUtils.read3x3MatrixFromNBT("MOI", compound); physicsRotationNodeWorld.readFromNBTTag(compound); if (!BlockPhysicsDetails.BLOCK_MASS_VERSION.equals(compound.getString("block_mass_ver"))) { this.recalculateShipInertia(); } } // Called upon a Ship being created from the World, and generates the physics // data for it public void recalculateShipInertia() { linearMomentum.zero(); angularVelocity.zero(); gameTickCenterOfMass.zero(); gameTickMass = 0; gameMoITensor = RotationMatrices.getZeroMatrix(3); IBlockState air = Blocks.AIR.getDefaultState(); for (BlockPos pos : getParent().getBlockPositions()) { onSetBlockState(air, getParent().getChunkAt(pos.getX() >> 4, pos.getZ() >> 4) .getBlockState(pos), pos); } } // These getter methods guarantee that only code within this class can modify // the mass, preventing outside code from breaking things public double getMass() { return gameTickMass; } public double getInvMass() { return 1D / gameTickMass; } public double getPhysicsTimeDeltaPerPhysTick() { return physTickTimeDelta; } public double getDragForPhysTick() { return Math.pow(DRAG_CONSTANT, getPhysicsTimeDeltaPerPhysTick() * 20D); } public void addPotentialActiveForcePos(BlockPos pos) { this.activeForcePositions.add(pos); } /** * @return The inverse moment of inertia tensor with local translation (0 vector is at the * center of mass), but rotated into world coordinates. */ public double[] getPhysInvMOITensor() { return physInvMOITensor; } /** * @param physInvMOITensor the physInvMOITensor to set */ private void setPhysInvMOITensor(double[] physInvMOITensor) { this.physInvMOITensor = physInvMOITensor; } /** * @return The moment of inertia tensor with local translation (0 vector is at the center of * mass), but rotated into world coordinates. */ public double[] getPhysMOITensor() { return this.physMOITensor; } /** * @return the parent */ public PhysicsObject getParent() { return parent; } /** * @return the worldCollision */ public WorldPhysicsCollider getWorldCollision() { return worldCollision; } public double getInertiaAlongRotationAxis() { org.valkyrienskies.mod.common.math.Vector rotationAxis = new org.valkyrienskies.mod.common.math.Vector( angularVelocity); rotationAxis.normalize(); RotationMatrices.applyTransform3by3(getPhysMOITensor(), rotationAxis); return rotationAxis.length(); } @Deprecated public org.valkyrienskies.mod.common.math.Vector getCopyOfPhysCoordinates() { return new Vector(physX, physY, physZ); } public IRotationNodeWorld getPhysicsRotationNodeWorld() { return physicsRotationNodeWorld; } }