www.pudn.com > racecar.zip > uBobbyClasses.pas


unit uBobbyClasses; 
 
interface 
 
uses 
  Classes, SysUtils, ODEImport, math; 
 
const 
  cLIQUID_DENSITY = 3; 
  cGRAVITY = -9.91; 
 
type 
  TBobby = class; 
  TBobbyHandler = class; 
 
  // TBobby is a particle that handles buoyancy. It bobs in the water, hence 
  // the name 
  TOnGetDepthAndNormal = procedure (Bobby: TBobby) of object; 
 
  TBobby = class 
    // The position of the Bobby, relative to the body. This will be modified 
    // by the bobbys location and rotation 
    Position : TdVector3; 
 
    // TotalVolume is stored to speed up calculations. It's updated whenever 
    // radius is altered 
    TotalVolume : single; 
 
    // Keeps track of how much of the bobbys volume is in submerged 
    SubmergedVolume : single; 
 
    // A Bobby must be connected to a body that it can act upon 
    Body : PdxBody; 
 
    // The current depth of the bobby 
    CenterDepth : single; 
 
    // WaterNormal is a vector that describes the normal of the water at the 
    // location of the bobby 
    WaterNormal : TdVector3; 
 
    // This is the force as it has been calculated by the bobby, depending on 
    // the displacement 
    BuoyancyForce : TdVector3; 
 
    // This is the position that the force acts upon 
    BuoyancyForceCenter : TdVector3; 
 
    // BobbyHandler is responsible for handling this bobby 
    BobbyHandler : TBobbyHandler; 
 
    // WorldPosition holds the world position of the bobby 
    WorldPosition : TdVector3; 
 
    // OldWorldPosition is used to calculate where the Bobby was before, for 
    // calculating drag 
    OldWorldPosition : TdVector3; 
 
    // DragForce is the drag that the bobby feels due to it's speed in the 
    // liquid. If the bobby isn't submerged, the drag is zero 
    DragForce : TdVector3; 
    BobbySpeed : TdVector3; 
 
    // DragCoefficient is used to calculate drag forces 
    DragCoefficient : single; 
 
    // SurfaceArea is the area of the surface 
    SurfaceArea : single; 
 
    // SubmergedSurfaceArea is the area that's submerged 
    SubmergedSurfaceArea : single; 
 
    // Update 
    procedure Update(DeltaTime : single); 
 
    // UpdateWorldPosition updates the world position 
    procedure UpdateWorldPosition; 
 
    procedure UpdateSpeed(DeltaTime : single); 
 
    // Calculate the drag force 
    procedure CalcDragForce; 
 
    // Calculate the magnitude and direction of the buoyancy force 
    procedure CalcBuoyancyForce; 
 
    // Apply the buoyancy force to the body 
    procedure ApplyForces; 
 
    constructor Create(BobbyHandler : TBobbyHandler); 
    destructor Destroy; override; 
  private 
    FRadius: single; 
    procedure SetRadius(const Value: single); 
 
  public 
    // The Radius of the bobby determines it's buoyancy. The amount of liquid 
    // it displaces is the same as the volume 
    property Radius : single read FRadius write SetRadius; 
  end; 
 
  TBobbyList = class(TList) 
  private 
    function GetItems(i: integer): TBobby; 
    procedure SetItems(i: integer; const Value: TBobby); 
  public 
    property Items[i : integer] : TBobby read GetItems write SetItems; default; 
  end; 
 
  TBobbyHandler = class 
  private 
    FOnGetDepthAndNormal: TOnGetDepthAndNormal; 
    procedure SetOnGetDepthAndNormal(const Value: TOnGetDepthAndNormal); 
  public 
    // Bobbies is a list of all handled bobbies (duh!) 
    Bobbies : TBobbyList; 
 
    // LiquidDensity determines the density of the liquid that the bobby is 
    // suspended in 
    LiquidDensity : single; 
 
    // Gravity determines how much the mass of the water weights. This should 
    // be the same gravity as ODE uses. 
    Gravity : TdVector3; 
 
    // AddBobby adds a new bobby to the bobbies list 
    procedure AddBobby(Bobby : TBobby); 
 
    // AddBobby removes a bobby to the bobbies list 
    procedure RemoveBobby(Bobby : TBobby); 
 
    // Free all bobbies and clear the list 
    procedure ClearBobbies; 
 
    // UpdateBobbies will 
    // * Update each bobby world position 
    // * Call GetDepthAndNormal with each bobby 
    // * Calculate the buoyancy forces 
    // * Calculate the drag forces 
    // * Add the forces to the bodies 
    procedure UpdateBobbies(DeltaTime : single); 
 
    property OnGetDepthAndNormal : TOnGetDepthAndNormal read FOnGetDepthAndNormal write SetOnGetDepthAndNormal; 
 
    function GetSubmergedAmount : single; 
 
    constructor Create; 
    destructor Destroy; override; 
  end; 
 
  function SphereCapVolume(const SphereRadius, CapHeight : single) : single; 
  function SphereCapCentroidHeight(SphereRadius, CapHeight : single) : single; 
 
  function SubmergedSphereCapVolume(const SphereRadius, CenterDepth : single) : single; 
  function SubmergedSphereCapCentroidHeight(SphereRadius, CenterDepth : single) : single; 
 
