2
// This unit is part of the GLScene Engine https://github.com/glscene
5
Standard TGLBehaviour subclasses for GLScene
8
08/05/08 - DaStr - Added a global GetInertia() function
9
19/12/06 - DaStr - TGLBAcceleration.Create - creates Inertia right away,
10
thus displaying it in the XCollection Editor
11
TGLBAcceleration.DoProgress - raises an exception
12
when required Inertia component is deleted by user
13
24/09/02 - Egg - Support for negative rotation speeds (Marco Chong)
14
02/10/00 - Egg - Fixed TGLBInertia.DoProgress (DamplingEnabled bug)
15
09/10/00 - Egg - Fixed ApplyTranslationAcceleration & ApplyForce
16
11/08/00 - Egg - Fixed translation bug with root level objects & Inertia
17
10/04/00 - Egg - Improved persistence logic
18
06/04/00 - Egg - Added Damping stuff to inertia
19
05/04/00 - Egg - Creation
41
{ Holds parameters for TGLScene basic damping model.
42
Damping is modeled by calculating a force from the speed, this force
43
can then be transformed to an acceleration is you know the object's mass.
45
damping = constant + linear * Speed + quadratic * Speed^2
46
accel = damping / Mass
47
That's just basic physics :). A note on the components :
48
constant : use it for solid friction (will stop abruptly an object after
50
linear : linear friction damping.
51
quadratic : expresses viscosity.
53
TGLDamping = class(TGLUpdateAbleObject)
65
constructor Create(aOwner: TPersistent); override;
66
destructor Destroy; override;
68
procedure WriteToFiler(writer: TWriter);
69
procedure ReadFromFiler(reader: TReader);
71
procedure Assign(Source: TPersistent); override;
72
{ Calculates attenuated speed over deltaTime.
73
Integration step is 0.01 sec, and the following formula is applied
74
at each step: constant+linear*speed+quadratic*speed^2 }
75
function Calculate(speed, deltaTime: double): double;
76
// Returns a "[constant; linear; quadractic]" string
77
function AsString(const damping: TGLDamping): string;
78
{ Sets all damping parameters in a single call. }
79
procedure SetDamping(const constant: single = 0; const linear: single = 0;
80
const quadratic: single = 0);
84
property Constant: single read FConstant write FConstant;
85
property Linear: single read FLinear write FLinear;
86
property Quadratic: single read FQuadratic write FQuadratic;
91
{ Simple translation and rotation Inertia behaviour.
92
Stores translation and rotation speeds, to which you can apply
94
Note that the rotation model is not physical, so feel free to contribute
95
a "realworld" inertia class with realistic, axis-free, rotation inertia
96
if this approximation does not suits your needs :). }
97
TGLBInertia = class(TGLBehaviour)
101
FTranslationSpeed: TGLCoordinates;
102
FTurnSpeed, FRollSpeed, FPitchSpeed: single;
103
FTranslationDamping, FRotationDamping: TGLDamping;
104
FDampingEnabled: boolean;
108
procedure SetTranslationSpeed(const val: TGLCoordinates);
109
procedure SetTranslationDamping(const val: TGLDamping);
110
procedure SetRotationDamping(const val: TGLDamping);
112
procedure WriteToFiler(writer: TWriter); override;
113
procedure ReadFromFiler(reader: TReader); override;
117
constructor Create(aOwner: TGLXCollection); override;
118
destructor Destroy; override;
120
procedure Assign(Source: TPersistent); override;
122
class function FriendlyName: string; override;
123
class function FriendlyDescription: string; override;
124
class function UniqueItem: boolean; override;
126
procedure DoProgress(const progressTime: TProgressTimes); override;
128
{ Adds time-proportionned acceleration to the speed. }
129
procedure ApplyTranslationAcceleration(const deltaTime: double;
130
const accel: TVector);
131
{ Applies a timed force to the inertia.
132
If Mass is null, nothing is done. }
133
procedure ApplyForce(const deltaTime: double; const force: TVector);
134
{ Applies a timed torque to the inertia (yuck!).
135
This gets a "yuck!" because it is as false as the rest of the
137
procedure ApplyTorque(const deltaTime: double;
138
const turnTorque, rollTorque, pitchTorque: single);
139
{ Inverts the translation vector. }
140
procedure MirrorTranslation;
141
{ Bounce speed as if hitting a surface.
142
restitution is the coefficient of restituted energy (1=no energy loss,
143
0=no bounce). The normal is NOT assumed to be normalized. }
144
procedure SurfaceBounce(const surfaceNormal: TVector; restitution: single);
148
property Mass: single read FMass write FMass;
149
property TranslationSpeed: TGLCoordinates
150
read FTranslationSpeed write SetTranslationSpeed;
151
property TurnSpeed: single read FTurnSpeed write FTurnSpeed;
152
property RollSpeed: single read FRollSpeed write FRollSpeed;
153
property PitchSpeed: single read FPitchSpeed write FPitchSpeed;
155
{ Enable/Disable damping (damping has a high cpu-cycle cost).
156
Damping is enabled by default. }
157
property DampingEnabled: boolean read FDampingEnabled write FDampingEnabled;
158
{ Damping applied to translation speed.
159
Note that it is not "exactly" applied, ie. if damping would stop
160
your object after 0.5 time unit, and your progression steps are
161
of 1 time unit, there will be an integration error of 0.5 time unit. }
162
property TranslationDamping: TGLDamping read FTranslationDamping
163
write SetTranslationDamping;
164
{ Damping applied to rotation speed (yuck!).
165
Well, this one is not "exact", like TranslationDamping, and neither
166
it is "physical" since I'm reusing the mass and... and... well don't
167
show this to your science teacher 8).
168
Anyway that's easier to use than the realworld formulas, calculated
169
faster, and properly used can give a good illusion of reality. }
170
property RotationDamping: TGLDamping read FRotationDamping write SetRotationDamping;
175
{ Applies a constant acceleration to a TGLBInertia. }
176
TGLBAcceleration = class(TGLBehaviour)
179
FAcceleration: TGLCoordinates;
183
procedure SetAcceleration(const val: TGLCoordinates);
185
procedure WriteToFiler(writer: TWriter); override;
186
procedure ReadFromFiler(reader: TReader); override;
190
constructor Create(aOwner: TGLXCollection); override;
191
destructor Destroy; override;
193
procedure Assign(Source: TPersistent); override;
195
class function FriendlyName: string; override;
196
class function FriendlyDescription: string; override;
197
class function UniqueItem: boolean; override;
199
procedure DoProgress(const progressTime: TProgressTimes); override;
203
property Acceleration: TGLCoordinates read FAcceleration write FAcceleration;
206
{ Returns or creates the TGLBInertia within the given behaviours.
207
This helper function is convenient way to access a TGLBInertia. }
208
function GetInertia(const AGLSceneObject: TGLBaseSceneObject): TGLBInertia;
209
function GetOrCreateInertia(behaviours: TGLBehaviours): TGLBInertia; overload;
210
function GetOrCreateInertia(obj: TGLBaseSceneObject): TGLBInertia; overload;
212
{ Returns or creates the TGLBAcceleration within the given behaviours.
213
This helper function is convenient way to access a TGLBAcceleration. }
214
function GetOrCreateAcceleration(behaviours: TGLBehaviours): TGLBAcceleration;
216
function GetOrCreateAcceleration(obj: TGLBaseSceneObject): TGLBAcceleration; overload;
218
// ------------------------------------------------------------------
219
// ------------------------------------------------------------------
220
// ------------------------------------------------------------------
222
// ------------------------------------------------------------------
223
// ------------------------------------------------------------------
224
// ------------------------------------------------------------------
228
function GetInertia(const AGLSceneObject: TGLBaseSceneObject): TGLBInertia;
232
i := AGLSceneObject.behaviours.IndexOfClass(TGLBInertia);
234
Result := TGLBInertia(AGLSceneObject.behaviours[i])
239
// GetOrCreateInertia (TGLBehaviours)
241
function GetOrCreateInertia(behaviours: TGLBehaviours): TGLBInertia;
245
i := behaviours.IndexOfClass(TGLBInertia);
247
Result := TGLBInertia(behaviours[i])
249
Result := TGLBInertia.Create(behaviours);
252
// GetOrCreateInertia (TGLBaseSceneObject)
254
function GetOrCreateInertia(obj: TGLBaseSceneObject): TGLBInertia;
256
Result := GetOrCreateInertia(obj.Behaviours);
259
// GetOrCreateAcceleration (TGLBehaviours)
261
function GetOrCreateAcceleration(behaviours: TGLBehaviours): TGLBAcceleration;
265
i := behaviours.IndexOfClass(TGLBAcceleration);
267
Result := TGLBAcceleration(behaviours[i])
269
Result := TGLBAcceleration.Create(behaviours);
272
// GetOrCreateAcceleration (TGLBaseSceneObject)
274
function GetOrCreateAcceleration(obj: TGLBaseSceneObject): TGLBAcceleration;
276
Result := GetOrCreateAcceleration(obj.Behaviours);
280
// ------------------ TGLDamping ------------------
285
constructor TGLDamping.Create(aOwner: TPersistent);
287
inherited Create(AOwner);
290
destructor TGLDamping.Destroy;
297
procedure TGLDamping.Assign(Source: TPersistent);
299
if Source is TGLDamping then
301
FConstant := TGLDamping(Source).Constant;
302
FLinear := TGLDamping(Source).Linear;
303
FQuadratic := TGLDamping(Source).Quadratic;
306
inherited Assign(Source);
311
procedure TGLDamping.WriteToFiler(writer: TWriter);
317
WriteInteger(0); // Archive Version 0
318
writeStuff := (FConstant <> 0) or (FLinear <> 0) or (FQuadratic <> 0);
319
WriteBoolean(writeStuff);
322
WriteFloat(FConstant);
324
WriteFloat(FQuadratic);
331
procedure TGLDamping.ReadFromFiler(reader: TReader);
335
ReadInteger; // ignore Archive Version
338
FConstant := ReadFloat;
339
FLinear := ReadFloat;
340
FQuadratic := ReadFloat;
353
function TGLDamping.Calculate(speed, deltaTime: double): double;
357
while deltaTime > 0 do
359
if deltaTime > 0.01 then
362
deltaTime := deltaTime - 0.01;
369
speed := speed - dt * ((FQuadratic * speed + FLinear) * speed + FConstant);
376
function TGLDamping.AsString(const damping: TGLDamping): string;
378
Result := Format('[%f; %f; %f]', [Constant, Linear, Quadratic]);
383
procedure TGLDamping.SetDamping(const constant: single = 0;
384
const linear: single = 0; const quadratic: single = 0);
386
FConstant := constant;
388
FQuadratic := quadratic;
392
// ------------------ TGLBInertia ------------------
397
constructor TGLBInertia.Create(aOwner: TGLXCollection);
399
inherited Create(aOwner);
400
FTranslationSpeed := TGLCoordinates.CreateInitialized(Self, NullHmgVector, csVector);
402
FDampingEnabled := True;
403
FTranslationDamping := TGLDamping.Create(Self);
404
FRotationDamping := TGLDamping.Create(Self);
409
destructor TGLBInertia.Destroy;
411
FRotationDamping.Free;
412
FTranslationDamping.Free;
413
FTranslationSpeed.Free;
419
procedure TGLBInertia.Assign(Source: TPersistent);
421
if Source.ClassType = Self.ClassType then
423
FMass := TGLBInertia(Source).Mass;
424
FTranslationSpeed.Assign(TGLBInertia(Source).FTranslationSpeed);
425
FTurnSpeed := TGLBInertia(Source).TurnSpeed;
426
FRollSpeed := TGLBInertia(Source).RollSpeed;
427
FPitchSpeed := TGLBInertia(Source).PitchSpeed;
428
FDampingEnabled := TGLBInertia(Source).DampingEnabled;
429
FTranslationDamping.Assign(TGLBInertia(Source).TranslationDamping);
430
FRotationDamping.Assign(TGLBInertia(Source).RotationDamping);
432
inherited Assign(Source);
437
procedure TGLBInertia.WriteToFiler(writer: TWriter);
442
WriteInteger(0); // Archive Version 0
444
FTranslationSpeed.WriteToFiler(writer);
445
WriteFloat(FTurnSpeed);
446
WriteFloat(FRollSpeed);
447
WriteFloat(FPitchSpeed);
448
WriteBoolean(FDampingEnabled);
449
FTranslationDamping.WriteToFiler(writer);
450
FRotationDamping.WriteToFiler(writer);
456
procedure TGLBInertia.ReadFromFiler(reader: TReader);
461
ReadInteger; // ignore archiveVersion
463
FTranslationSpeed.ReadFromFiler(reader);
464
FTurnSpeed := ReadFloat;
465
FRollSpeed := ReadFloat;
466
FPitchSpeed := ReadFloat;
467
FDampingEnabled := ReadBoolean;
468
FTranslationDamping.ReadFromFiler(reader);
469
FRotationDamping.ReadFromFiler(reader);
473
// SetTranslationSpeed
475
procedure TGLBInertia.SetTranslationSpeed(const val: TGLCoordinates);
477
FTranslationSpeed.Assign(val);
480
// SetTranslationDamping
482
procedure TGLBInertia.SetTranslationDamping(const val: TGLDamping);
484
FTranslationDamping.Assign(val);
489
procedure TGLBInertia.SetRotationDamping(const val: TGLDamping);
491
FRotationDamping.Assign(val);
496
class function TGLBInertia.FriendlyName: string;
498
Result := 'Simple Inertia';
501
// FriendlyDescription
503
class function TGLBInertia.FriendlyDescription: string;
505
Result := 'A simple translation and rotation inertia';
510
class function TGLBInertia.UniqueItem: boolean;
517
procedure TGLBInertia.DoProgress(const progressTime: TProgressTimes);
520
speed, newSpeed: double;
522
procedure ApplyRotationDamping(var rotationSpeed: single);
524
if rotationSpeed > 0 then
526
rotationSpeed := RotationDamping.Calculate(rotationSpeed, progressTime.deltaTime);
527
if rotationSpeed <= 0 then
532
rotationSpeed := -RotationDamping.Calculate(-rotationSpeed, progressTime.deltaTime);
533
if rotationSpeed >= 0 then
539
// Apply damping to speed
540
if DampingEnabled then
542
// Translation damping
543
speed := TranslationSpeed.VectorLength;
546
newSpeed := TranslationDamping.Calculate(speed, progressTime.deltaTime);
547
if newSpeed <= 0 then
549
trnVector := NullHmgVector;
550
TranslationSpeed.AsVector := trnVector;
554
TranslationSpeed.Scale(newSpeed / Speed);
555
SetVector(trnVector, TranslationSpeed.AsVector);
559
SetVector(trnVector, NullHmgVector);
560
// Rotation damping (yuck!)
561
ApplyRotationDamping(FTurnSpeed);
562
ApplyRotationDamping(FRollSpeed);
563
ApplyRotationDamping(FPitchSpeed);
566
SetVector(trnVector, TranslationSpeed.AsVector);
567
// Apply speed to object
568
with OwnerBaseSceneObject do
571
Position.AddScaledVector(deltaTime, trnVector);
572
TurnAngle := TurnAngle + TurnSpeed * deltaTime;
573
RollAngle := RollAngle + RollSpeed * deltaTime;
574
PitchAngle := PitchAngle + PitchSpeed * deltaTime;
578
// ApplyTranslationAcceleration
580
procedure TGLBInertia.ApplyTranslationAcceleration(const deltaTime: double;
581
const accel: TVector);
583
FTranslationSpeed.AsVector := VectorCombine(FTranslationSpeed.AsVector,
584
accel, 1, deltaTime);
589
procedure TGLBInertia.ApplyForce(const deltaTime: double; const force: TVector);
592
FTranslationSpeed.AsVector :=
593
VectorCombine(FTranslationSpeed.AsVector, force, 1, deltaTime / Mass);
598
procedure TGLBInertia.ApplyTorque(const deltaTime: double;
599
const turnTorque, rollTorque, pitchTorque: single);
605
factor := deltaTime / Mass;
606
FTurnSpeed := FTurnSpeed + turnTorque * factor;
607
FRollSpeed := FRollSpeed + rollTorque * factor;
608
FPitchSpeed := FPitchSpeed + pitchTorque * factor;
614
procedure TGLBInertia.MirrorTranslation;
616
FTranslationSpeed.Invert;
621
procedure TGLBInertia.SurfaceBounce(const surfaceNormal: TVector; restitution: single);
625
// does the current speed vector comply?
626
f := VectorDotProduct(FTranslationSpeed.AsVector, surfaceNormal);
629
// remove the non-complying part of the speed vector
630
FTranslationSpeed.AddScaledVector(-f / VectorNorm(surfaceNormal) *
631
(1 + restitution), surfaceNormal);
636
// ------------------ TGLBAcceleration ------------------
641
constructor TGLBAcceleration.Create(aOwner: TGLXCollection);
644
if aOwner <> nil then
645
if not (csReading in TComponent(aOwner.Owner).ComponentState) then
646
GetOrCreateInertia(TGLBehaviours(aOwner));
647
FAcceleration := TGLCoordinates.CreateInitialized(Self, NullHmgVector, csVector);
652
destructor TGLBAcceleration.Destroy;
660
procedure TGLBAcceleration.Assign(Source: TPersistent);
662
if Source.ClassType = Self.ClassType then
664
FAcceleration.Assign(TGLBAcceleration(Source).FAcceleration);
666
inherited Assign(Source);
671
procedure TGLBAcceleration.WriteToFiler(writer: TWriter);
676
WriteInteger(0); // Archive Version 0
677
FAcceleration.WriteToFiler(writer);
683
procedure TGLBAcceleration.ReadFromFiler(reader: TReader);
688
ReadInteger; // ignore archiveVersion
689
FAcceleration.ReadFromFiler(reader);
695
procedure TGLBAcceleration.SetAcceleration(const val: TGLCoordinates);
697
FAcceleration.Assign(val);
702
class function TGLBAcceleration.FriendlyName: string;
704
Result := 'Simple Acceleration';
707
// FriendlyDescription
709
class function TGLBAcceleration.FriendlyDescription: string;
711
Result := 'A simple and constant acceleration';
716
class function TGLBAcceleration.UniqueItem: boolean;
723
procedure TGLBAcceleration.DoProgress(const progressTime: TProgressTimes);
726
Inertia: TGLBInertia;
728
i := Owner.IndexOfClass(TGLBInertia);
731
Inertia := TGLBInertia(Owner[i]);
732
Inertia.ApplyTranslationAcceleration(progressTime.deltaTime,
733
FAcceleration.DirectVector);
737
TGLBInertia.Create(Owner);
738
//on next progress event this exception won't be raised, because TGLBInertia will be created again
739
raise Exception.Create(ClassName + ' requires ' + TGLBInertia.ClassName +
740
'! (' + TGLBInertia.ClassName + ' was added to the Behaviours again)');
744
// ------------------------------------------------------------------
745
// ------------------------------------------------------------------
746
// ------------------------------------------------------------------
748
// ------------------------------------------------------------------
749
// ------------------------------------------------------------------
750
// ------------------------------------------------------------------
752
// class registrations
753
RegisterXCollectionItemClass(TGLBInertia);
754
RegisterXCollectionItemClass(TGLBAcceleration);
758
UnregisterXCollectionItemClass(TGLBInertia);
759
UnregisterXCollectionItemClass(TGLBAcceleration);