GLS.Behaviours.pas 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. //
  2. // The graphics engine GLScene https://github.com/glscene
  3. //
  4. unit GLS.Behaviours;
  5. (* Standard TGLBehaviour subclasses *)
  6. interface
  7. {$I GLScene.Defines.inc}
  8. uses
  9. System.Classes,
  10. System.SysUtils,
  11. GLScene.VectorTypes,
  12. GLS.Scene,
  13. GLScene.VectorGeometry,
  14. GLS.XCollection,
  15. GLS.BaseClasses,
  16. GLS.Coordinates;
  17. type
  18. (*
  19. Holds parameters for a basic damping model by calculating a force
  20. from the speed, this force can then be transformed to an acceleration
  21. is you know the object's mass.
  22. Formulas:
  23. damping = constant + linear * Speed + quadratic * Speed^2
  24. accel = damping / Mass
  25. That's just basic physics. A note on the components:
  26. constant: use it for solid friction (will stop abruptly an object after decreasing its speed.
  27. linear: linear friction damping.
  28. quadratic: expresses viscosity
  29. *)
  30. TGLDamping = class(TGLUpdateAbleObject)
  31. private
  32. FConstant: single;
  33. FLinear: single;
  34. FQuadratic: single;
  35. public
  36. constructor Create(aOwner: TPersistent); override;
  37. destructor Destroy; override;
  38. procedure WriteToFiler(writer: TWriter);
  39. procedure ReadFromFiler(reader: TReader);
  40. procedure Assign(Source: TPersistent); override;
  41. (*Calculates attenuated speed over deltaTime.
  42. integration step is 0.01 sec, and the following formula is applied
  43. at each step: constant+linear*speed+quadratic*speed^2 *)
  44. function Calculate(speed, deltaTime: Double): Double;
  45. // Returns a "[constant; linear; quadratic]" String
  46. function AsString(const damping: TGLDamping): String;
  47. // Sets all damping parameters in a single call.
  48. procedure SetDamping(const constant: Single = 0;
  49. const linear: Single = 0;
  50. const quadratic: Single = 0);
  51. published
  52. property Constant: Single read FConstant write FConstant;
  53. property Linear: Single read FLinear write FLinear;
  54. property Quadratic: Single read FQuadratic write FQuadratic;
  55. end;
  56. (* Simple translation and rotation Inertia behaviour.
  57. Stores translation and rotation speeds, to which you can apply
  58. accelerations.
  59. Note that the rotation model is not physical, so feel free to contribute
  60. a "realworld" inertia class with realistic, axis-free, rotation inertia
  61. if this approximation does not suits your needs :). *)
  62. TGLBInertia = class(TGLBehaviour)
  63. private
  64. FMass: single;
  65. FTranslationSpeed: TGLCoordinates;
  66. FTurnSpeed, FRollSpeed, FPitchSpeed: single;
  67. FTranslationDamping, FRotationDamping: TGLDamping;
  68. FDampingEnabled: boolean;
  69. protected
  70. procedure SetTranslationSpeed(const val: TGLCoordinates);
  71. procedure SetTranslationDamping(const val: TGLDamping);
  72. procedure SetRotationDamping(const val: TGLDamping);
  73. procedure WriteToFiler(writer: TWriter); override;
  74. procedure ReadFromFiler(reader: TReader); override;
  75. public
  76. constructor Create(aOwner: TXCollection); override;
  77. destructor Destroy; override;
  78. procedure Assign(Source: TPersistent); override;
  79. class function FriendlyName: string; override;
  80. class function FriendlyDescription: string; override;
  81. class function UniqueItem: boolean; override;
  82. procedure DoProgress(const progressTime: TGLProgressTimes); override;
  83. // Adds time-proportionned acceleration to the speed.
  84. procedure ApplyTranslationAcceleration(const deltaTime: double;
  85. const accel: TGLVector);
  86. // Applies a timed force to the inertia. If Mass is null, nothing is done.
  87. procedure ApplyForce(const deltaTime: Double; const Force: TGLVector);
  88. (*Applies a timed torque to the inertia (yuck!).
  89. This gets a "yuck!" because it is as false as the rest of the rotation model. *)
  90. procedure ApplyTorque(const deltaTime: double;
  91. const turnTorque, rollTorque, pitchTorque: single);
  92. // Inverts the translation vector.
  93. procedure MirrorTranslation;
  94. (* Bounce speed as if hitting a surface.
  95. restitution is the coefficient of restituted energy (1=no energy loss, 0=no bounce).
  96. The normal is NOT assumed to be normalized. *)
  97. procedure SurfaceBounce(const surfaceNormal: TGLVector; restitution: single);
  98. published
  99. property Mass: single read FMass write FMass;
  100. property TranslationSpeed: TGLCoordinates read FTranslationSpeed write SetTranslationSpeed;
  101. property TurnSpeed: single read FTurnSpeed write FTurnSpeed;
  102. property RollSpeed: single read FRollSpeed write FRollSpeed;
  103. property PitchSpeed: single read FPitchSpeed write FPitchSpeed;
  104. // Enable/Disable damping (damping has a high cpu-cycle cost). Damping is enabled by default.
  105. property DampingEnabled: boolean read FDampingEnabled write FDampingEnabled;
  106. (* Damping applied to translation speed.
  107. Note that it is not "exactly" applied, ie. if damping would stop
  108. your object after 0.5 time unit, and your progression steps are
  109. of 1 time unit, there will be an integration error of 0.5 time unit. *)
  110. property TranslationDamping: TGLDamping read FTranslationDamping write SetTranslationDamping;
  111. (* Damping applied to rotation speed (yuck!).
  112. Well, this one is not "exact", like TranslationDamping, and neither
  113. it is "physical" since I'm reusing the mass and... and... well don't
  114. show this to your science teacher 8).
  115. Anyway that's easier to use than the realworld formulas, calculated
  116. faster, and properly used can give a good illusion of reality. *)
  117. property RotationDamping: TGLDamping read FRotationDamping write SetRotationDamping;
  118. end;
  119. // Applies a constant acceleration to a TGLBInertia.
  120. TGLBAcceleration = class(TGLBehaviour)
  121. private
  122. FAcceleration: TGLCoordinates;
  123. protected
  124. procedure SetAcceleration(const val: TGLCoordinates);
  125. procedure WriteToFiler(writer: TWriter); override;
  126. procedure ReadFromFiler(reader: TReader); override;
  127. public
  128. constructor Create(aOwner: TXCollection); override;
  129. destructor Destroy; override;
  130. procedure Assign(Source: TPersistent); override;
  131. class function FriendlyName: string; override;
  132. class function FriendlyDescription: string; override;
  133. class function UniqueItem: boolean; override;
  134. procedure DoProgress(const progressTime: TGLProgressTimes); override;
  135. published
  136. property Acceleration: TGLCoordinates read FAcceleration write FAcceleration;
  137. end;
  138. (* Returns or creates the TGLBInertia within the given behaviours.
  139. This helper function is convenient way to access a TGLBInertia. *)
  140. function GetInertia(const AGLSceneObject: TGLBaseSceneObject): TGLBInertia;
  141. function GetOrCreateInertia(behaviours: TGLBehaviours): TGLBInertia; overload;
  142. function GetOrCreateInertia(obj: TGLBaseSceneObject): TGLBInertia; overload;
  143. (* Returns or creates the TGLBAcceleration within the given behaviours.
  144. This helper function is convenient way to access a TGLBAcceleration. *)
  145. function GetOrCreateAcceleration(behaviours: TGLBehaviours): TGLBAcceleration; overload;
  146. function GetOrCreateAcceleration(obj: TGLBaseSceneObject): TGLBAcceleration; overload;
  147. // ------------------------------------------------------------------
  148. implementation
  149. // ------------------------------------------------------------------
  150. function GetInertia(const AGLSceneObject: TGLBaseSceneObject): TGLBInertia;
  151. var
  152. i: integer;
  153. begin
  154. i := AGLSceneObject.behaviours.IndexOfClass(TGLBInertia);
  155. if i >= 0 then
  156. Result := TGLBInertia(AGLSceneObject.behaviours[i])
  157. else
  158. Result := nil;
  159. end;
  160. function GetOrCreateInertia(behaviours: TGLBehaviours): TGLBInertia;
  161. var
  162. i: integer;
  163. begin
  164. i := behaviours.IndexOfClass(TGLBInertia);
  165. if i >= 0 then
  166. Result := TGLBInertia(behaviours[i])
  167. else
  168. Result := TGLBInertia.Create(behaviours);
  169. end;
  170. function GetOrCreateInertia(obj: TGLBaseSceneObject): TGLBInertia;
  171. begin
  172. Result := GetOrCreateInertia(obj.Behaviours);
  173. end;
  174. function GetOrCreateAcceleration(behaviours: TGLBehaviours): TGLBAcceleration;
  175. var
  176. i: integer;
  177. begin
  178. i := behaviours.IndexOfClass(TGLBAcceleration);
  179. if i >= 0 then
  180. Result := TGLBAcceleration(behaviours[i])
  181. else
  182. Result := TGLBAcceleration.Create(behaviours);
  183. end;
  184. function GetOrCreateAcceleration(obj: TGLBaseSceneObject): TGLBAcceleration;
  185. begin
  186. Result := GetOrCreateAcceleration(obj.Behaviours);
  187. end;
  188. // ------------------
  189. // ------------------ TGLDamping ------------------
  190. // ------------------
  191. constructor TGLDamping.Create(aOwner: TPersistent);
  192. begin
  193. inherited Create(AOwner);
  194. end;
  195. destructor TGLDamping.Destroy;
  196. begin
  197. inherited Destroy;
  198. end;
  199. procedure TGLDamping.Assign(Source: TPersistent);
  200. begin
  201. if Source is TGLDamping then
  202. begin
  203. FConstant := TGLDamping(Source).Constant;
  204. FLinear := TGLDamping(Source).Linear;
  205. FQuadratic := TGLDamping(Source).Quadratic;
  206. end
  207. else
  208. inherited Assign(Source);
  209. end;
  210. procedure TGLDamping.WriteToFiler(writer: TWriter);
  211. var
  212. writeStuff: boolean;
  213. begin
  214. with writer do
  215. begin
  216. WriteInteger(0); // Archive Version 0
  217. writeStuff := (FConstant <> 0) or (FLinear <> 0) or (FQuadratic <> 0);
  218. WriteBoolean(writeStuff);
  219. if writeStuff then
  220. begin
  221. WriteFloat(FConstant);
  222. WriteFloat(FLinear);
  223. WriteFloat(FQuadratic);
  224. end;
  225. end;
  226. end;
  227. procedure TGLDamping.ReadFromFiler(reader: TReader);
  228. begin
  229. with reader do
  230. begin
  231. ReadInteger; // ignore Archive Version
  232. if ReadBoolean then
  233. begin
  234. FConstant := ReadFloat;
  235. FLinear := ReadFloat;
  236. FQuadratic := ReadFloat;
  237. end
  238. else
  239. begin
  240. FConstant := 0;
  241. FLinear := 0;
  242. FQuadratic := 0;
  243. end;
  244. end;
  245. end;
  246. function TGLDamping.Calculate(speed, deltaTime: double): double;
  247. var
  248. dt: double;
  249. begin
  250. while deltaTime > 0 do
  251. begin
  252. if deltaTime > 0.01 then
  253. begin
  254. dt := 0.01;
  255. deltaTime := deltaTime - 0.01;
  256. end
  257. else
  258. begin
  259. dt := deltaTime;
  260. deltaTime := 0;
  261. end;
  262. speed := speed - dt * ((FQuadratic * speed + FLinear) * speed + FConstant);
  263. end;
  264. Result := speed;
  265. end;
  266. function TGLDamping.AsString(const damping: TGLDamping): string;
  267. begin
  268. Result := Format('[%f; %f; %f]', [Constant, Linear, Quadratic]);
  269. end;
  270. procedure TGLDamping.SetDamping(const constant: single = 0;
  271. const linear: single = 0; const quadratic: single = 0);
  272. begin
  273. FConstant := constant;
  274. FLinear := linear;
  275. FQuadratic := quadratic;
  276. end;
  277. // ------------------
  278. // ------------------ TGLBInertia ------------------
  279. // ------------------
  280. constructor TGLBInertia.Create(aOwner: TXCollection);
  281. begin
  282. inherited Create(aOwner);
  283. FTranslationSpeed := TGLCoordinates.CreateInitialized(Self, NullHmgVector, csVector);
  284. FMass := 1;
  285. FDampingEnabled := True;
  286. FTranslationDamping := TGLDamping.Create(Self);
  287. FRotationDamping := TGLDamping.Create(Self);
  288. end;
  289. destructor TGLBInertia.Destroy;
  290. begin
  291. FRotationDamping.Free;
  292. FTranslationDamping.Free;
  293. FTranslationSpeed.Free;
  294. inherited Destroy;
  295. end;
  296. procedure TGLBInertia.Assign(Source: TPersistent);
  297. begin
  298. if Source.ClassType = Self.ClassType then
  299. begin
  300. FMass := TGLBInertia(Source).Mass;
  301. FTranslationSpeed.Assign(TGLBInertia(Source).FTranslationSpeed);
  302. FTurnSpeed := TGLBInertia(Source).TurnSpeed;
  303. FRollSpeed := TGLBInertia(Source).RollSpeed;
  304. FPitchSpeed := TGLBInertia(Source).PitchSpeed;
  305. FDampingEnabled := TGLBInertia(Source).DampingEnabled;
  306. FTranslationDamping.Assign(TGLBInertia(Source).TranslationDamping);
  307. FRotationDamping.Assign(TGLBInertia(Source).RotationDamping);
  308. end;
  309. inherited Assign(Source);
  310. end;
  311. procedure TGLBInertia.WriteToFiler(writer: TWriter);
  312. begin
  313. inherited;
  314. with writer do
  315. begin
  316. WriteInteger(0); // Archive Version 0
  317. WriteFloat(FMass);
  318. FTranslationSpeed.WriteToFiler(writer);
  319. WriteFloat(FTurnSpeed);
  320. WriteFloat(FRollSpeed);
  321. WriteFloat(FPitchSpeed);
  322. WriteBoolean(FDampingEnabled);
  323. FTranslationDamping.WriteToFiler(writer);
  324. FRotationDamping.WriteToFiler(writer);
  325. end;
  326. end;
  327. procedure TGLBInertia.ReadFromFiler(reader: TReader);
  328. begin
  329. inherited;
  330. with reader do
  331. begin
  332. ReadInteger; // ignore archiveVersion
  333. FMass := ReadFloat;
  334. FTranslationSpeed.ReadFromFiler(reader);
  335. FTurnSpeed := ReadFloat;
  336. FRollSpeed := ReadFloat;
  337. FPitchSpeed := ReadFloat;
  338. FDampingEnabled := ReadBoolean;
  339. FTranslationDamping.ReadFromFiler(reader);
  340. FRotationDamping.ReadFromFiler(reader);
  341. end;
  342. end;
  343. procedure TGLBInertia.SetTranslationSpeed(const val: TGLCoordinates);
  344. begin
  345. FTranslationSpeed.Assign(val);
  346. end;
  347. procedure TGLBInertia.SetTranslationDamping(const val: TGLDamping);
  348. begin
  349. FTranslationDamping.Assign(val);
  350. end;
  351. procedure TGLBInertia.SetRotationDamping(const val: TGLDamping);
  352. begin
  353. FRotationDamping.Assign(val);
  354. end;
  355. class function TGLBInertia.FriendlyName: string;
  356. begin
  357. Result := 'Simple Inertia';
  358. end;
  359. class function TGLBInertia.FriendlyDescription: string;
  360. begin
  361. Result := 'A simple translation and rotation inertia';
  362. end;
  363. class function TGLBInertia.UniqueItem: boolean;
  364. begin
  365. Result := True;
  366. end;
  367. procedure TGLBInertia.DoProgress(const progressTime: TGLProgressTimes);
  368. var
  369. trnVector: TGLVector;
  370. speed, newSpeed: double;
  371. procedure ApplyRotationDamping(var rotationSpeed: single);
  372. begin
  373. if rotationSpeed > 0 then
  374. begin
  375. rotationSpeed := RotationDamping.Calculate(rotationSpeed, progressTime.deltaTime);
  376. if rotationSpeed <= 0 then
  377. rotationSpeed := 0;
  378. end
  379. else
  380. begin
  381. rotationSpeed := -RotationDamping.Calculate(-rotationSpeed, progressTime.deltaTime);
  382. if rotationSpeed >= 0 then
  383. rotationSpeed := 0;
  384. end;
  385. end;
  386. begin
  387. // Apply damping to speed
  388. if DampingEnabled then
  389. begin
  390. // Translation damping
  391. speed := TranslationSpeed.VectorLength;
  392. if speed > 0 then
  393. begin
  394. newSpeed := TranslationDamping.Calculate(speed, progressTime.deltaTime);
  395. if newSpeed <= 0 then
  396. begin
  397. trnVector := NullHmgVector;
  398. TranslationSpeed.AsVector := trnVector;
  399. end
  400. else
  401. begin
  402. TranslationSpeed.Scale(newSpeed / Speed);
  403. SetVector(trnVector, TranslationSpeed.AsVector);
  404. end;
  405. end
  406. else
  407. SetVector(trnVector, NullHmgVector);
  408. // Rotation damping (yuck!)
  409. ApplyRotationDamping(FTurnSpeed);
  410. ApplyRotationDamping(FRollSpeed);
  411. ApplyRotationDamping(FPitchSpeed);
  412. end
  413. else
  414. SetVector(trnVector, TranslationSpeed.AsVector);
  415. // Apply speed to object
  416. with OwnerBaseSceneObject do
  417. with progressTime do
  418. begin
  419. Position.AddScaledVector(deltaTime, trnVector);
  420. TurnAngle := TurnAngle + TurnSpeed * deltaTime;
  421. RollAngle := RollAngle + RollSpeed * deltaTime;
  422. PitchAngle := PitchAngle + PitchSpeed * deltaTime;
  423. end;
  424. end;
  425. procedure TGLBInertia.ApplyTranslationAcceleration(const deltaTime: double;
  426. const accel: TGLVector);
  427. begin
  428. FTranslationSpeed.AsVector := VectorCombine(FTranslationSpeed.AsVector,
  429. accel, 1, deltaTime);
  430. end;
  431. procedure TGLBInertia.ApplyForce(const deltaTime: double; const force: TGLVector);
  432. begin
  433. if Mass <> 0 then
  434. FTranslationSpeed.AsVector :=
  435. VectorCombine(FTranslationSpeed.AsVector, force, 1, deltaTime / Mass);
  436. end;
  437. procedure TGLBInertia.ApplyTorque(const deltaTime: double;
  438. const turnTorque, rollTorque, pitchTorque: single);
  439. var
  440. factor: double;
  441. begin
  442. if Mass <> 0 then
  443. begin
  444. factor := deltaTime / Mass;
  445. FTurnSpeed := FTurnSpeed + turnTorque * factor;
  446. FRollSpeed := FRollSpeed + rollTorque * factor;
  447. FPitchSpeed := FPitchSpeed + pitchTorque * factor;
  448. end;
  449. end;
  450. procedure TGLBInertia.MirrorTranslation;
  451. begin
  452. FTranslationSpeed.Invert;
  453. end;
  454. procedure TGLBInertia.SurfaceBounce(const surfaceNormal: TGLVector; restitution: single);
  455. var
  456. f: single;
  457. begin
  458. // does the current speed vector comply?
  459. f := VectorDotProduct(FTranslationSpeed.AsVector, surfaceNormal);
  460. if f < 0 then
  461. begin
  462. // remove the non-complying part of the speed vector
  463. FTranslationSpeed.AddScaledVector(-f / VectorNorm(surfaceNormal) *
  464. (1 + restitution), surfaceNormal);
  465. end;
  466. end;
  467. // ------------------
  468. // ------------------ TGLBAcceleration ------------------
  469. // ------------------
  470. constructor TGLBAcceleration.Create(aOwner: TXCollection);
  471. begin
  472. inherited;
  473. if aOwner <> nil then
  474. if not (csReading in TComponent(aOwner.Owner).ComponentState) then
  475. GetOrCreateInertia(TGLBehaviours(aOwner));
  476. FAcceleration := TGLCoordinates.CreateInitialized(Self, NullHmgVector, csVector);
  477. end;
  478. destructor TGLBAcceleration.Destroy;
  479. begin
  480. inherited;
  481. FAcceleration.Free;
  482. end;
  483. procedure TGLBAcceleration.Assign(Source: TPersistent);
  484. begin
  485. if Source.ClassType = Self.ClassType then
  486. begin
  487. FAcceleration.Assign(TGLBAcceleration(Source).FAcceleration);
  488. end;
  489. inherited Assign(Source);
  490. end;
  491. procedure TGLBAcceleration.WriteToFiler(writer: TWriter);
  492. begin
  493. inherited;
  494. with writer do
  495. begin
  496. WriteInteger(0); // Archive Version 0
  497. FAcceleration.WriteToFiler(writer);
  498. end;
  499. end;
  500. procedure TGLBAcceleration.ReadFromFiler(reader: TReader);
  501. begin
  502. inherited;
  503. with reader do
  504. begin
  505. ReadInteger; // ignore archiveVersion
  506. FAcceleration.ReadFromFiler(reader);
  507. end;
  508. end;
  509. procedure TGLBAcceleration.SetAcceleration(const val: TGLCoordinates);
  510. begin
  511. FAcceleration.Assign(val);
  512. end;
  513. class function TGLBAcceleration.FriendlyName: string;
  514. begin
  515. Result := 'Simple Acceleration';
  516. end;
  517. class function TGLBAcceleration.FriendlyDescription: string;
  518. begin
  519. Result := 'A simple and constant acceleration';
  520. end;
  521. class function TGLBAcceleration.UniqueItem: boolean;
  522. begin
  523. Result := False;
  524. end;
  525. procedure TGLBAcceleration.DoProgress(const progressTime: TGLProgressTimes);
  526. var
  527. i: integer;
  528. Inertia: TGLBInertia;
  529. begin
  530. i := Owner.IndexOfClass(TGLBInertia);
  531. if i >= 0 then
  532. begin
  533. Inertia := TGLBInertia(Owner[i]);
  534. Inertia.ApplyTranslationAcceleration(progressTime.deltaTime,
  535. FAcceleration.DirectVector);
  536. end
  537. else
  538. begin
  539. TGLBInertia.Create(Owner);
  540. //on next progress event this exception won't be raised, because TGLBInertia will be created again
  541. raise Exception.Create(ClassName + ' requires ' + TGLBInertia.ClassName +
  542. '! (' + TGLBInertia.ClassName + ' was added to the Behaviours again)');
  543. end;
  544. end;
  545. // ------------------------------------------------------------------
  546. initialization
  547. // ------------------------------------------------------------------
  548. // class registrations
  549. RegisterXCollectionItemClass(TGLBInertia);
  550. RegisterXCollectionItemClass(TGLBAcceleration);
  551. finalization
  552. UnregisterXCollectionItemClass(TGLBInertia);
  553. UnregisterXCollectionItemClass(TGLBAcceleration);
  554. end.