implementation 
 
const 
  c4PiDiv3 = 4 * pi / 3; 
  cPiDiv3 = pi / 3; 
 
function SphereCapVolume(const SphereRadius, CapHeight : single) : single; 
begin 
  // Calculates the volume of a sphere cap, as clipped by a plane 
  // SphereRadius is the radius of the sphere 
  // CapHeight is the height from the _TOP_ of the sphere to the clipping plane 
 
{ 
See http://mathworld.wolfram.com/SphericalCap.html and 
    http://mathforum.org/dr.math/faq/formulas/faq.sphere.html and 
    http://mathforum.org/library/drmath/view/55253.html 
 
     V_cap = 2/3 pi r^2 h - 1/3 pi (2rh - h^2)(r - h) 
           = 2/3 pi r^2 h - 1/3 pi (2r^2h - 3rh^2 +h^3) 
           = 1/3 pi h [2r^2 - 2r^2 + 3rh - h^2] 
           = 1/3 pi h (3rh - h^2) 
*** 
 
  Both sources seem to agree ;) 
} 
 
  Assert(Abs(CapHeight)<=2 * SphereRadius, 
    Format('Cap must be smaller than sphere diameter, Abs(%f) > 2 * %f!',[CapHeight, SphereRadius]));//} 
 
  // Calculate the volume 
  result := cPiDiv3 * CapHeight * (3 * SphereRadius * CapHeight - CapHeight * CapHeight) 
end; 
 
function SphereCapCentroidHeight(SphereRadius, CapHeight : single) : single; 
begin 
  // This function from http://mathworld.wolfram.com/SphericalCap.html, 
  //  (Harris and Stocker 1998, p. 107). 
 
  Assert(Abs(CapHeight)<=2 * SphereRadius, 
    Format('Cap must be smaller than sphere diameter, Abs(%f) > 2 * %f!',[CapHeight, SphereRadius]));//} 
 
  result := 3*sqr(2 * SphereRadius - CapHeight)/(4 * (3 * SphereRadius - CapHeight)); 
end; 
 
function SubmergedSphereCapVolume(const SphereRadius, CenterDepth : single) : single; 
begin 
  // Is it not submerged at all? 
  if CenterDepth >= SphereRadius then 
    result := 0 
  // Is it fully submerged? 
  else if CenterDepth <= -SphereRadius then 
    result := c4PiDiv3 * SphereRadius * SphereRadius * SphereRadius 
  else 
    // Partially submerged, the amount submerged is calculated by the cap volume 
    result := SphereCapVolume(SphereRadius, SphereRadius - CenterDepth ); 
end; 
 
function SubmergedSphereCapCentroidHeight(SphereRadius, CenterDepth : single) : single; 
begin 
  // Is it not submerged at all? If it's not submerged, it has no centroid! 
  if CenterDepth >= SphereRadius then 
    result := 0 
  // Is it fully submerged? 
  else if CenterDepth <= -SphereRadius then 
    result := 0 
  else 
    // Partially submerged, the amount submerged is calculated by the cap volume 
    result := -SphereCapCentroidHeight(SphereRadius, (SphereRadius - CenterDepth)); 
end; 
 
{ TBobby } 
 
procedure TBobby.ApplyForces; 
begin 
  Assert(Assigned(Body),'Bobby has no body!'); 
 
  dBodyAddForceAtPos(Body, 
    BuoyancyForce[0] + DragForce[0], BuoyancyForce[1] + DragForce[1], BuoyancyForce[2] + DragForce[2], 
    BuoyancyForceCenter[0], BuoyancyForceCenter[1], BuoyancyForceCenter[2]); 
end; 
 
procedure TBobby.CalcBuoyancyForce; 
var 
  DisplacementMass : single; 
  Depth : single; 
  g : single; 
