Files
2025-09-18 17:55:52 -04:00

696 lines
26 KiB
C++

#include "stdafx.h"
#include "v8World/Buoyancy.h"
#include "Util/Math.h"
#include "Voxel/Util.h"
#include "V8DataModel/MegaCluster.h"
#include "V8Kernel/Kernel.h"
#include "V8Kernel/Body.h"
#include "V8Kernel/Constants.h"
#include "V8World/ContactManager.h"
#include "V8World/Primitive.h"
#include "V8World/World.h"
#include "v8world/Assembly.h"
#include "voxel/Grid.h"
#include "voxel2/Grid.h"
namespace RBX
{
using namespace Voxel;
///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
float BuoyancyContact::waterViscosity = 0.02; // This is exposed as prop_WaterViscosity in PhysicsSettings.cpp
const float BuoyancyContact::waterDensity = 1.00f;
void BuoyancyContact::removeAllConnectorsFromKernel()
{
Kernel* kernel = NULL;
for (size_t i = 0; i < connectors.size(); ++i) {
if (connectors[i]->isInKernel()) {
kernel = kernel ? kernel : getKernel(); // small optimization - getKernel walks the IPipelines
kernel->removeConnector(connectors[i]);
}
}
}
void BuoyancyContact::putAllConnectorsInKernel()
{
Kernel* kernel = getKernel();;
for (size_t i = 0; i < connectors.size(); ++i) {
if (!connectors[i]->isInKernel()) {
kernel->insertConnector(connectors[i]);
}
}
}
void BuoyancyContact::deleteConnectors()
{
removeAllConnectorsFromKernel();
for (size_t i = 0; i < connectors.size(); ++i) {
RBXASSERT(!connectors[i]->isInKernel());
delete connectors[i];
}
connectors.fastClear();
floaterPrim->onBuoyancyChanged( false );
}
void BuoyancyContact::deleteAllConnectors()
{
deleteConnectors();
}
Geometry::GeometryType BuoyancyContact::determineGeometricType( Primitive *primitive )
{
if (RBX::PartInstance::fromPrimitive(primitive)->getPartType() == CYLINDER_PART)
return Geometry::GEOMETRY_CYLINDER;
if (primitive->getCollideType() == Geometry::COLLIDE_BALL)
return Geometry::GEOMETRY_BALL;
if (primitive->getGeometryType() == Geometry::GEOMETRY_WEDGE)
return Geometry::GEOMETRY_WEDGE;
if (primitive->getGeometryType() == Geometry::GEOMETRY_CORNERWEDGE)
return Geometry::GEOMETRY_CORNERWEDGE;
return Geometry::GEOMETRY_BLOCK;
}
void BuoyancyContact::updateBuoyancyFloatingForce()
{
fullBuoyancy.x = 0.0f;
fullBuoyancy.z = 0.0f;
if (getPrimitive(0)->getWorld()->getUsingNewPhysicalProperties())
{
// Buoyancy force does not depend on the object density. Only water density.
fullBuoyancy.y = - floaterPrim->getGeometry()->getVolume() * Units::kmsAccelerationToRbx( RBX::Constants::getKmsGravity() ) * waterDensity;
}
else
{
// Buoyant force: Fb = p_water * grav_constant * Volume_displaced
// Can be expanded into: Fb = p_o * g * V / (p_w/p_o)
// Unfortunately legacy Roblox never knew the correct mass of an object, so what the hell?
fullBuoyancy.y = - floaterPrim->getConstBody()->getMass() * (Units::kmsAccelerationToRbx( RBX::Constants::getKmsGravity() ) / floaterPrim->getSpecificGravity());
}
}
BuoyancyContact::BuoyancyContact( Primitive* p0, Primitive* p1 ) :
floaterPrim(p1),
Contact(p0, p1),
radius(floaterPrim->getRadius()),
fullSurfaceArea(0.0f),
voxelGrid(NULL),
smoothGrid(NULL)
{
updateBuoyancyFloatingForce();
MegaClusterInstance* mci = boost::polymorphic_downcast<MegaClusterInstance*>(p0->getOwner());
if (mci->isSmooth())
smoothGrid = mci->getSmoothGrid();
else
voxelGrid = mci->getVoxelGrid();
}
BuoyancyContact::~BuoyancyContact()
{
deleteConnectors();
RBXASSERT(connectors.size() == 0);
floaterPrim->onBuoyancyChanged( false );
}
void BuoyancyContact::computeExtentsWaterBand( const Extents& extents, float& floatDistance, float& sinkDistance )
{
float waterLevel;
if (hasDistanceSubmergedUnderWater(extents.min(), waterLevel, extents.max())) {
floatDistance = waterLevel - extents.min().y;
RBXASSERT(floatDistance >= -1e-5);
if (worldPosUnderWater(extents.max())) {
// fully submerged
sinkDistance = -1.0f;
} else if (worldPosAboveWater(extents.max(), extents.min().y, waterLevel)) {
// Partially submerged
sinkDistance = extents.max().y - waterLevel;
RBXASSERT(sinkDistance >= -1e-5);
} else {
// Degenerate case: no water found below the top point within the extent.
// Bottom side in the water while top side out of water.
// Let's test the center and approximate
if (worldPosUnderWater(extents.bottomCenter()))
sinkDistance = floatDistance; // receive 1/2 of the buoyancy
else
floatDistance = -1.0f; // receive no buoyancy
}
} else {
// No water found above the bottom point
if (hasDistanceSubmergedUnderWater(extents.max(), waterLevel, extents.max())) {
floatDistance = waterLevel - extents.min().y;
RBXASSERT(floatDistance >= -1e-5);
if (worldPosAboveWater(extents.min(), extents.min().y - floatDistance, waterLevel)) {
// Partially submerged
sinkDistance = extents.max().y - waterLevel;
} else {
// Degenerate case: No water found below the bottom point within extent.
// Top side in the water while bottom side out of water.
// Let's test the center and approximate
if (worldPosUnderWater(extents.bottomCenter()))
sinkDistance = floatDistance; // receive 1/2 of the buoyancy
else
floatDistance = -1.0f; // receive no buoyancy
}
} else {
// Make sure we are completely out of water
floatDistance = 0.0f;
for (unsigned int i = 0; i < 7; ++i) {
Vector3 corner = extents.getCorner(i);
if (corner != extents.min() && corner != extents.max() && worldPosUnderWater(corner))
floatDistance += 1.0f;
}
if (floatDistance > 0.0f) {
// Aha, singular case, some middle corners are in the water.
// Sample more points to have a smoother approximation
if (worldPosUnderWater(extents.center()))
floatDistance += 1.0f;
for (unsigned int i = 0; i < 7; ++i) {
Vector3 midCorner = (extents.getCorner(i) + extents.center()) / 2;
if (worldPosUnderWater(midCorner))
floatDistance += 1.0f;
}
sinkDistance = 17.0f - floatDistance;
} else
floatDistance = -1.0f; // completely out of water
}
}
}
// Linear Interpolation.
// Override this to provide more accurate interpolation for a specific shape
//
void BuoyancyContact::updateSubmergeRatio()
{
for (unsigned int i = 0; i < connectors.size(); ++i) {
float floatDistance, sinkDistance;
connectors[i]->getWaterBand(floatDistance, sinkDistance);
if (floatDistance <= 0.0f)
connectors[i]->setSubMergeRatio(0.0f); // outside water
else if (sinkDistance <= 0.0f)
connectors[i]->setSubMergeRatio(1.0f); // fully submerged
else
connectors[i]->setSubMergeRatio(floatDistance / (floatDistance + sinkDistance));
}
}
Vector3 BuoyancyContact::getWaterVelocity( int i )
{
return cellVelocity( connectors[i]->getWorldPosition() );
}
void BuoyancyContact::onPrimitiveContactParametersChanged()
{
if (floaterPrim->getWorld() && floaterPrim->getWorld()->getUsingNewPhysicalProperties())
{
floaterPrim->getWorld()->ticklePrimitive(floaterPrim, true);
}
}
void BuoyancyContact::updateConnectors()
{
const Vector3 partialBuoyancy = fullBuoyancy / connectors.size();
float viscosity_K = -waterViscosity / RBX::Constants::kernelDt() / connectors.size();
for (unsigned int i = 0; i < connectors.size(); ++i) {
float submergeRatio = connectors[i]->getSubMergeRatio();
if (submergeRatio > 0.0f) {
const Vector3 relativeVelocity = floaterPrim->getPV().velocity.linear - getWaterVelocity(i);
if ( relativeVelocity.isZero() ) // to avoid division by zero
connectors[i]->setForce( partialBuoyancy * submergeRatio );
else
{
const Vector3 relativeVelocityInObject = floaterPrim->getCoordinateFrame().vectorToObjectSpace(relativeVelocity);
// Scale the viscosity by the cross sections in axis aligned directions
Vector3 viscosityInObject = relativeVelocityInObject * getCrossSections(i, relativeVelocityInObject);
Vector3 linearViscosity = viscosity_K * floaterPrim->getCoordinateFrame().vectorToWorldSpace(viscosityInObject);
connectors[i]->setForce( ( partialBuoyancy + linearViscosity ) * submergeRatio );
}
} else
connectors[i]->setForce( Vector3::zero() );
}
}
bool BuoyancyContact::stepContact()
{
if (computeIsColliding(0.0)) {
if (inKernel()) {
if (connectors.size() == 0) {
createConnectors();
putAllConnectorsInKernel();
}
updateWaterBand();
updateSubmergeRatio();
updateConnectors();
}
floaterPrim->onBuoyancyChanged( true );
return true;
}
else {
deleteAllConnectors();
return false;
}
}
bool BuoyancyContact::cellHasWater( Vector3int16 pos )
{
if (smoothGrid)
return smoothGrid->getCell(pos.x, pos.y, pos.z).getMaterial() == Voxel2::Cell::Material_Water;
else
return !voxelGrid->getWaterCell(pos).isEmpty();
}
bool BuoyancyContact::worldPosUnderWater( const Vector3& pos )
{
Vector3int16 internalPos(worldToCell_floor(pos));
return cellHasWater(internalPos);
}
// Search water level above the specified world pos if under water
bool BuoyancyContact::hasDistanceSubmergedUnderWater( const Vector3& worldpos, float& waterLevel, const Vector3& worldMaxSearch )
{
Vector3int16 pos(worldToCell_floor(worldpos));
if (!cellHasWater(pos))
return false;
Vector3int16 endPos(worldToCell_floor(worldMaxSearch));
pos.y++;
// TODO: Optimization: Lazy eval material
while (pos.y <= endPos.y && cellHasWater(pos))
pos.y++;
waterLevel = cellToWorld_smallestCorner(pos).y;
return true;
}
// Search water level below the specified world pos if above water
bool BuoyancyContact::worldPosAboveWater( const Vector3& worldpos, int minY, float& waterLevel )
{
Vector3int16 pos(worldToCell_floor(worldpos));
// Skip the current cell the worldpos is located
do {
waterLevel = cellToWorld_smallestCorner(pos).y;
pos.y--;
} while (!cellHasWater(pos) && waterLevel >= minY);
if (waterLevel >= minY)
return true; // found water below the position
return false; // no water below the position and above minY
}
Vector3 BuoyancyContact::cellVelocity( const Vector3& worldpos )
{
if (!voxelGrid)
return Vector3::zero();
Vector3int16 pos(worldToCell_floor(worldpos));
Cell cell = voxelGrid->getWaterCell(pos);
if (cell.isEmpty())
return Vector3::zero();
int magnitude = 16 * cell.water.getForce();
Vector3 velocity(0.0f, 0.0f, 0.0f);
velocity[cell.water.getDirection() >> 1] = (cell.water.getDirection() & 0x1) ? magnitude : -magnitude;
return velocity;
}
// extremely expedited "broadphase" check
bool BuoyancyContact::isTouchingWater( Primitive* prim )
{
Extents extents = prim->getExtentsWorld();
// check center
if (worldPosUnderWater(extents.center()))
return true;
// check circumscribing corners
for (int i = 0; i < 8; ++i)
if (worldPosUnderWater(extents.getCorner(i)))
return true;
return false;
}
bool BuoyancyContact::computeIsColliding( float )
{
return isTouchingWater(floaterPrim);
}
bool BuoyancyContact::computeIsCollidingUi( float )
{
// override to always return false so can build underwater; shouldn't affect HumanoidState code
getPrimitive(0)->getFastFuzzyExtents(); // updates - outside of world loop
getPrimitive(1)->getFastFuzzyExtents();
return false;
}
BuoyancyContact* BuoyancyContact::create( Primitive* p0, Primitive *p1 )
{
Geometry::GeometryType geoType = BuoyancyContact::determineGeometricType(p1);
BuoyancyContact* contact = NULL;
switch( geoType )
{
case Geometry::GEOMETRY_BALL:
contact = new BuoyancyBallContact(p0, p1);
break;
case Geometry::GEOMETRY_CYLINDER:
contact = new BuoyancyCylinderContact(p0, p1);
break;
case Geometry::GEOMETRY_BLOCK:
contact = new BuoyancyBoxContact(p0, p1);
break;
case Geometry::GEOMETRY_WEDGE:
contact = new BuoyancyWedgeContact(p0, p1);
break;
case Geometry::GEOMETRY_CORNERWEDGE:
contact = new BuoyancyCornerWedgeContact(p0, p1);
break;
default:
RBXASSERT(contact);
}
contact->initializeCrossSections();
return contact;
}
//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
void BuoyancyBallContact::initializeCrossSections()
{
radius = floaterPrim->getSize().x / 2.0f;
// Partial submerged cross section will be interpolated as submerged volume
crossSectionArea = Math::pif() * radius * radius;
fullSurfaceArea = 4.0f * crossSectionArea;
}
void BuoyancyBallContact::getSurfaceAreaInDirection(const Vector3& relativeVelocity, float& crossArea, float& tangentArea)
{
crossArea = crossSectionArea;
tangentArea = 2.0f * crossSectionArea;
}
Vector3 BuoyancyBallContact::getCrossSections(int, const Vector3&)
{
return Vector3(crossSectionArea, crossSectionArea, crossSectionArea) / 8.0f;
}
bool BuoyancyBallContact::computeIsColliding( float overlapIgnored )
{
Vector3 center = floaterPrim->getPV().position.translation;
if (worldPosUnderWater(center))
return true;
Extents extents = floaterPrim->getExtentsLocal();
for (int faceId = NORM_X; faceId <= NORM_Z_NEG; ++faceId)
if (worldPosUnderWater(center + extents.faceCenter(static_cast<NormalId>(faceId))))
return true;
return false;
}
void BuoyancyBallContact::createConnectors()
{
connectors.push_back(new BuoyancyConnector(getBody(0), getBody(1), Vector3::zero()));
}
void BuoyancyBallContact::updateWaterBand()
{
RBXASSERT(connectors.size() == 1); // Ball has only one connector
float floatDistance, sinkDistance, waterLevel;
Vector3 center = floaterPrim->getPV().position.translation;
Vector3 bottom = center + Vector3(0.0f, -radius, 0.0f);
Vector3 top = center + Vector3(0.0f, radius, 0.0f);
if (hasDistanceSubmergedUnderWater(bottom, waterLevel, top)) {
floatDistance = waterLevel - bottom.y;
RBXASSERT(floatDistance >= -1e-5);
if (worldPosUnderWater(top)) {
// fully submerged
sinkDistance = -1.0f;
} else {
bool aboveWater = worldPosAboveWater(top, bottom.y, waterLevel);
RBXASSERT(aboveWater);
// Partially submerged
sinkDistance = top.y - waterLevel;
RBXASSERT(sinkDistance >= -1e-5);
}
} else {
// No water found above the bottom point
if (worldPosAboveWater(top, bottom.y, waterLevel)) {
floatDistance = top.y - waterLevel;
RBXASSERT(floatDistance >= -1e-5);
sinkDistance = waterLevel - bottom.y;
RBXASSERT(sinkDistance >= -1e-5);
} else {
// De-generate case : No water found at the top and bottom
// Just sample the 4 sides and give a rough estimate
float leftHasWater = worldPosUnderWater(center + Vector3(-radius, 0.0f, 0.0f)) ? 1.0f : 0.0f;
float rightHasWater = worldPosUnderWater(center + Vector3(radius, 0.0f, 0.0f)) ? 1.0f : 0.0f;
float frontHasWater = worldPosUnderWater(center + Vector3(0.0f, 0.0f, -radius)) ? 1.0f : 0.0f;
float backHasWater = worldPosUnderWater(center + Vector3(0.0f, 0.0f, radius)) ? 1.0f : 0.0f;
floatDistance = leftHasWater + rightHasWater + frontHasWater + backHasWater;
sinkDistance = 4.0f - floatDistance;
}
}
connectors[0]->setWaterBand(floatDistance, sinkDistance);
}
// override to interpolate over volume accurately
// integration over water depth yields volume fraction as (2 + 3k - k^3)/4 where k is normalized water depth over [-1, 1]
void BuoyancyBallContact::updateSubmergeRatio()
{
RBXASSERT(connectors.size() == 1);
float floatDistance, sinkDistance;
connectors[0]->getWaterBand(floatDistance, sinkDistance);
if (floatDistance <= 0.0f)
connectors[0]->setSubMergeRatio(0.0f); // out of water
else if (sinkDistance <= 0.0f)
connectors[0]->setSubMergeRatio(1.0f); // fully submerged
else {
float normalizedDepth = floatDistance / ( floatDistance + sinkDistance );
connectors[0]->setSubMergeRatio( normalizedDepth * normalizedDepth * (3 - 2 * normalizedDepth) );
}
}
RBX::Vector3 BuoyancyBallContact::getWaterVelocity( int )
{
Vector3 center = floaterPrim->getPV().position.translation;
Vector3 velocity = cellVelocity( center );
if (velocity != Vector3::zero())
return velocity;
// Check center of 6 faces of the bounding box
Extents extents = floaterPrim->getExtentsLocal();
for (int faceId = NORM_X; faceId <= NORM_Z_NEG; ++faceId) {
velocity = cellVelocity( center + extents.faceCenter(static_cast<NormalId>(faceId)) );
if (velocity != Vector3::zero())
return velocity;
}
return velocity;
}
//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
void BuoyancyBoxContact::initializeCrossSections()
{
// Cross section and tangent surface area depend on the direction of relative water velocity:
// for full submergence, just transform relative velocity direction into box CFrame, and
// dot product it with the pre-computed axis aligned cross section vector
// for partial submergence, linear interpolation on the 3 surface areas (faces) would probably be more than enough
Vector3 boxSize = floaterPrim->getSize();
crossSectionSurfaceAreas.x = boxSize.y * boxSize.z;
crossSectionSurfaceAreas.y = boxSize.x * boxSize.z;
crossSectionSurfaceAreas.z = boxSize.x * boxSize.y;
tangentSurfaceAreas.x = 2.0f * ( crossSectionSurfaceAreas.y + crossSectionSurfaceAreas.z );
tangentSurfaceAreas.y = 2.0f * ( crossSectionSurfaceAreas.x + crossSectionSurfaceAreas.z );
tangentSurfaceAreas.z = 2.0f * ( crossSectionSurfaceAreas.x + crossSectionSurfaceAreas.y );
fullSurfaceArea = crossSectionSurfaceAreas.sum() * 2.0f;
}
Vector3 BuoyancyBoxContact::getCrossSections(int i, const Vector3& velocity)
{
const Extents extents = floaterPrim->getExtentsLocal();
Vector3int16 index = extents.getCornerIndex(i);
// Polarity: 0 - negative axis direction points outwards
// 1 - positive axis direction points outwards
// index.x : 0,0,0,0,1,1,1,1
// index.y : 0,0,1,1,0,0,1,1
// index.z : 0,1,0,1,0,1,0,1
Vector3 crossAreas[2];
crossAreas[0] = crossSectionSurfaceAreas / 4.0f;
crossAreas[1] = Vector3::zero();
Vector3int16 sign(velocity.x > 0, velocity.y > 0, velocity.z > 0);;
return Vector3( crossAreas[sign.x ^ index.x].x,
crossAreas[sign.y ^ index.y].y,
crossAreas[sign.z ^ index.z].z );
}
void BuoyancyBoxContact::getSurfaceAreaInDirection(const Vector3& relativeVelocityDir, float& crossArea, float& tangentArea)
{
Vector3 componentWeights = relativeVelocityDir * relativeVelocityDir;
RBXASSERT(Math::fuzzyEq(componentWeights.x + componentWeights.y + componentWeights.z, 1.0f, 1.0e-4f));
crossArea = crossSectionSurfaceAreas.dot(componentWeights);
tangentArea = tangentSurfaceAreas.dot(componentWeights);
}
void BuoyancyBoxContact::createConnectors()
{
// 8 corners
const Extents extents = floaterPrim->getExtentsLocal();
for (unsigned int i = 0; i < 8; ++i)
connectors.push_back(new BuoyancyConnector(getBody(0), getBody(1), extents.getCorner(i) / 2.0f));
}
void BuoyancyBoxContact::updateWaterBand()
{
// A box is divided into 8 voxels, one for each connector.
// Each voxel computes its own submersion ratio.
RBXASSERT(connectors.size() == 8);
for (unsigned int i = 0; i < connectors.size(); ++i) {
float floatDistance, sinkDistnace;
Extents primExtents = floaterPrim->getExtentsLocal();
Extents voxelExtents = Extents::vv(Vector3::zero(), primExtents.getCorner(i));
Extents voxelWorldExtents = voxelExtents.toWorldSpace(floaterPrim->getCoordinateFrame());
computeExtentsWaterBand(voxelWorldExtents, floatDistance, sinkDistnace);
connectors[i]->setWaterBand(floatDistance, sinkDistnace);
}
}
BuoyancyBoxContact::BuoyancyBoxContact( Primitive* p0, Primitive* p1 ) : BuoyancyContact(p0, p1)
{
crossSectionSurfaceAreas = tangentSurfaceAreas = Vector3::zero();
}
///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
void BuoyancyCylinderContact::initializeCrossSections()
{
BuoyancyBoxContact::initializeCrossSections();
// same as box, except caps are circles instead of rectangles
// Note: This is a good approximation which doesn't use sqrt, but to be technically accurate, we would need our
// getSurfaceAreaInDirection to return
// surfaceAreas[0]*direction.x + surfaceAreas[1]*(sqrt(direction.y^2 + direction.z^2))
// instead of the current effective return of
// surfaceAreas[0]*direction.x + surfaceAreas[1]*(direction.y + direction.z)
float cylinderRadius = floaterPrim->getSize().y / 2.0f;
crossSectionSurfaceAreas.x = Math::pif() * cylinderRadius * cylinderRadius;
}
// High-fidelity Cylinder Interpolator:
// Blend (based on how "vertical" cylinder is) between the linear BuoyancyBoxContact::updateSubmergeRatio()
// and a spherical interpolator for cylinder in horizontal position as the following:
// v = 1/2 + (arcsin(x) + x*sqrt(1 - x^2))/pi, where v is the fraction of the volume and x is the fractional
// height of cylinder under water over the interval [-1, 1].
void RBX::BuoyancyCylinderContact::updateSubmergeRatio()
{
// Just do a linear interpolation for vertical case
BuoyancyBoxContact::updateSubmergeRatio();
RBXASSERT(connectors.size() == 8);
// we average the 4 ratios, and then map them to [-1, 1] from [0, 1]
float startPointRatio = (connectors[0]->getSubMergeRatio() + connectors[1]->getSubMergeRatio() +
connectors[2]->getSubMergeRatio() + connectors[3]->getSubMergeRatio()) * 0.5f - 1.0f;
float endPointRatio = (connectors[4]->getSubMergeRatio() + connectors[5]->getSubMergeRatio() +
connectors[6]->getSubMergeRatio() + connectors[7]->getSubMergeRatio()) * 0.5f - 1.0f;
// the length of the cylinder runs along its local x-axis
static const Vector3 upVector(0.0f, 1.0f, 0.0f);
float verticality = fabs((floaterPrim->getCoordinateFrame().rightVector().unit()).dot(upVector));
// we calculate the horizontal cylinder ratios here
static const float pi_inverse = 1.0f / 3.1415926535f;
startPointRatio = 0.5f + ( asinf(startPointRatio) + startPointRatio * sqrt(1.0f - startPointRatio*startPointRatio) ) * pi_inverse;
endPointRatio = 0.5f + ( asinf(endPointRatio) + endPointRatio * sqrt(1.0f - endPointRatio*endPointRatio) ) * pi_inverse;
// we use verticality to weight the average between the original submergeRatio and the horizontal-cylinder ratios
for (int i = 0; i < 4; i++)
connectors[i]->setSubMergeRatio( verticality * (connectors[i]->getSubMergeRatio()) + (1.0f - verticality) * (startPointRatio) );
for (int i = 4; i < 8; i++)
connectors[i]->setSubMergeRatio( verticality * (connectors[i]->getSubMergeRatio()) + (1.0f - verticality) * (endPointRatio) );
}
///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
void BuoyancyWedgeContact::initializeCrossSections()
{
Vector3 boxSize = floaterPrim->getSize();
crossSectionSurfaceAreas.x = boxSize.y * boxSize.z / 2.0f;
crossSectionSurfaceAreas.y = boxSize.x * boxSize.z;
crossSectionSurfaceAreas.z = boxSize.x * boxSize.y;
float slopingSurfaceArea = boxSize.x * sqrtf(boxSize.y*boxSize.y + boxSize.z*boxSize.z);
tangentSurfaceAreas.x = crossSectionSurfaceAreas.y + crossSectionSurfaceAreas.z + slopingSurfaceArea;
tangentSurfaceAreas.y = 2.0f * ( crossSectionSurfaceAreas.x + crossSectionSurfaceAreas.z );
tangentSurfaceAreas.z = 2.0f * ( crossSectionSurfaceAreas.x + crossSectionSurfaceAreas.y );
fullSurfaceArea = 2.0f * crossSectionSurfaceAreas.x + tangentSurfaceAreas.x;
}
// apply weights to voxels for approximation of wedge shape volumes
void BuoyancyWedgeContact::updateSubmergeRatio()
{
BuoyancyBoxContact::updateSubmergeRatio();
// GetMass() still returns a mass as if it were a box, so voxel weights must sum to number of voxels (8 here)
static const float WedgeVoxelWeightArray[8] = {1.0f, 2.0f, 0.0f, 1.0f, 1.0f, 2.0f, 0.0f, 1.0f};
RBXASSERT(connectors.size() == 8);
for (unsigned int i = 0; i < connectors.size(); ++i)
connectors[i]->setSubMergeRatio( WedgeVoxelWeightArray[i] * connectors[i]->getSubMergeRatio() );
}
///////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
void BuoyancyCornerWedgeContact::initializeCrossSections()
{
Vector3 boxSize = floaterPrim->getSize();
crossSectionSurfaceAreas.x = boxSize.y * boxSize.z / 2.0f;
crossSectionSurfaceAreas.y = boxSize.x * boxSize.z;
crossSectionSurfaceAreas.z = boxSize.x * boxSize.y / 2.0f;
tangentSurfaceAreas.x = 2.0f * ( crossSectionSurfaceAreas.y + crossSectionSurfaceAreas.z );
tangentSurfaceAreas.y = 2.0f * ( crossSectionSurfaceAreas.x + crossSectionSurfaceAreas.z );
tangentSurfaceAreas.z = 2.0f * ( crossSectionSurfaceAreas.x + crossSectionSurfaceAreas.y );
float slopingSurfaceArea1 = boxSize.x * sqrtf(boxSize.y*boxSize.y + boxSize.z*boxSize.z);
float slopingSurfaceArea2 = boxSize.z * sqrtf(boxSize.y*boxSize.y + boxSize.x*boxSize.x);
fullSurfaceArea = crossSectionSurfaceAreas.sum() + slopingSurfaceArea1 + slopingSurfaceArea2;
}
void BuoyancyCornerWedgeContact::updateSubmergeRatio()
{
BuoyancyBoxContact::updateSubmergeRatio();
static const float CornerWedgeVoxelWeightArray[8] = {1.5f, 1.0f, 0.0f, 0.0f, 3.0f, 1.5f, 1.0f, 0.0f};
RBXASSERT(connectors.size() == 8);
for (unsigned int i = 0; i < connectors.size(); ++i)
connectors[i]->setSubMergeRatio( CornerWedgeVoxelWeightArray[i] * connectors[i]->getSubMergeRatio() );
}
}