123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615 |
- //
- // The graphics engine GLScene
- //
- unit GLS.Behaviours;
- (* Standard TGLBehaviour subclasses *)
- interface
- {$I Stage.Defines.inc}
- uses
- System.Classes,
- System.SysUtils,
- Stage.VectorTypes,
- GLS.Scene,
- Stage.VectorGeometry,
- GLS.XCollection,
- GLS.BaseClasses,
- GLS.Coordinates;
- type
- (*
- Holds parameters for a basic damping model by calculating a force
- from the speed, this force can then be transformed to an acceleration
- is you know the object's mass.
- Formulas:
- damping = constant + linear * Speed + quadratic * Speed^2
- accel = damping / Mass
- That's just basic physics. A note on the components:
- constant: use it for solid friction (will stop abruptly an object after decreasing its speed.
- linear: linear friction damping.
- quadratic: expresses viscosity
- *)
- TGLDamping = class(TGLUpdateAbleObject)
- private
- FConstant: single;
- FLinear: single;
- FQuadratic: single;
- public
- constructor Create(aOwner: TPersistent); override;
- destructor Destroy; override;
- procedure WriteToFiler(writer: TWriter);
- procedure ReadFromFiler(reader: TReader);
- procedure Assign(Source: TPersistent); override;
- (*Calculates attenuated speed over deltaTime.
- integration step is 0.01 sec, and the following formula is applied
- at each step: constant+linear*speed+quadratic*speed^2 *)
- function Calculate(speed, deltaTime: Double): Double;
- // Returns a "[constant; linear; quadratic]" String
- function AsString(const damping: TGLDamping): String;
- // Sets all damping parameters in a single call.
- procedure SetDamping(const constant: Single = 0;
- const linear: Single = 0;
- const quadratic: Single = 0);
- published
- property Constant: Single read FConstant write FConstant;
- property Linear: Single read FLinear write FLinear;
- property Quadratic: Single read FQuadratic write FQuadratic;
- end;
- (* Simple translation and rotation Inertia behaviour.
- Stores translation and rotation speeds, to which you can apply
- accelerations.
- Note that the rotation model is not physical, so feel free to contribute
- a "realworld" inertia class with realistic, axis-free, rotation inertia
- if this approximation does not suits your needs :). *)
- TGLBInertia = class(TGLBehaviour)
- private
- FMass: single;
- FTranslationSpeed: TGLCoordinates;
- FTurnSpeed, FRollSpeed, FPitchSpeed: single;
- FTranslationDamping, FRotationDamping: TGLDamping;
- FDampingEnabled: boolean;
- protected
- procedure SetTranslationSpeed(const val: TGLCoordinates);
- procedure SetTranslationDamping(const val: TGLDamping);
- procedure SetRotationDamping(const val: TGLDamping);
- procedure WriteToFiler(writer: TWriter); override;
- procedure ReadFromFiler(reader: TReader); override;
- public
- constructor Create(aOwner: TXCollection); override;
- destructor Destroy; override;
- procedure Assign(Source: TPersistent); override;
- class function FriendlyName: string; override;
- class function FriendlyDescription: string; override;
- class function UniqueItem: boolean; override;
- procedure DoProgress(const progressTime: TGLProgressTimes); override;
- // Adds time-proportionned acceleration to the speed.
- procedure ApplyTranslationAcceleration(const deltaTime: double;
- const accel: TGLVector);
- // Applies a timed force to the inertia. If Mass is null, nothing is done.
- procedure ApplyForce(const deltaTime: Double; const Force: TGLVector);
- (*Applies a timed torque to the inertia (yuck!).
- This gets a "yuck!" because it is as false as the rest of the rotation model. *)
- procedure ApplyTorque(const deltaTime: double;
- const turnTorque, rollTorque, pitchTorque: single);
- // Inverts the translation vector.
- procedure MirrorTranslation;
- (* Bounce speed as if hitting a surface.
- restitution is the coefficient of restituted energy (1=no energy loss, 0=no bounce).
- The normal is NOT assumed to be normalized. *)
- procedure SurfaceBounce(const surfaceNormal: TGLVector; restitution: single);
- published
- property Mass: single read FMass write FMass;
- property TranslationSpeed: TGLCoordinates read FTranslationSpeed write SetTranslationSpeed;
- property TurnSpeed: single read FTurnSpeed write FTurnSpeed;
- property RollSpeed: single read FRollSpeed write FRollSpeed;
- property PitchSpeed: single read FPitchSpeed write FPitchSpeed;
- // Enable/Disable damping (damping has a high cpu-cycle cost). Damping is enabled by default.
- property DampingEnabled: boolean read FDampingEnabled write FDampingEnabled;
- (* Damping applied to translation speed.
- Note that it is not "exactly" applied, ie. if damping would stop
- your object after 0.5 time unit, and your progression steps are
- of 1 time unit, there will be an integration error of 0.5 time unit. *)
- property TranslationDamping: TGLDamping read FTranslationDamping write SetTranslationDamping;
- (* Damping applied to rotation speed (yuck!).
- Well, this one is not "exact", like TranslationDamping, and neither
- it is "physical" since I'm reusing the mass and... and... well don't
- show this to your science teacher 8).
- Anyway that's easier to use than the realworld formulas, calculated
- faster, and properly used can give a good illusion of reality. *)
- property RotationDamping: TGLDamping read FRotationDamping write SetRotationDamping;
- end;
- // Applies a constant acceleration to a TGLBInertia.
- TGLBAcceleration = class(TGLBehaviour)
- private
- FAcceleration: TGLCoordinates;
- protected
- procedure SetAcceleration(const val: TGLCoordinates);
- procedure WriteToFiler(writer: TWriter); override;
- procedure ReadFromFiler(reader: TReader); override;
- public
- constructor Create(aOwner: TXCollection); override;
- destructor Destroy; override;
- procedure Assign(Source: TPersistent); override;
- class function FriendlyName: string; override;
- class function FriendlyDescription: string; override;
- class function UniqueItem: boolean; override;
- procedure DoProgress(const progressTime: TGLProgressTimes); override;
- published
- property Acceleration: TGLCoordinates read FAcceleration write FAcceleration;
- end;
- (* Returns or creates the TGLBInertia within the given behaviours.
- This helper function is convenient way to access a TGLBInertia. *)
- function GetInertia(const AGLSceneObject: TGLBaseSceneObject): TGLBInertia;
- function GetOrCreateInertia(behaviours: TGLBehaviours): TGLBInertia; overload;
- function GetOrCreateInertia(obj: TGLBaseSceneObject): TGLBInertia; overload;
- (* Returns or creates the TGLBAcceleration within the given behaviours.
- This helper function is convenient way to access a TGLBAcceleration. *)
- function GetOrCreateAcceleration(behaviours: TGLBehaviours): TGLBAcceleration; overload;
- function GetOrCreateAcceleration(obj: TGLBaseSceneObject): TGLBAcceleration; overload;
- implementation // -----------------------------------------------------------
- function GetInertia(const AGLSceneObject: TGLBaseSceneObject): TGLBInertia;
- var
- i: integer;
- begin
- i := AGLSceneObject.behaviours.IndexOfClass(TGLBInertia);
- if i >= 0 then
- Result := TGLBInertia(AGLSceneObject.behaviours[i])
- else
- Result := nil;
- end;
- function GetOrCreateInertia(behaviours: TGLBehaviours): TGLBInertia;
- var
- i: integer;
- begin
- i := behaviours.IndexOfClass(TGLBInertia);
- if i >= 0 then
- Result := TGLBInertia(behaviours[i])
- else
- Result := TGLBInertia.Create(behaviours);
- end;
- function GetOrCreateInertia(obj: TGLBaseSceneObject): TGLBInertia;
- begin
- Result := GetOrCreateInertia(obj.Behaviours);
- end;
- function GetOrCreateAcceleration(behaviours: TGLBehaviours): TGLBAcceleration;
- var
- i: integer;
- begin
- i := behaviours.IndexOfClass(TGLBAcceleration);
- if i >= 0 then
- Result := TGLBAcceleration(behaviours[i])
- else
- Result := TGLBAcceleration.Create(behaviours);
- end;
- function GetOrCreateAcceleration(obj: TGLBaseSceneObject): TGLBAcceleration;
- begin
- Result := GetOrCreateAcceleration(obj.Behaviours);
- end;
- // ------------------
- // ------------------ TGLDamping ------------------
- // ------------------
- constructor TGLDamping.Create(aOwner: TPersistent);
- begin
- inherited Create(AOwner);
- end;
- destructor TGLDamping.Destroy;
- begin
- inherited Destroy;
- end;
- procedure TGLDamping.Assign(Source: TPersistent);
- begin
- if Source is TGLDamping then
- begin
- FConstant := TGLDamping(Source).Constant;
- FLinear := TGLDamping(Source).Linear;
- FQuadratic := TGLDamping(Source).Quadratic;
- end
- else
- inherited Assign(Source);
- end;
- procedure TGLDamping.WriteToFiler(writer: TWriter);
- var
- writeStuff: boolean;
- begin
- with writer do
- begin
- WriteInteger(0); // Archive Version 0
- writeStuff := (FConstant <> 0) or (FLinear <> 0) or (FQuadratic <> 0);
- WriteBoolean(writeStuff);
- if writeStuff then
- begin
- WriteFloat(FConstant);
- WriteFloat(FLinear);
- WriteFloat(FQuadratic);
- end;
- end;
- end;
- procedure TGLDamping.ReadFromFiler(reader: TReader);
- begin
- with reader do
- begin
- ReadInteger; // ignore Archive Version
- if ReadBoolean then
- begin
- FConstant := ReadFloat;
- FLinear := ReadFloat;
- FQuadratic := ReadFloat;
- end
- else
- begin
- FConstant := 0;
- FLinear := 0;
- FQuadratic := 0;
- end;
- end;
- end;
- function TGLDamping.Calculate(speed, deltaTime: double): double;
- var
- dt: double;
- begin
- while deltaTime > 0 do
- begin
- if deltaTime > 0.01 then
- begin
- dt := 0.01;
- deltaTime := deltaTime - 0.01;
- end
- else
- begin
- dt := deltaTime;
- deltaTime := 0;
- end;
- speed := speed - dt * ((FQuadratic * speed + FLinear) * speed + FConstant);
- end;
- Result := speed;
- end;
- function TGLDamping.AsString(const damping: TGLDamping): string;
- begin
- Result := Format('[%f; %f; %f]', [Constant, Linear, Quadratic]);
- end;
- procedure TGLDamping.SetDamping(const constant: single = 0;
- const linear: single = 0; const quadratic: single = 0);
- begin
- FConstant := constant;
- FLinear := linear;
- FQuadratic := quadratic;
- end;
- // ------------------
- // ------------------ TGLBInertia ------------------
- // ------------------
- constructor TGLBInertia.Create(aOwner: TXCollection);
- begin
- inherited Create(aOwner);
- FTranslationSpeed := TGLCoordinates.CreateInitialized(Self, NullHmgVector, csVector);
- FMass := 1;
- FDampingEnabled := True;
- FTranslationDamping := TGLDamping.Create(Self);
- FRotationDamping := TGLDamping.Create(Self);
- end;
- destructor TGLBInertia.Destroy;
- begin
- FRotationDamping.Free;
- FTranslationDamping.Free;
- FTranslationSpeed.Free;
- inherited Destroy;
- end;
- procedure TGLBInertia.Assign(Source: TPersistent);
- begin
- if Source.ClassType = Self.ClassType then
- begin
- FMass := TGLBInertia(Source).Mass;
- FTranslationSpeed.Assign(TGLBInertia(Source).FTranslationSpeed);
- FTurnSpeed := TGLBInertia(Source).TurnSpeed;
- FRollSpeed := TGLBInertia(Source).RollSpeed;
- FPitchSpeed := TGLBInertia(Source).PitchSpeed;
- FDampingEnabled := TGLBInertia(Source).DampingEnabled;
- FTranslationDamping.Assign(TGLBInertia(Source).TranslationDamping);
- FRotationDamping.Assign(TGLBInertia(Source).RotationDamping);
- end;
- inherited Assign(Source);
- end;
- procedure TGLBInertia.WriteToFiler(writer: TWriter);
- begin
- inherited;
- with writer do
- begin
- WriteInteger(0); // Archive Version 0
- WriteFloat(FMass);
- FTranslationSpeed.WriteToFiler(writer);
- WriteFloat(FTurnSpeed);
- WriteFloat(FRollSpeed);
- WriteFloat(FPitchSpeed);
- WriteBoolean(FDampingEnabled);
- FTranslationDamping.WriteToFiler(writer);
- FRotationDamping.WriteToFiler(writer);
- end;
- end;
- procedure TGLBInertia.ReadFromFiler(reader: TReader);
- begin
- inherited;
- with reader do
- begin
- ReadInteger; // ignore archiveVersion
- FMass := ReadFloat;
- FTranslationSpeed.ReadFromFiler(reader);
- FTurnSpeed := ReadFloat;
- FRollSpeed := ReadFloat;
- FPitchSpeed := ReadFloat;
- FDampingEnabled := ReadBoolean;
- FTranslationDamping.ReadFromFiler(reader);
- FRotationDamping.ReadFromFiler(reader);
- end;
- end;
- procedure TGLBInertia.SetTranslationSpeed(const val: TGLCoordinates);
- begin
- FTranslationSpeed.Assign(val);
- end;
- procedure TGLBInertia.SetTranslationDamping(const val: TGLDamping);
- begin
- FTranslationDamping.Assign(val);
- end;
- procedure TGLBInertia.SetRotationDamping(const val: TGLDamping);
- begin
- FRotationDamping.Assign(val);
- end;
- class function TGLBInertia.FriendlyName: string;
- begin
- Result := 'Simple Inertia';
- end;
- class function TGLBInertia.FriendlyDescription: string;
- begin
- Result := 'A simple translation and rotation inertia';
- end;
- class function TGLBInertia.UniqueItem: boolean;
- begin
- Result := True;
- end;
- procedure TGLBInertia.DoProgress(const progressTime: TGLProgressTimes);
- var
- trnVector: TGLVector;
- speed, newSpeed: double;
- procedure ApplyRotationDamping(var rotationSpeed: single);
- begin
- if rotationSpeed > 0 then
- begin
- rotationSpeed := RotationDamping.Calculate(rotationSpeed, progressTime.deltaTime);
- if rotationSpeed <= 0 then
- rotationSpeed := 0;
- end
- else
- begin
- rotationSpeed := -RotationDamping.Calculate(-rotationSpeed, progressTime.deltaTime);
- if rotationSpeed >= 0 then
- rotationSpeed := 0;
- end;
- end;
- begin
- // Apply damping to speed
- if DampingEnabled then
- begin
- // Translation damping
- speed := TranslationSpeed.VectorLength;
- if speed > 0 then
- begin
- newSpeed := TranslationDamping.Calculate(speed, progressTime.deltaTime);
- if newSpeed <= 0 then
- begin
- trnVector := NullHmgVector;
- TranslationSpeed.AsVector := trnVector;
- end
- else
- begin
- TranslationSpeed.Scale(newSpeed / Speed);
- SetVector(trnVector, TranslationSpeed.AsVector);
- end;
- end
- else
- SetVector(trnVector, NullHmgVector);
- // Rotation damping (yuck!)
- ApplyRotationDamping(FTurnSpeed);
- ApplyRotationDamping(FRollSpeed);
- ApplyRotationDamping(FPitchSpeed);
- end
- else
- SetVector(trnVector, TranslationSpeed.AsVector);
- // Apply speed to object
- with OwnerBaseSceneObject do
- with progressTime do
- begin
- Position.AddScaledVector(deltaTime, trnVector);
- TurnAngle := TurnAngle + TurnSpeed * deltaTime;
- RollAngle := RollAngle + RollSpeed * deltaTime;
- PitchAngle := PitchAngle + PitchSpeed * deltaTime;
- end;
- end;
- procedure TGLBInertia.ApplyTranslationAcceleration(const deltaTime: double;
- const accel: TGLVector);
- begin
- FTranslationSpeed.AsVector := VectorCombine(FTranslationSpeed.AsVector,
- accel, 1, deltaTime);
- end;
- procedure TGLBInertia.ApplyForce(const deltaTime: double; const force: TGLVector);
- begin
- if Mass <> 0 then
- FTranslationSpeed.AsVector :=
- VectorCombine(FTranslationSpeed.AsVector, force, 1, deltaTime / Mass);
- end;
- procedure TGLBInertia.ApplyTorque(const deltaTime: double;
- const turnTorque, rollTorque, pitchTorque: single);
- var
- factor: double;
- begin
- if Mass <> 0 then
- begin
- factor := deltaTime / Mass;
- FTurnSpeed := FTurnSpeed + turnTorque * factor;
- FRollSpeed := FRollSpeed + rollTorque * factor;
- FPitchSpeed := FPitchSpeed + pitchTorque * factor;
- end;
- end;
- procedure TGLBInertia.MirrorTranslation;
- begin
- FTranslationSpeed.Invert;
- end;
- procedure TGLBInertia.SurfaceBounce(const surfaceNormal: TGLVector; restitution: single);
- var
- f: single;
- begin
- // does the current speed vector comply?
- f := VectorDotProduct(FTranslationSpeed.AsVector, surfaceNormal);
- if f < 0 then
- begin
- // remove the non-complying part of the speed vector
- FTranslationSpeed.AddScaledVector(-f / VectorNorm(surfaceNormal) *
- (1 + restitution), surfaceNormal);
- end;
- end;
- // ------------------
- // ------------------ TGLBAcceleration ------------------
- // ------------------
- constructor TGLBAcceleration.Create(aOwner: TXCollection);
- begin
- inherited;
- if aOwner <> nil then
- if not (csReading in TComponent(aOwner.Owner).ComponentState) then
- GetOrCreateInertia(TGLBehaviours(aOwner));
- FAcceleration := TGLCoordinates.CreateInitialized(Self, NullHmgVector, csVector);
- end;
- destructor TGLBAcceleration.Destroy;
- begin
- inherited;
- FAcceleration.Free;
- end;
- procedure TGLBAcceleration.Assign(Source: TPersistent);
- begin
- if Source.ClassType = Self.ClassType then
- begin
- FAcceleration.Assign(TGLBAcceleration(Source).FAcceleration);
- end;
- inherited Assign(Source);
- end;
- procedure TGLBAcceleration.WriteToFiler(writer: TWriter);
- begin
- inherited;
- with writer do
- begin
- WriteInteger(0); // Archive Version 0
- FAcceleration.WriteToFiler(writer);
- end;
- end;
- procedure TGLBAcceleration.ReadFromFiler(reader: TReader);
- begin
- inherited;
- with reader do
- begin
- ReadInteger; // ignore archiveVersion
- FAcceleration.ReadFromFiler(reader);
- end;
- end;
- procedure TGLBAcceleration.SetAcceleration(const val: TGLCoordinates);
- begin
- FAcceleration.Assign(val);
- end;
-
- class function TGLBAcceleration.FriendlyName: string;
- begin
- Result := 'Simple Acceleration';
- end;
- class function TGLBAcceleration.FriendlyDescription: string;
- begin
- Result := 'A simple and constant acceleration';
- end;
- class function TGLBAcceleration.UniqueItem: boolean;
- begin
- Result := False;
- end;
- procedure TGLBAcceleration.DoProgress(const progressTime: TGLProgressTimes);
- var
- i: integer;
- Inertia: TGLBInertia;
- begin
- i := Owner.IndexOfClass(TGLBInertia);
- if i >= 0 then
- begin
- Inertia := TGLBInertia(Owner[i]);
- Inertia.ApplyTranslationAcceleration(progressTime.deltaTime,
- FAcceleration.DirectVector);
- end
- else
- begin
- TGLBInertia.Create(Owner);
- //on next progress event this exception won't be raised, because TGLBInertia will be created again
- raise Exception.Create(ClassName + ' requires ' + TGLBInertia.ClassName +
- '! (' + TGLBInertia.ClassName + ' was added to the Behaviours again)');
- end;
- end;
- initialization // ------------------------------------------------------------
- // class registrations
- RegisterXCollectionItemClass(TGLBInertia);
- RegisterXCollectionItemClass(TGLBAcceleration);
- finalization // --------------------------------------------------------------
- UnregisterXCollectionItemClass(TGLBInertia);
- UnregisterXCollectionItemClass(TGLBAcceleration);
- end.
|