GXS.Behaviours.pas 18 KB

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