Game Development Community

Physics Simulation / Buoyancy

by Erik Wassermann · in Torque 3D Professional · 01/24/2013 (1:12 pm) · 17 replies

Hi Everyone,

as mentioned in my description I am a simulation engineer in the German Navy. I am evaluating Torque3D 2.0 as a base fo a multipurpose navy simualtion. In this Thread I want to decribe my experience with the physic simualtion implemented in Torque3D 2.0 with special interest in buoyancy.
I will switch the in game physics completly to SI units and adapt some computations to make them more reasonable. The main aim is to change the game physics to produce reasonable output which can be compared to real world values.

In my first post i will describe the generell buoyancy computation.

Yes You guessed right- Navy means floating things in water...
and here we are at the core of my current problems:
BUOYANCY

As some other have already experienced the buoyancy computation in Torque3D is ... lets say there������´s much popential for improvement.
Instead of toying around with datablocks to get it working I directly digged into the source code to investigate whats so utterly wrong with the physics simulation. I used the Full template and experimented with the parameters available with a waterplane. As a test object i made a copy of the RigidShape BouncinBoulder. In the future I will create a nes Ship or FloatingVehicle class and it will inherit from RigidShape. But for now lets take a look at the rigidShape.

In rigidShape.updateforces is the basic computation fpr buoyancy force (l 1159-1162)
// Container buoyancy & drag
   force  += Point3F(0, 0,-mBuoyancy * sRigidShapeGravity * mRigid.mass * mGravityMod);
   force  -= mRigid.linVelocity * mDrag;
   torque -= mRigid.angMomentum * mDrag;
with shapeBase::UpdateContainer(l 1592-1604)
// This value might be useful as a datablock value,
   // This is what allows the player to stand in shallow water (below this coverage)
   // without jiggling from buoyancy
   if (mWaterCoverage >= 0.25f) 
   {      
      // water viscosity is used as drag for in water.
      // ShapeBaseData drag is used for drag outside of water.
      // Combine these two components to calculate this ShapeBase object's 
      // current drag.
      mDrag = ( info.waterCoverage * info.waterViscosity ) + 
              ( 1.0f - info.waterCoverage ) * mDataBlock->drag;
      mBuoyancy = (info.waterDensity / mDataBlock->density) * info.waterCoverage;
   }
This basic computation is in fact according to real world physics and should cause no problems. You should just eliminate the factor in the if-clause. For a flaoting vessel it makes no sense to compute the buoyancy force first when at least a quarter of the object is immersed.

But You should take a very close look at the computation of the drag force and the included factor mDrag.
In general the drag force has to be proportional with the squared velocity not linear as implemented above. You have to add mRigid.linVelocity.len() as additional factor:
force -= mRigid.linVelocity.len() * mRigid.linVelocity * mDrag;
Do not simply square the vector You will end up with a scalar but we want to stick to a vector.

The missing relation between drag and squared velocity is one main reason for irrational behaviour of floating vehicles. A lot of people experienced that their boat is starting to oszillata at the water surface and jumps out of the water. This is a clear evidence that the resistance forces (drag and friction) are way too tiny to damp fast moving objects.

Another weak point is the factor mDrag. If your object is not in the water it represent the datablock value drag. If it is in the water this factor is replaced with waterViscosity. This is highly confusing because this factor has nothing to do with real world water viscosity.
You have to carefully adjust drag and viscosity to getyour boat to float. In real world physics the drag depends on the drag coefficient cw, the front facing area of your object and the density of the fluid your object is moving trough(air or water) F(drag) = cw*A*rho/2 * V^2
Regarding thie formula the factor drag represents cw*A*rho/2
For example a car can have a cw value between 0.3 -0.8(sleek car-truck), assuming a front facing area of about 3m^2 and an air-density of about 1.184 you get a value for drag raging from 0.5 to 1.4.
To get reasonable value for our drag force You have to detemine the right values for mdataBlock.drag.