begin 
  // PLEASE NOTE: This function currently only handles gravity that lies along 
  // z! 
   
  // Make sure there's a body to apply the force to 
  Assert(Assigned(Body),'Bobby has no body!'); 
 
  // The force center will go through the center of the Bobby, but it will be 
  // moved ackording to the normal of the water and the depth of the water 
  // around the bobby 
  Depth := SubmergedSphereCapCentroidHeight(Radius, CenterDepth); 
 
  BuoyancyForceCenter[0] := WorldPosition[0] + Depth * WaterNormal[0]; 
  BuoyancyForceCenter[1] := WorldPosition[1] + Depth * WaterNormal[1]; 
  BuoyancyForceCenter[2] := WorldPosition[2] + Depth * WaterNormal[2];//} 
 
  // Calculate displaced volume 
  SubmergedVolume := SubmergedSphereCapVolume(Radius, CenterDepth); 
 
  if SubmergedVolume = 0 then 
    SubmergedSurfaceArea := 0 
  else if CenterDepth<-Radius then 
    SubmergedSurfaceArea := SurfaceArea 
  else 
    SubmergedSurfaceArea := SurfaceArea * (Radius * 2 - CenterDepth); 
 
  // Calculate displacement mass 
  DisplacementMass := SubmergedVolume * BobbyHandler.LiquidDensity; 
 
  // The lifting force is always opposing gravity 
  BuoyancyForce[0] := - BobbyHandler.Gravity[0] * DisplacementMass; 
  BuoyancyForce[1] := - BobbyHandler.Gravity[1] * DisplacementMass; 
  BuoyancyForce[2] := - BobbyHandler.Gravity[2] * DisplacementMass;//} 
 
  g := BobbyHandler.Gravity[0]; 
 
  if abs(BobbyHandler.Gravity[1]) > abs(g) then 
    g := BobbyHandler.Gravity[1]; 
 
  if abs(BobbyHandler.Gravity[2]) > abs(g) then 
    g := BobbyHandler.Gravity[2]; 
 
  // The sideways moving force is proportional to the slope of the water normal 
  if (BobbyHandler.Gravity[0]=0) then 
    BuoyancyForce[0] := BuoyancyForce[0] - WaterNormal[0] * DisplacementMass * g; 
 
  if (BobbyHandler.Gravity[1]=0) then 
    BuoyancyForce[1] := BuoyancyForce[1] - WaterNormal[1] * DisplacementMass * g; 
 
  if (BobbyHandler.Gravity[2]=0) then 
    BuoyancyForce[2] := BuoyancyForce[2] - WaterNormal[2] * DisplacementMass * g;//} 
end; 
 
procedure TBobby.CalcDragForce; 
var 
  ForceMagnitude : single; 
  Speed, Speed2 : single; 
begin 
  // If this is the first time, ignore drag! 
  if OldWorldPosition[0] = -10e5 then exit; 
 
  // If the bobby isn't submerged, the force is zero 
  if SubmergedVolume=0 then 
  begin 
    DragForce[0] := 0; 
    DragForce[1] := 0; 
    DragForce[2] := 0; 
    exit; 
  end; 
 
  Speed2 := (sqr(BobbySpeed[0]) + sqr(BobbySpeed[1]) + sqr(BobbySpeed[2])); 
  Speed := sqrt(Speed2); 
 
  // Fd = Cd / 2 * p * V^2 * A 
  // Cd = Drag coefficient 
  // p = density of medium 
  // v = flow speed 
  // A = cross sectional area 
  ForceMagnitude := 
    - DragCoefficient / 2 * BobbyHandler.LiquidDensity * Speed2 * SubmergedSurfaceArea; 
 
  // Preset drag force to a normalized version of bobby speed 
  DragForce[0] := BobbySpeed[0] / Speed * ForceMagnitude; 
  DragForce[1] := BobbySpeed[1] / Speed * ForceMagnitude; 
  DragForce[2] := BobbySpeed[2] / Speed * ForceMagnitude; 
end; 
 
constructor TBobby.Create(BobbyHandler: TBobbyHandler); 
begin 
  self.BobbyHandler := BobbyHandler; 
  BobbyHandler.AddBobby(self); 
 
  // Note that the bobby hasn't ever had a position before, so that the speed 
  // calculation doesn't come out bonkers 
  WorldPosition[0] := -10e5; 
  OldWorldPosition[0] := -10e5; 
 
  DragCoefficient := 1.5; 
end; 
 
destructor TBobby.Destroy; 
begin 
  BobbyHandler.RemoveBobby(self); 
 
  inherited; 
end; 
 
procedure TBobby.SetRadius(const Value: single); 
begin 
  FRadius := Value; 
  TotalVolume := 4 / 3 * pi * Value * Value * Value; 
  SurfaceArea := pi * sqr(Value); 
