Buoyancy-in-parts for irregular moving water #183
-
Since earlier discussion on buoyancy, (Apply buoyancy impulse offset #76) I've been brainstorming how to handle fluids such as the following scenarios:
##Current brainstorm logic:
This allows choosing a subdivision level and allowing currents and irregular water shapes to apply forces based on a chosen resolution of voxel size, allowing 1x distinct plane/current per voxel. To some degree, any surface shape of the water, from curves to waves, can be broken down into planar cuts of the various tetrahedron. ##Simpler non-exact Workaround |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 7 replies
-
I think both methods could work. The first one would be quite CPU intensive (you'll get a lot of tetrahedrons). W.r.t. point 2: We could add next to 'fluid velocity' a 'fluid angular velocity' which you could calculate by sampling the fluid velocity at e.g. the edges of the bounding box and use w = Sum(r x v) / num samples to get the average angular velocity. As an option 3: You could probably add a 2nd plane to the buoyancy calculations and define the water volume as the intersection between the two half spaces of those 2 planes. You would use 1 plane as water surface, the 2nd one to limit the water horizontally. |
Beta Was this translation helpful? Give feedback.
-
Fixed Constraint does not seem to work for this. It kind of goes crazy trying to adjust the objects to the fixed point. A Distance Constraint of super low value appears to allow enough wiggle room to solve the constraint without the boat flying off into the sky with a helicopter of float spheres spinning around it. import initJolt from './js/jolt-physics.wasm-compat.js';
const count = 128;
const data = new Float32Array(count);
for (let i = 0; i < count; i++) {
data[i] = Math.sin(Math.PI * (2*i/count));
}
const sinoidalWave = new THREE.DataTexture(data, count, 1, THREE.RedFormat, THREE.FloatType);
sinoidalWave.wrapS = sinoidalWave.wrapT = THREE.RepeatWrapping;
sinoidalWave.offset.set(0, 0);
sinoidalWave.repeat.set(16, 1);
sinoidalWave.magFilter = THREE.NearestFilter;
sinoidalWave.needsUpdate = true;
const WAVE_HEIGHT = 0.25;
function getWaveElevation(x) {
const uv = x * sinoidalWave.repeat.x + sinoidalWave.offset.x;
return data[0|(count * (uv % 1.0))] * WAVE_HEIGHT;
}
function makeRiver() {
const planeMesh = new THREE.Mesh(
new THREE.PlaneGeometry(32, 16, 256, 1),
new THREE.MeshPhongMaterial({
displacementMap: sinoidalWave,
displacementScale: WAVE_HEIGHT,
flatShading: true,
color: 0x9999ff,
opacity: 0.5,
transparent: true
})
);
planeMesh.material.needsUpdate = true;
planeMesh.position.y += 1.5;
scene.add(planeMesh);
return planeMesh;
}
initJolt().then(function (Jolt) {
// Initialize this example
setupCollisionFiltering = function (settings) {
let objectFilter = new Jolt.ObjectLayerPairFilterTable(3);
objectFilter.EnableCollision(LAYER_NON_MOVING, LAYER_MOVING);
objectFilter.EnableCollision(LAYER_MOVING, LAYER_MOVING);
const BP_LAYER_NON_MOVING = new Jolt.BroadPhaseLayer(0);
const BP_LAYER_MOVING = new Jolt.BroadPhaseLayer(1);
const NUM_BROAD_PHASE_LAYERS = 2;
let bpInterface = new Jolt.BroadPhaseLayerInterfaceTable(3, NUM_BROAD_PHASE_LAYERS);
bpInterface.MapObjectToBroadPhaseLayer(LAYER_NON_MOVING, BP_LAYER_NON_MOVING);
bpInterface.MapObjectToBroadPhaseLayer(LAYER_MOVING, BP_LAYER_MOVING);
bpInterface.MapObjectToBroadPhaseLayer(2, BP_LAYER_MOVING);
settings.mObjectLayerPairFilter = objectFilter;
settings.mBroadPhaseLayerInterface = bpInterface;
settings.mObjectVsBroadPhaseLayerFilter = new Jolt.ObjectVsBroadPhaseLayerFilterTable(settings.mBroadPhaseLayerInterface, NUM_BROAD_PHASE_LAYERS, settings.mObjectLayerPairFilter, NUM_OBJECT_LAYERS);
};
initExample(Jolt, null);
const light = new THREE.AmbientLight( 0x404040 ); // soft white light
scene.add( light );
const river1 = makeRiver();
river1.rotation.x = -Math.PI / 2;
river1.position.z += 8;
const river2 = makeRiver();
river2.rotation.x = Math.PI / 2;
river2.rotation.y = Math.PI;
river2.position.z -= 8;
const position = new Jolt.RVec3();
position.Set(0.5,5,10);
const floatingBodies = [];
const ball = createSphere(position, 0.5, Jolt.EMotionType_Dynamic, LAYER_MOVING, 0x990000);
floatingBodies.push(ball);
createFloor();
const W = 3;
const H = 0.5;
const D = 1;
const extent = new Jolt.Vec3(W, H, D);
position.Set(2.5,4,8);
const constraintSettings = new Jolt.DistanceConstraintSettings();
constraintSettings.mMinDistance = 0;
constraintSettings.mMaxDistance = .025;
{
const shapeSettings = new Jolt.BoxShapeSettings(extent);
const shape = shapeSettings.Create().Get();
const bodySettings = new Jolt.BodyCreationSettings(shape, position,Jolt.Quat.prototype.sIdentity(), Jolt.EMotionType_Dynamic, LAYER_MOVING)
bodySettings.mOverrideMassProperties = Jolt.EOverrideMassProperties_CalculateInertia;
bodySettings.mMassPropertiesOverride.mMass = 2;
const log = bodyInterface.CreateBody(bodySettings);
addToScene(log, 0x003366);
Jolt.destroy(bodySettings);
Jolt.destroy(shapeSettings);
[[1,-1],[1,1],[-1,-1],[-1,1],[0,-1],[0,1]].forEach(([x,z]) => {
position.Set(x*W+2.5, 4-H, z*D+8);
const float = createSphere(position, 0.5, Jolt.EMotionType_Dynamic, 2, 0x660099);
constraintSettings.mPoint1 = constraintSettings.mPoint2 = position;
physicsSystem.AddConstraint(constraintSettings.Create(log, float));
floatingBodies.push(float);
})
}
extent.Set(D,H,W);
position.Set(0,4,0);
{
const shapeSettings = new Jolt.BoxShapeSettings(extent);
const shape = shapeSettings.Create().Get();
const bodySettings = new Jolt.BodyCreationSettings(shape, position,Jolt.Quat.prototype.sIdentity(), Jolt.EMotionType_Dynamic, LAYER_MOVING)
bodySettings.mOverrideMassProperties = Jolt.EOverrideMassProperties_CalculateInertia;
bodySettings.mMassPropertiesOverride.mMass = 2;
const log = bodyInterface.CreateBody(bodySettings);
addToScene(log, 0x003366);
Jolt.destroy(bodySettings);
Jolt.destroy(shapeSettings);
[[1,-1],[1,1],[-1,-1],[-1,1],[1,0],[-1,0]].forEach(([x,z]) => {
position.Set(x*D, 4-H, z*W);
const float = createSphere(position, 0.5, Jolt.EMotionType_Dynamic, 2, 0x660099);
constraintSettings.mPoint1 = constraintSettings.mPoint2 = position;
physicsSystem.AddConstraint(constraintSettings.Create(log, float));
floatingBodies.push(float);
})
}
const raycaster = new THREE.Raycaster();
const ray = raycaster.ray;
ray.direction.set(0, -1, 0);
const gravity = new Jolt.Vec3(0, -9.8, 0);
const buoyancy = 1.1;
const linearDrag = 0.3;
const angularDrag = 0.05;
const surfaceNormal = new Jolt.Vec3(0, 1, 0);
const fluidVelocity = new Jolt.Vec3(-0.5, 0, 0);
const waterSurfacePosition = new Jolt.RVec3(0, 2.5, 0);
const WATER_SPEED = 0.4;
function floatBody(body, deltaTime) {
const pos = body.GetPosition();
ray.origin.set(pos.GetX(), pos.GetY(), pos.GetZ());
ray.origin.y += 3;
const obj = raycaster.intersectObjects([river1, river2]);
obj.forEach(result => {
{
const { x, y, z} = result.point;
const height = getWaveElevation(result.uv.x);
waterSurfacePosition.Set(x, y+height, z);
}
if(result.object == river1) {
fluidVelocity.Set(-WATER_SPEED, 0, 0)
}
if(result.object == river2) {
fluidVelocity.Set(WATER_SPEED, 0, 0)
}
body.ApplyBuoyancyImpulse(waterSurfacePosition, surfaceNormal, buoyancy, linearDrag, angularDrag, fluidVelocity, gravity, deltaTime);
});
}
onExampleUpdate = function (time, deltaTime) {
floatingBodies.forEach(body => floatBody(body, deltaTime));
sinoidalWave.offset.x += deltaTime * 0.1;
}
}); |
Beta Was this translation helpful? Give feedback.
-
So there are a couple of problems here:
and removing:
(note that we shouldn't change this globally for all demos, but for this experiment it's fine) |
Beta Was this translation helpful? Give feedback.
So there are a couple of problems here:
and removing:
createSphere
function doesn't override mass, so that sphere is about 500 kg. Attaching that to a 2 kg main body gives a very unstable situation (equivalent to what happens in the samples app HeavyOnLightTest). I fixed this by adding this to thecreateSphere
function: