123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619 |
- //
- //
- // The graphics engine GXScene https://github.com/glscene
- //
- //
- unit GXS.Behaviours;
- (* Standard TgxBehaviour subclasses *)
- interface
- {$I Stage.Defines.inc}
- uses
- System.Classes,
- System.SysUtils,
- GXS.XCollection,
- Stage.VectorTypes,
- GXS.Scene,
- Stage.VectorGeometry,
- GXS.BaseClasses,
- GXS.Coordinates;
- type
- (* Holds parameters for TgxScene basic damping model.
- Damping is modeled 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. *)
- TgxDamping = class(TgxUpdateAbleObject)
- 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; quadractic]" string
- function AsString(const damping: TgxDamping): 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 :). *)
- TgxBInertia = class(TgxBehaviour)
- private
- FMass: single;
- FTranslationSpeed: TgxCoordinates;
- FTurnSpeed, FRollSpeed, FPitchSpeed: single;
- FTranslationDamping, FRotationDamping: TgxDamping;
- FDampingEnabled: boolean;
- protected
- procedure SetTranslationSpeed(const val: TgxCoordinates);
- procedure SetTranslationDamping(const val: TgxDamping);
- procedure SetRotationDamping(const val: TgxDamping);
- 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: TgxProgressTimes); override;
- // Adds time-proportionned acceleration to the speed.
- procedure ApplyTranslationAcceleration(const deltaTime: double;
- const accel: TVector4f);
- // Applies a timed force to the inertia. If Mass is null, nothing is done.
- procedure ApplyForce(const deltaTime: double; const force: TVector4f);
- (* 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: TVector4f; restitution: single);
- published
- property Mass: single read FMass write FMass;
- property TranslationSpeed: TgxCoordinates
- 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: TgxDamping 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: TgxDamping read FRotationDamping write SetRotationDamping;
- end;
- // Applies a constant acceleration to a TgxBInertia.
- TgxBAcceleration = class(TgxBehaviour)
- private
- FAcceleration: TgxCoordinates;
- protected
- procedure SetAcceleration(const val: TgxCoordinates);
- 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: TgxProgressTimes); override;
- published
- property Acceleration: TgxCoordinates read FAcceleration write FAcceleration;
- end;
- (* Returns or creates the TgxBInertia within the given behaviours.
- This helper function is convenient way to access a TgxBInertia. *)
- function GetInertia(const AGLXceneObject: TgxBaseSceneObject): TgxBInertia;
- function GetOrCreateInertia(behaviours: TgxBehaviours): TgxBInertia; overload;
- function GetOrCreateInertia(obj: TgxBaseSceneObject): TgxBInertia; overload;
- (* Returns or creates the TgxBAcceleration within the given behaviours.
- This helper function is convenient way to access a TgxBAcceleration. *)
- function GetOrCreateAcceleration(behaviours: TgxBehaviours): TgxBAcceleration;
- overload;
- function GetOrCreateAcceleration(obj: TgxBaseSceneObject): TgxBAcceleration; overload;
- // ------------------------------------------------------------------
- implementation
- // ------------------------------------------------------------------
- function GetInertia(const AGLXceneObject: TgxBaseSceneObject): TgxBInertia;
- var
- i: integer;
- begin
- i := AGLXceneObject.behaviours.IndexOfClass(TgxBInertia);
- if i >= 0 then
- Result := TgxBInertia(AGLXceneObject.behaviours[i])
- else
- Result := nil;
- end;
- function GetOrCreateInertia(behaviours: TgxBehaviours): TgxBInertia;
- var
- i: integer;
- begin
- i := behaviours.IndexOfClass(TgxBInertia);
- if i >= 0 then
- Result := TgxBInertia(behaviours[i])
- else
- Result := TgxBInertia.Create(behaviours);
- end;
- function GetOrCreateInertia(obj: TgxBaseSceneObject): TgxBInertia;
- begin
- Result := GetOrCreateInertia(obj.Behaviours);
- end;
- function GetOrCreateAcceleration(behaviours: TgxBehaviours): TgxBAcceleration;
- var
- i: integer;
- begin
- i := behaviours.IndexOfClass(TgxBAcceleration);
- if i >= 0 then
- Result := TgxBAcceleration(behaviours[i])
- else
- Result := TgxBAcceleration.Create(behaviours);
- end;
- function GetOrCreateAcceleration(obj: TgxBaseSceneObject): TgxBAcceleration;
- begin
- Result := GetOrCreateAcceleration(obj.Behaviours);
- end;
- // ------------------
- // ------------------ TgxDamping ------------------
- // ------------------
- constructor TgxDamping.Create(aOwner: TPersistent);
- begin
- inherited Create(AOwner);
- end;
- destructor TgxDamping.Destroy;
- begin
- inherited Destroy;
- end;
- procedure TgxDamping.Assign(Source: TPersistent);
- begin
- if Source is TgxDamping then
- begin
- FConstant := TgxDamping(Source).Constant;
- FLinear := TgxDamping(Source).Linear;
- FQuadratic := TgxDamping(Source).Quadratic;
- end
- else
- inherited Assign(Source);
- end;
- procedure TgxDamping.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 TgxDamping.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 TgxDamping.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 TgxDamping.AsString(const damping: TgxDamping): string;
- begin
- Result := Format('[%f; %f; %f]', [Constant, Linear, Quadratic]);
- end;
- procedure TgxDamping.SetDamping(const constant: single = 0;
- const linear: single = 0; const quadratic: single = 0);
- begin
- FConstant := constant;
- FLinear := linear;
- FQuadratic := quadratic;
- end;
- // ------------------
- // ------------------ TgxBInertia ------------------
- // ------------------
- constructor TgxBInertia.Create(aOwner: TXCollection);
- begin
- inherited Create(aOwner);
- FTranslationSpeed := TgxCoordinates.CreateInitialized(Self, NullHmgVector, csVector);
- FMass := 1;
- FDampingEnabled := True;
- FTranslationDamping := TgxDamping.Create(Self);
- FRotationDamping := TgxDamping.Create(Self);
- end;
- destructor TgxBInertia.Destroy;
- begin
- FRotationDamping.Free;
- FTranslationDamping.Free;
- FTranslationSpeed.Free;
- inherited Destroy;
- end;
- procedure TgxBInertia.Assign(Source: TPersistent);
- begin
- if Source.ClassType = Self.ClassType then
- begin
- FMass := TgxBInertia(Source).Mass;
- FTranslationSpeed.Assign(TgxBInertia(Source).FTranslationSpeed);
- FTurnSpeed := TgxBInertia(Source).TurnSpeed;
- FRollSpeed := TgxBInertia(Source).RollSpeed;
- FPitchSpeed := TgxBInertia(Source).PitchSpeed;
- FDampingEnabled := TgxBInertia(Source).DampingEnabled;
- FTranslationDamping.Assign(TgxBInertia(Source).TranslationDamping);
- FRotationDamping.Assign(TgxBInertia(Source).RotationDamping);
- end;
- inherited Assign(Source);
- end;
- procedure TgxBInertia.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 TgxBInertia.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 TgxBInertia.SetTranslationSpeed(const val: TgxCoordinates);
- begin
- FTranslationSpeed.Assign(val);
- end;
- procedure TgxBInertia.SetTranslationDamping(const val: TgxDamping);
- begin
- FTranslationDamping.Assign(val);
- end;
- procedure TgxBInertia.SetRotationDamping(const val: TgxDamping);
- begin
- FRotationDamping.Assign(val);
- end;
- class function TgxBInertia.FriendlyName: string;
- begin
- Result := 'Simple Inertia';
- end;
- class function TgxBInertia.FriendlyDescription: string;
- begin
- Result := 'A simple translation and rotation inertia';
- end;
- class function TgxBInertia.UniqueItem: boolean;
- begin
- Result := True;
- end;
- procedure TgxBInertia.DoProgress(const progressTime: TgxProgressTimes);
- var
- trnVector: TVector4f;
- 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 TgxBInertia.ApplyTranslationAcceleration(const deltaTime: double;
- const accel: TVector4f);
- begin
- FTranslationSpeed.AsVector := VectorCombine(FTranslationSpeed.AsVector,
- accel, 1, deltaTime);
- end;
- procedure TgxBInertia.ApplyForce(const deltaTime: double; const force: TVector4f);
- begin
- if Mass <> 0 then
- FTranslationSpeed.AsVector :=
- VectorCombine(FTranslationSpeed.AsVector, force, 1, deltaTime / Mass);
- end;
- procedure TgxBInertia.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 TgxBInertia.MirrorTranslation;
- begin
- FTranslationSpeed.Invert;
- end;
- procedure TgxBInertia.SurfaceBounce(const surfaceNormal: TVector4f; 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;
- // ------------------
- // ------------------ TgxBAcceleration ------------------
- // ------------------
- constructor TgxBAcceleration.Create(aOwner: TXCollection);
- begin
- inherited;
- if aOwner <> nil then
- if not (csReading in TComponent(aOwner.Owner).ComponentState) then
- GetOrCreateInertia(TgxBehaviours(aOwner));
- FAcceleration := TgxCoordinates.CreateInitialized(Self, NullHmgVector, csVector);
- end;
- destructor TgxBAcceleration.Destroy;
- begin
- inherited;
- FAcceleration.Free;
- end;
- procedure TgxBAcceleration.Assign(Source: TPersistent);
- begin
- if Source.ClassType = Self.ClassType then
- begin
- FAcceleration.Assign(TgxBAcceleration(Source).FAcceleration);
- end;
- inherited Assign(Source);
- end;
- procedure TgxBAcceleration.WriteToFiler(writer: TWriter);
- begin
- inherited;
- with writer do
- begin
- WriteInteger(0); // Archive Version 0
- FAcceleration.WriteToFiler(writer);
- end;
- end;
- procedure TgxBAcceleration.ReadFromFiler(reader: TReader);
- begin
- inherited;
- with reader do
- begin
- ReadInteger; // ignore archiveVersion
- FAcceleration.ReadFromFiler(reader);
- end;
- end;
- procedure TgxBAcceleration.SetAcceleration(const val: TgxCoordinates);
- begin
- FAcceleration.Assign(val);
- end;
- class function TgxBAcceleration.FriendlyName: string;
- begin
- Result := 'Simple Acceleration';
- end;
- class function TgxBAcceleration.FriendlyDescription: string;
- begin
- Result := 'A simple and constant acceleration';
- end;
- class function TgxBAcceleration.UniqueItem: boolean;
- begin
- Result := False;
- end;
- procedure TgxBAcceleration.DoProgress(const progressTime: TgxProgressTimes);
- var
- i: integer;
- Inertia: TgxBInertia;
- begin
- i := Owner.IndexOfClass(TgxBInertia);
- if i >= 0 then
- begin
- Inertia := TgxBInertia(Owner[i]);
- Inertia.ApplyTranslationAcceleration(progressTime.deltaTime,
- FAcceleration.DirectVector);
- end
- else
- begin
- TgxBInertia.Create(Owner);
- //on next progress event this exception won't be raised, because TgxBInertia will be created again
- raise Exception.Create(ClassName + ' requires ' + TgxBInertia.ClassName +
- '! (' + TgxBInertia.ClassName + ' was added to the Behaviours again)');
- end;
- end;
- // ------------------------------------------------------------------
- initialization
- // ------------------------------------------------------------------
- RegisterXCollectionItemClass(TgxBInertia);
- RegisterXCollectionItemClass(TgxBAcceleration);
- finalization
- UnregisterXCollectionItemClass(TgxBInertia);
- UnregisterXCollectionItemClass(TgxBAcceleration);
- end.
|