end; 
 
procedure TBobby.Update(DeltaTime : single); 
begin 
  // If the body that the bobby is connected to is disabled, then we can jump out 
  // here 
  if (dBodyIsEnabled(Body)=0) then exit; 
 
  // * Update bobby world position 
  UpdateWorldPosition; 
 
  // * Update bobby speed 
  UpdateSpeed(DeltaTime); 
 
  // * Call GetDepthAndNormal with each bobby 
  BobbyHandler.OnGetDepthAndNormal(self); 
 
  // * Calculate the buoyancy forces 
  CalcBuoyancyForce; 
 
  // * Calculate the drag forces 
  CalcDragForce; 
 
  // * Add the forces to the bodies 
  ApplyForces; 
end; 
 
procedure TBobby.UpdateSpeed(DeltaTime: single); 
begin 
  // Also update the speed of the bobby 
  BobbySpeed[0] := (WorldPosition[0] - OldWorldPosition[0]) / DeltaTime; 
  BobbySpeed[1] := (WorldPosition[1] - OldWorldPosition[1]) / DeltaTime; 
  BobbySpeed[2] := (WorldPosition[2] - OldWorldPosition[2]) / DeltaTime; 
end; 
 
procedure TBobby.UpdateWorldPosition; 
var 
  pos : PdVector3; 
begin 
  OldWorldPosition := WorldPosition; 
 
  // First, calculate the actual center of the bobby 
  dBodyVectorToWorld(Body, Position[0], Position[1], Position[2], WorldPosition); 
 
  pos := dBodyGetPosition(Body); 
 
 
  Assert(abs(pos[2])>-10e10 , Format('Bad body position : %f, %f, %f',[pos[0], pos[1], pos[2]])); 
 
  WorldPosition[0] := WorldPosition[0] + Pos[0]; 
  WorldPosition[1] := WorldPosition[1] + Pos[1]; 
  WorldPosition[2] := WorldPosition[2] + Pos[2]; 
end; 
 
{ TBobbyHandler } 
 
procedure TBobbyHandler.AddBobby(Bobby: TBobby); 
begin 
  Bobbies.Add(Bobby); 
end; 
 
procedure TBobbyHandler.ClearBobbies; 
begin 
  try 
    while Bobbies.Count>0 do 
      Bobbies[Bobbies.Count-1].Free; 
  finally 
    Bobbies.Clear; 
  end; 
end; 
 
constructor TBobbyHandler.Create; 
begin 
  Bobbies := TBobbyList.Create; 
  LiquidDensity := cLIQUID_DENSITY; 
  Gravity[0] := 0; 
  Gravity[1] := 0; 
  Gravity[2] := cGRAVITY; 
end; 
 
destructor TBobbyHandler.Destroy; 
begin 
  ClearBobbies; 
  FreeAndNil(Bobbies); 
 
  inherited; 
end; 
 
function TBobbyHandler.GetSubmergedAmount: single; 
var 
  i : integer; 
  TotVolume, TotSubmergedVolume : single; 
begin 
  TotVolume := 0; 
  TotSubmergedVolume := 0; 
  for i := 0 to Bobbies.Count-1 do 
    with Bobbies[i] do 
    begin 
      TotVolume := TotVolume + TotalVolume; 
      TotSubmergedVolume := TotSubmergedVolume + SubmergedVolume; 
    end; 
 
  if TotVolume = 0 then 
    result := 0 
  else 
    result := TotSubmergedVolume / TotVolume; 
end; 
 
procedure TBobbyHandler.RemoveBobby(Bobby: TBobby); 
begin 
  Bobbies.Remove(Bobby); 
end; 
 
procedure TBobbyHandler.SetOnGetDepthAndNormal( 
  const Value: TOnGetDepthAndNormal); 
begin 
  FOnGetDepthAndNormal := Value; 
end; 
 
procedure TBobbyHandler.UpdateBobbies(DeltaTime : single); 
var 
  i : integer; 
  Bobby : TBobby; 
begin 
  Assert(Assigned(OnGetDepthAndNormal),'OnGetDepthAndNormal must be assigned!'); 
  for i := 0 to Bobbies.Count-1 do 
  begin 
    // Retrieve the bobby for easy access 
    Bobby := Bobbies[i]; 
 
    // Update the bobby 
    Bobby.Update(DeltaTime); 
  end; 
end; 
 
{ TBobbyList } 
 
function TBobbyList.GetItems(i: integer): TBobby; 
begin 
  result := Get(i); 
end; 
 
procedure TBobbyList.SetItems(i: integer; const Value: TBobby); 
begin 
  Put(i, Value); 
end; 
end.