/// <inheritdoc/> public override void Apply(RigidBody body) { if (body == null) { throw new ArgumentNullException("body", "Rigid body in area of effect must not be null."); } // Get BuoyancyData. If necessary create new data. BuoyancyData data = body.BuoyancyData; if (data == null) { Prepare(body); data = body.BuoyancyData; } // Compute size of volume that is in the water and the center of the submerged volume. Vector3 centerOfSubmergedVolume; float submergedVolume = GetSubmergedVolume(body.Scale, body.Pose, data.Mesh, out centerOfSubmergedVolume); if (submergedVolume > 0) { // The up force. Vector3 buoyancyForce = (Density * submergedVolume * Gravity) * Surface.Normal; // The total volume of the body. float totalVolume = data.Volume * body.Scale.X * body.Scale.Y * body.Scale.Z; // The fraction of the total mass that is under the water (assuming constant density). float submergedMass = body.MassFrame.Mass * submergedVolume / totalVolume; // Compute linear drag. Vector3 centerLinearVelocity = body.GetVelocityOfWorldPoint(centerOfSubmergedVolume); Vector3 dragForce = (submergedMass * LinearDrag) * (Velocity - centerLinearVelocity); // Apply up force and linear drag force. Vector3 totalForce = buoyancyForce + dragForce; AddForce(body, totalForce, centerOfSubmergedVolume); // Apply torque for angular drag. // body.Length is proportional to the unscaled shape. Apply scaling to get a new // proportional value for the scaled shape. float length = data.Length * 1.0f / 3.0f * (body.Scale.X + body.Scale.Y + body.Scale.Z); float lengthSquared = length * length; Vector3 dragTorque = (-submergedMass * AngularDrag * lengthSquared) * body.AngularVelocity; AddTorque(body, dragTorque); } }
/// <summary> /// Prepares the specified rigid body for the buoyancy effect. /// </summary> /// <param name="body">The rigid body.</param> /// <remarks> /// <para> /// This method is automatically called for each body that touches the water. It computes /// additional information per rigid body that is needed for the buoyancy effect. /// </para> /// <para> /// To prepare the rigid bodies ahead of time, this method can be called manually - but this /// is not required. It is sufficient to call this method once per rigid body, then the body /// is prepared for all buoyancy effect instances. /// </para> /// </remarks> /// <exception cref="ArgumentNullException"> /// <paramref name="body"/> is <see langword="null"/>. /// </exception> public static void Prepare(RigidBody body) { // Must be called once per shape not really per body - but we keep this info internal in // case we make changes in the future, like per-body buoyancy settings. if (body == null) { throw new ArgumentNullException("body"); } Shape shape = body.Shape; // Try to use existing data for the same shape. var simulation = body.Simulation; if (simulation != null) { int numberOfRigidBodies = simulation.RigidBodies.Count; for (int i = 0; i < numberOfRigidBodies; i++) { var otherBody = simulation.RigidBodies[i]; if (otherBody.Shape == shape && otherBody.BuoyancyData != null) { body.BuoyancyData = otherBody.BuoyancyData; return; } } } BuoyancyData data = new BuoyancyData(); data.Mesh = shape.GetMesh(0.01f, 4); data.Volume = data.Mesh.GetVolume(); data.Length = shape.GetAabb(Pose.Identity).Extent.LargestComponent; body.BuoyancyData = data; }