The same thing has to be done foe waterViscosity, because it is used as a subsitute for drag when the object is under water. Taking into account that water has a density around 1000 up to 1030 (depends on saltinity) the values for waterViscosity has to be 900 times greater than the value for drag.

It is very inconvenient taht the density of the fluid is included in mdataBlock.drag. It would be much better to separate it and leave drag as a purely object-related factor.

I will describe that in the next post and will show some other grave shortcomings of the game physics.

About the author

Simulation Engineer in the German Navy Working on a navy simualtion based on Torque3D -let them float-

Recent Threads


#1
01/24/2013 (2:00 pm)
This is an area that the Steering Committee is hoping to see addressed in the future. We are definitely interested in your observations or any improvements that can be offered.
#2
01/24/2013 (4:08 pm)
I second that! This is some really good info. Please let us know what more you find out.

Ron
#3
01/24/2013 (11:04 pm)
In this post I will take anotherlook at mDrag. As mentioned in the first post it is inconvenient to have the density of the fluid the object is moving in included within that factor.

Lets change the computation to alter this in shapeBase::UpdateContainer(l 1601-1604):
mDrag =  info.waterCoverage * info.waterDensity * mdataBlock->drag  +
          ( 1.0f - info.waterCoverage ) * mDataBlock->drag * mAirDensity;
Included is a new variable called mAirDensity:
F32 mAirDensity //default air density for physics
I defined it in shapebase.h(inserted in l 906)
initialize in shapeBase.cpp (inserted in l 869):
mAirDensity( 1.184f),[/code]

Now mDrag = mdataBlock.drag * density(fluid) (or combined out of two densities if object is not fully submersed)
That gives us the opportunity to define mdataBlock.drag as a purely object related factor: mdataBlock.drag = cw*A/2. It is only depending on dragcoefficient (cw) and the half of the front facing area (A/2).

For example a ordinary car:
cw=0.3 A=2m^2 gives a drag value of 0.3
another example: human(standing)
cw=0.78 A=1m^2 gives a drag value of 0.39 (yep right, even worse than your car!)
my test RigidShape(remember a ball)
cw=0.1 A=3.14 (assuming r=1m) drag=0.157[b]

[b]IMPORTANT:
the above metioned computation for mDrag includes the density of air and water (real world values!) thus the density of the waterplane or waterblock has to be set to at least 1000 (or 1030 for salt water(currently a maximum of 1000 is allowed but You can change that to 1050)). The density of your object has to be set accordingly (my test RigidShape.density=500);

Now the drag force will be computed in a physical reasonable way and our rigidShape object will float at the water surface. The value for waterViscosity is for now without any meaning but we will come back to it when we are dealing with friction (I am planing to use friction to slow down the rotation of an object).

In the next post i will discuss how we use the computed forces to achieve an acceleration and with the acceleration we will compute the movement our object.
#4
01/25/2013 (1:09 am)
This is absolutely great stuff. I'm looking forward to the rest of this.
#5
01/28/2013 (8:10 am)
Now lets take a look at the integration (for people who are not familiar with physics- this is the process of computing velocity and position from a given acceleration using incredible small time steps)
For now I will not calculate any torques and their resulting rotational accelerations. The implementation in Torque3D is a nightmare but changing that involves some serious thinking about inertia coefficients and tensors, quite heavy stuff...
I will come back to that when its time to implement a moment which will point the top of our object out of the water instead of tumbling around without knowing where is UP.

The base for all integration methods is the acceleration:
a = F / m.

To compute the movement of an object following steps have to be done:
1. sum up all forces acting on our object: F= F1 + F2 +...
2. sum up all torques acting on our object: M= M1 + M2 +... (I skip that for now..)
3. compute the acceleration: a = F / m
4. compute the rotational acceleration (also postponed,)
5. compute the velocity with integral timestep dt: V= V + a * dt
6. compute the position with integral timestep dt: Pos= Pos + V * dt
7. compute rotation with integral timestep dt (postponed)

1. sum up the forces:
Sum(F) = gravity + buoyancy + drag + additional forces (collision, propulsion etc)


