GLS.Behaviours.pas 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. //
  2. // The graphics engine GLScene
  3. //
  4. unit GLS.Behaviours;
  5. (* Standard TGLBehaviour subclasses *)
  6. interface
  7. {$I Stage.Defines.inc}
  8. uses
  9. System.Classes,
  10. System.SysUtils,
  11. Stage.VectorTypes,
  12. GLS.Scene,
  13. Stage.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. implementation // -----------------------------------------------------------
  148. function GetInertia(const AGLSceneObject: TGLBaseSceneObject): TGLBInertia;
  149. var
  150. i: integer;
  151. begin
  152. i := AGLSceneObject.behaviours.IndexOfClass(TGLBInertia);
  153. if i >= 0 then
  154. Result := TGLBInertia(AGLSceneObject.behaviours[i])
  155. else
  156. Result := nil;
  157. end;
  158. function GetOrCreateInertia(behaviours: TGLBehaviours): TGLBInertia;
  159. var
  160. i: integer;
  161. begin
  162. i := behaviours.IndexOfClass(TGLBInertia);
  163. if i >= 0 then
  164. Result := TGLBInertia(behaviours[i])
  165. else
  166. Result := TGLBInertia.Create(behaviours);
  167. end;
  168. function GetOrCreateInertia(obj: TGLBaseSceneObject): TGLBInertia;
  169. begin
  170. Result := GetOrCreateInertia(obj.Behaviours);
  171. end;
  172. function GetOrCreateAcceleration(behaviours: TGLBehaviours): TGLBAcceleration;
  173. var
  174. i: integer;
  175. begin
  176. i := behaviours.IndexOfClass(TGLBAcceleration);
  177. if i >= 0 then
  178. Result := TGLBAcceleration(behaviours[i])
  179. else
  180. Result := TGLBAcceleration.Create(behaviours);
  181. end;
  182. function GetOrCreateAcceleration(obj: TGLBaseSceneObject): TGLBAcceleration;
  183. begin
  184. Result := GetOrCreateAcceleration(obj.Behaviours);
  185. end;
  186. // ------------------
  187. // ------------------ TGLDamping ------------------
  188. // ------------------
  189. constructor TGLDamping.Create(aOwner: TPersistent);
  190. begin
  191. inherited Create(AOwner);
  192. end;
  193. destructor TGLDamping.Destroy;
  194. begin
  195. inherited Destroy;
  196. end;
  197. procedure TGLDamping.Assign(Source: TPersistent);
  198. begin
  199. if Source is TGLDamping then
  200. begin
  201. FConstant := TGLDamping(Source).Constant;
  202. FLinear := TGLDamping(Source).Linear;
  203. FQuadratic := TGLDamping(Source).Quadratic;
  204. end
  205. else
  206. inherited Assign(Source);
  207. end;
  208. procedure TGLDamping.WriteToFiler(writer: TWriter);
  209. var
  210. writeStuff: boolean;
  211. begin
  212. with writer do
  213. begin
  214. WriteInteger(0); // Archive Version 0
  215. writeStuff := (FConstant <> 0) or (FLinear <> 0) or (FQuadratic <> 0);
  216. WriteBoolean(writeStuff);
  217. if writeStuff then
  218. begin
  219. WriteFloat(FConstant);
  220. WriteFloat(FLinear);
  221. WriteFloat(FQuadratic);
  222. end;
  223. end;
  224. end;
  225. procedure TGLDamping.ReadFromFiler(reader: TReader);
  226. begin
  227. with reader do
  228. begin
  229. ReadInteger; // ignore Archive Version
  230. if ReadBoolean then
  231. begin
  232. FConstant := ReadFloat;
  233. FLinear := ReadFloat;
  234. FQuadratic := ReadFloat;
  235. end
  236. else
  237. begin
  238. FConstant := 0;
  239. FLinear := 0;
  240. FQuadratic := 0;
  241. end;
  242. end;
  243. end;
  244. function TGLDamping.Calculate(speed, deltaTime: double): double;
  245. var
  246. dt: double;
  247. begin
  248. while deltaTime > 0 do
  249. begin
  250. if deltaTime > 0.01 then
  251. begin
  252. dt := 0.01;
  253. deltaTime := deltaTime - 0.01;
  254. end
  255. else
  256. begin
  257. dt := deltaTime;
  258. deltaTime := 0;
  259. end;
  260. speed := speed - dt * ((FQuadratic * speed + FLinear) * speed + FConstant);
  261. end;
  262. Result := speed;
  263. end;
  264. function TGLDamping.AsString(const damping: TGLDamping): string;
  265. begin
  266. Result := Format('[%f; %f; %f]', [Constant, Linear, Quadratic]);
  267. end;
  268. procedure TGLDamping.SetDamping(const constant: single = 0;
  269. const linear: single = 0; const quadratic: single = 0);
  270. begin
  271. FConstant := constant;
  272. FLinear := linear;
  273. FQuadratic := quadratic;
  274. end;
  275. // ------------------
  276. // ------------------ TGLBInertia ------------------
  277. // ------------------
  278. constructor TGLBInertia.Create(aOwner: TXCollection);
  279. begin
  280. inherited Create(aOwner);
  281. FTranslationSpeed := TGLCoordinates.CreateInitialized(Self, NullHmgVector, csVector);
  282. FMass := 1;
  283. FDampingEnabled := True;
  284. FTranslationDamping := TGLDamping.Create(Self);
  285. FRotationDamping := TGLDamping.Create(Self);
  286. end;
  287. destructor TGLBInertia.Destroy;
  288. begin
  289. FRotationDamping.Free;
  290. FTranslationDamping.Free;
  291. FTranslationSpeed.Free;
  292. inherited Destroy;
  293. end;
  294. procedure TGLBInertia.Assign(Source: TPersistent);
  295. begin
  296. if Source.ClassType = Self.ClassType then
  297. begin
  298. FMass := TGLBInertia(Source).Mass;
  299. FTranslationSpeed.Assign(TGLBInertia(Source).FTranslationSpeed);
  300. FTurnSpeed := TGLBInertia(Source).TurnSpeed;
  301. FRollSpeed := TGLBInertia(Source).RollSpeed;
  302. FPitchSpeed := TGLBInertia(Source).PitchSpeed;
  303. FDampingEnabled := TGLBInertia(Source).DampingEnabled;
  304. FTranslationDamping.Assign(TGLBInertia(Source).TranslationDamping);
  305. FRotationDamping.Assign(TGLBInertia(Source).RotationDamping);
  306. end;
  307. inherited Assign(Source);
  308. end;
  309. procedure TGLBInertia.WriteToFiler(writer: TWriter);
  310. begin
  311. inherited;
  312. with writer do
  313. begin
  314. WriteInteger(0); // Archive Version 0
  315. WriteFloat(FMass);
  316. FTranslationSpeed.WriteToFiler(writer);
  317. WriteFloat(FTurnSpeed);
  318. WriteFloat(FRollSpeed);
  319. WriteFloat(FPitchSpeed);
  320. WriteBoolean(FDampingEnabled);
  321. FTranslationDamping.WriteToFiler(writer);
  322. FRotationDamping.WriteToFiler(writer);
  323. end;
  324. end;
  325. procedure TGLBInertia.ReadFromFiler(reader: TReader);
  326. begin
  327. inherited;
  328. with reader do
  329. begin
  330. ReadInteger; // ignore archiveVersion
  331. FMass := ReadFloat;
  332. FTranslationSpeed.ReadFromFiler(reader);
  333. FTurnSpeed := ReadFloat;
  334. FRollSpeed := ReadFloat;
  335. FPitchSpeed := ReadFloat;
  336. FDampingEnabled := ReadBoolean;
  337. FTranslationDamping.ReadFromFiler(reader);
  338. FRotationDamping.ReadFromFiler(reader);
  339. end;
  340. end;
  341. procedure TGLBInertia.SetTranslationSpeed(const val: TGLCoordinates);
  342. begin
  343. FTranslationSpeed.Assign(val);
  344. end;
  345. procedure TGLBInertia.SetTranslationDamping(const val: TGLDamping);
  346. begin
  347. FTranslationDamping.Assign(val);
  348. end;
  349. procedure TGLBInertia.SetRotationDamping(const val: TGLDamping);
  350. begin
  351. FRotationDamping.Assign(val);
  352. end;
  353. class function TGLBInertia.FriendlyName: string;
  354. begin
  355. Result := 'Simple Inertia';
  356. end;
  357. class function TGLBInertia.FriendlyDescription: string;
  358. begin
  359. Result := 'A simple translation and rotation inertia';
  360. end;
  361. class function TGLBInertia.UniqueItem: boolean;
  362. begin
  363. Result := True;
  364. end;
  365. procedure TGLBInertia.DoProgress(const progressTime: TGLProgressTimes);
  366. var
  367. trnVector: TGLVector;
  368. speed, newSpeed: double;
  369. procedure ApplyRotationDamping(var rotationSpeed: single);
  370. begin
  371. if rotationSpeed > 0 then
  372. begin
  373. rotationSpeed := RotationDamping.Calculate(rotationSpeed, progressTime.deltaTime);
  374. if rotationSpeed <= 0 then
  375. rotationSpeed := 0;
  376. end
  377. else
  378. begin
  379. rotationSpeed := -RotationDamping.Calculate(-rotationSpeed, progressTime.deltaTime);
  380. if rotationSpeed >= 0 then
  381. rotationSpeed := 0;
  382. end;
  383. end;
  384. begin
  385. // Apply damping to speed
  386. if DampingEnabled then
  387. begin
  388. // Translation damping
  389. speed := TranslationSpeed.VectorLength;
  390. if speed > 0 then
  391. begin
  392. newSpeed := TranslationDamping.Calculate(speed, progressTime.deltaTime);
  393. if newSpeed <= 0 then
  394. begin
  395. trnVector := NullHmgVector;
  396. TranslationSpeed.AsVector := trnVector;
  397. end
  398. else
  399. begin
  400. TranslationSpeed.Scale(newSpeed / Speed);
  401. SetVector(trnVector, TranslationSpeed.AsVector);
  402. end;
  403. end
  404. else
  405. SetVector(trnVector, NullHmgVector);
  406. // Rotation damping (yuck!)
  407. ApplyRotationDamping(FTurnSpeed);
  408. ApplyRotationDamping(FRollSpeed);
  409. ApplyRotationDamping(FPitchSpeed);
  410. end
  411. else
  412. SetVector(trnVector, TranslationSpeed.AsVector);
  413. // Apply speed to object
  414. with OwnerBaseSceneObject do
  415. with progressTime do
  416. begin
  417. Position.AddScaledVector(deltaTime, trnVector);
  418. TurnAngle := TurnAngle + TurnSpeed * deltaTime;
  419. RollAngle := RollAngle + RollSpeed * deltaTime;
  420. PitchAngle := PitchAngle + PitchSpeed * deltaTime;
  421. end;
  422. end;
  423. procedure TGLBInertia.ApplyTranslationAcceleration(const deltaTime: double;
  424. const accel: TGLVector);
  425. begin
  426. FTranslationSpeed.AsVector := VectorCombine(FTranslationSpeed.AsVector,
  427. accel, 1, deltaTime);
  428. end;
  429. procedure TGLBInertia.ApplyForce(const deltaTime: double; const force: TGLVector);
  430. begin
  431. if Mass <> 0 then
  432. FTranslationSpeed.AsVector :=
  433. VectorCombine(FTranslationSpeed.AsVector, force, 1, deltaTime / Mass);
  434. end;
  435. procedure TGLBInertia.ApplyTorque(const deltaTime: double;
  436. const turnTorque, rollTorque, pitchTorque: single);
  437. var
  438. factor: double;
  439. begin
  440. if Mass <> 0 then
  441. begin
  442. factor := deltaTime / Mass;
  443. FTurnSpeed := FTurnSpeed + turnTorque * factor;
  444. FRollSpeed := FRollSpeed + rollTorque * factor;
  445. FPitchSpeed := FPitchSpeed + pitchTorque * factor;
  446. end;
  447. end;
  448. procedure TGLBInertia.MirrorTranslation;
  449. begin
  450. FTranslationSpeed.Invert;
  451. end;
  452. procedure TGLBInertia.SurfaceBounce(const surfaceNormal: TGLVector; restitution: single);
  453. var
  454. f: single;
  455. begin
  456. // does the current speed vector comply?
  457. f := VectorDotProduct(FTranslationSpeed.AsVector, surfaceNormal);
  458. if f < 0 then
  459. begin
  460. // remove the non-complying part of the speed vector
  461. FTranslationSpeed.AddScaledVector(-f / VectorNorm(surfaceNormal) *
  462. (1 + restitution), surfaceNormal);
  463. end;
  464. end;
  465. // ------------------
  466. // ------------------ TGLBAcceleration ------------------
  467. // ------------------
  468. constructor TGLBAcceleration.Create(aOwner: TXCollection);
  469. begin
  470. inherited;
  471. if aOwner <> nil then
  472. if not (csReading in TComponent(aOwner.Owner).ComponentState) then
  473. GetOrCreateInertia(TGLBehaviours(aOwner));
  474. FAcceleration := TGLCoordinates.CreateInitialized(Self, NullHmgVector, csVector);
  475. end;
  476. destructor TGLBAcceleration.Destroy;
  477. begin
  478. inherited;
  479. FAcceleration.Free;
  480. end;
  481. procedure TGLBAcceleration.Assign(Source: TPersistent);
  482. begin
  483. if Source.ClassType = Self.ClassType then
  484. begin
  485. FAcceleration.Assign(TGLBAcceleration(Source).FAcceleration);
  486. end;
  487. inherited Assign(Source);
  488. end;
  489. procedure TGLBAcceleration.WriteToFiler(writer: TWriter);
  490. begin
  491. inherited;
  492. with writer do
  493. begin
  494. WriteInteger(0); // Archive Version 0
  495. FAcceleration.WriteToFiler(writer);
  496. end;
  497. end;
  498. procedure TGLBAcceleration.ReadFromFiler(reader: TReader);
  499. begin
  500. inherited;
  501. with reader do
  502. begin
  503. ReadInteger; // ignore archiveVersion
  504. FAcceleration.ReadFromFiler(reader);
  505. end;
  506. end;
  507. procedure TGLBAcceleration.SetAcceleration(const val: TGLCoordinates);
  508. begin
  509. FAcceleration.Assign(val);
  510. end;
  511. class function TGLBAcceleration.FriendlyName: string;
  512. begin
  513. Result := 'Simple Acceleration';
  514. end;
  515. class function TGLBAcceleration.FriendlyDescription: string;
  516. begin
  517. Result := 'A simple and constant acceleration';
  518. end;
  519. class function TGLBAcceleration.UniqueItem: boolean;
  520. begin
  521. Result := False;
  522. end;
  523. procedure TGLBAcceleration.DoProgress(const progressTime: TGLProgressTimes);
  524. var
  525. i: integer;
  526. Inertia: TGLBInertia;
  527. begin
  528. i := Owner.IndexOfClass(TGLBInertia);
  529. if i >= 0 then
  530. begin
  531. Inertia := TGLBInertia(Owner[i]);
  532. Inertia.ApplyTranslationAcceleration(progressTime.deltaTime,
  533. FAcceleration.DirectVector);
  534. end
  535. else
  536. begin
  537. TGLBInertia.Create(Owner);
  538. //on next progress event this exception won't be raised, because TGLBInertia will be created again
  539. raise Exception.Create(ClassName + ' requires ' + TGLBInertia.ClassName +
  540. '! (' + TGLBInertia.ClassName + ' was added to the Behaviours again)');
  541. end;
  542. end;
  543. initialization // ------------------------------------------------------------
  544. // class registrations
  545. RegisterXCollectionItemClass(TGLBInertia);
  546. RegisterXCollectionItemClass(TGLBAcceleration);
  547. finalization // --------------------------------------------------------------
  548. UnregisterXCollectionItemClass(TGLBInertia);
  549. UnregisterXCollectionItemClass(TGLBAcceleration);
  550. end.