Gravity: (rigidShape.cpp l 1142)
Point3F gravForce(0, 0, sRigidShapeGravity * mRigid.mass * mGravityMod)

Really simple just gravity constant multiplied with object mass. I changed sRigidShapeGravity to -9.8 the default was -20. I also changed all other gravity values to -9.8 (all vehicles, player and some other). I really do not know why the default is -20?

Buoyancy:
Is already mentioned above (1.post).

Drag:
Discussed in the 2.post. In addition there is an additional vertFactor for drag. I have no clue for what it is needed and thus commented it out (rigidShape.cpp l 1156-1160)
Point3F vDrag = mRigid.linVelocity;
vdrag.convolve(point3F(1, 1, mDataBlock->vertFactor));
force -= vDrag * mDataBlock->dragForce;

Additional forces:
Collisions will lead to forces (spring forces against the collision normal). I will leave the collision implementation as it is for now.
The RigidShape has no propulsion etc thus no mor forces have to be computed. This will be used in the vehicle classes.

2. postponed

3 /5 /6. Acceleration/Velocity/Position :

Here is the original implementation:
// Update linear position, momentum
	   linPosition = linPosition + linVelocity * delta;
	   linMomentum = linMomentum + force * delta;
	   linVelocity = linMomentum * oneOverMass;

The base for the above integration is not the acceleration but the linear momentum [bold](linMomentum)[/bold]. Momentum is defined as p = m*V thus the acceleration is implicit included by the velocity. In fact it is a very efficient way to compute, because some multiplications are saved. Therefore I will leave it that way.
But we have to change the order. Now the linPosition is computed at first, but with the outdated velocity(linVelocity gets updated after the linMomentum).
The correct order have to be:
// Update linear position, momentum
   /28.01.13 by erik; linMomentum has to be computed at first!!
	   linMomentum = linMomentum + force * delta;/
	   linVelocity = linMomentum * oneOverMass;
	   linPosition = linPosition + linVelocity * delta;

With that corrected the integration works just fine. My TestRigidObject is floating beautifully at the water surface like it never did something else...

For now the RigidShape objects are floating in a physical correct way. The next step is the implementation of a righting moment. This will steady the top of our boat/ ship at the water surface and prevents it from tumbling around.
#6
01/28/2013 (8:26 am)
@Erik:
Thank-you for going through T3D's physics simulation. Very few have gone through that code, and it hasn't been reviewed and updated for some time.

This is rather timely as the Steering Committee were considering updating the physics libraries with T3D release 4.0, and this would also be a great time to update Torque 3D's home grown physics. I'm now tracking this thread and look forward to your future updates, as well as testing out your changes in the engine.

Thanks!

- Dave
#7
04/22/2013 (5:45 am)
really interesting read... right now i've got the water viscosity set at 444 (for a submarine game)...

i know this has got to be the most totally wrong way to do this :)

Erik... Wilhelmshaven, eh... a submarine game in the works ? :)

--Mike
#8
04/22/2013 (7:48 am)
I was always more concerned with the collision code. My problem is that it all looks right but sometimes it just fails....

Anyway, great to see someone really digging through this stuff. Thanks!
#9
04/22/2013 (12:49 pm)
This is interesting. What I have right now for swimming is very amateur compared to what I see here. I will have to take some time to read this later. Did you have any issue where your rise force was too great where you started to bound upon the water?
#10
08/06/2013 (9:39 am)
I started working on some code that inherits from PhysicsShape for redoing some buoyancy code. Instead of calculating off a single point, I'm generating a grid from the bounding box of the object that ray casts upwards to get points on the collision shape. This is then compared with the height of the water at a point (using Triton Ocean SDK). The volume displaced can be retrieved and an impulse can be applied at each point.

I haven't finished everything, I started re-working it last night from some old code, so not all the calculations are accurate.

I tested with an object that had a spherical collision shape and one that was shaped like a pyramid. The sphere tumbled with the waves as expected, and the pyramid remained upright.

I'm using a 3rd party library for the water, instead of just giving me the water height at a point, it also gives me the normal, I suppose WaterObject could be modified to support both, so a PhysicsShape with more realistic interaction with the water could be added to the codebase.
#11
08/08/2013 (12:06 pm)
Here's a quick video of what I have so far. Right now its calculating the object density by the datablock mass and the volume that is calculated. I'm not sure if this is the best way to do it, since really heavy things might not float properly, instead they'll sink. Also, the volume calculation accuracy depends on how large of a grid you specify.


There's still issues with the damping/drag as well, I haven't added in the air density stuff for the non-submerged portion of the object yet.
#12
08/08/2013 (1:51 pm)
That's pretty cool, Scott. And certainly realistic enough to make someone seasick! ;)
#13
08/08/2013 (2:59 pm)
That's very cool! The shape does look a little too jolly on those little waves, but I'm sure that's all down to datablock settings.
#14
08/09/2013 (2:05 am)
Hi Everyone!

After a long brake for other urgent projects I just restarted my work on Torque3D.

After updating to 3.0 I´m currently experimenting with the bullet physics included in version 3.0.
The stock template shows exactly the same behaviour as 2.0 with torque physics.

I´m trying to get buoyancy working with as little code changes as possible while just tweaking the datablock setting. As experienced in the last months I just don´t have enough time to fix the whole physic calculation.

@Scot:
A partition sheme or grid is the right way to go, but You can achieve the same by just using the metacentric height of the floating object. The metacentric height is the result of the underwater shape, the density etc and is the virtual lever for the buoyancy force. Thus it is giving You the chance to implement the righting moment in a very easy way bypassing the gridding or partitioning of your object.

Back in february I did some first test with such an implementation. I will dig out the code and port it to version 3.0 and have a closer look.

#15
08/09/2013 (2:21 pm)
@Erik, I'd be interested in seeing that code, I've never heard of metacentric height. Sounds like it would be a lot less CPU intensive.
#16
08/14/2013 (2:59 am)
The metacentric Height is a term from shipbuilding and is a benchmark for the stability of a vessel. The higher it is the faster the vessel will stabilize itself and the harder it will topple.

Besides the whole theory You can use the metacentric height as a virtual lever for your buoyancy force, thus giving You the chance to implement the righting moment of Your object in a easy way.

I did some tests with a hardcoded metacentric height for rigidshape wich worked really good.
in rigid.h and rigid.cpp I added the vector for the metacentric height:
Point3f metacentricHeight; //met height in object coordinates
Point3f worldmetacentricHeight; //met height in world space

Additionally You need a function which rotates the metacentric Height (remember just a vector) according to the orientation of Your rigidshape-object:
void updateMetacentricHeight()
 {
  angPosition.mulP(metacentricHeight, &worldmetacentricHeight)
 }

with that You can compute the resulting torque in rigidshape.cpp::updateForces(F32)
Point3f buoyforce (0,0,-mBuoyancy * sRigidshapeGravity * mRigid.mass * mGravityMod)
torque += mCross(mRigid.worldmetacentricHeight, buoyforce)

With that You get a torque that will upright Your object when floating.

ToDo:
-make the metacentricHeight relative to center of mass (at the moment it only works correctly for com (0,0,0))
-apply additional factor to differentiate between length and width of the object, at the moment the same torque will be applied no matter in wich direction the object is tilting
#17
08/24/2013 (1:05 am)
OK, I got a quick test working with the PhysicsShape.

Point3F buoyancyForce = buoyancy * -mWorld->getGravity() * TickSec * getDataBlock()->mass;
Point3F metacentricHeight(0,0,1);
MatrixF mTrans;
mPhysicsRep->getTransform(&mTrans);
mTrans.mulP(metacentricHeight);
mPhysicsRep->applyImpulse( metacentricHeight, buoyancyForce );

This method doesn't give me the rocking with the waves in the water. Though the water library I'm using does provide a normal vector when querying the height, so I could probably multiply that with the metacentric height, so the waves affect it.

I might use this method for buoys and other more simple objects, but use my other method with the grid for the user controlled hydroplanes.