GXS.Particles.pas 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. //
  2. // The graphics engine GLXEngine. The unit of GXScene for Delphi
  3. //
  4. unit GXS.Particles;
  5. (* Particle systems based on replication of full-featured scene objects *)
  6. interface
  7. {$I Stage.Defines.inc}
  8. uses
  9. Winapi.OpenGL,
  10. System.Classes,
  11. System.SysUtils,
  12. GXS.XCollection,
  13. GXS.BaseClasses,
  14. GXS.PersistentClasses,
  15. Stage.VectorGeometry,
  16. GXS.Scene,
  17. GXS.Context,
  18. GXS.Color,
  19. GXS.RenderContextInfo,
  20. GXS.State;
  21. type
  22. TgxParticleEvent = procedure(Sender: TObject; particle: TgxBaseSceneObject) of object;
  23. (* Manager object of a particle system.
  24. Particles in a TgxParticles system are described as normal scene objects,
  25. however their children are to be :
  26. "particle template" : the first object (index=0), this one will be
  27. duplicated to create new particles, it does not receive progression
  28. events and is visible at design-time only.
  29. "live particle" : the other objects (index>0), this ones are rendered
  30. and receive progression events.
  31. TgxParticles may also maintain an internal, non-persistent
  32. ("freezed") set of objects : the allocated objects pool. Why ? Creating
  33. and freeing objects takes cpu-cycles, especially for the TComponent class,
  34. and objects are TComponent. To reduce this load (and at the expense
  35. of memory space), the particle systems can move "dead" particles to a pool
  36. instead of freeing them, and will pick in the pool instead of creating
  37. new objects when new particles are requested. To take advantage of this
  38. behaviour, you should set the ParticlePoolSize property to a non-null
  39. value and use the KillParticle function instead of "Free" to kill a particle.
  40. All direct access to a TgxParticles children should be avoided.
  41. For high-performance particle systems of basic particles, you should
  42. look into GXS.ParticleFX instead, TgxParticles being rather focused on
  43. complex particles. *)
  44. TgxParticles = class(TgxImmaterialSceneObject)
  45. private
  46. FCubeSize: Single;
  47. FEdgeColor: TgxColor;
  48. FVisibleAtRunTime: Boolean;
  49. particlePool: TList;
  50. FParticlePoolSize: Integer;
  51. FOnCreateParticle: TgxParticleEvent;
  52. FOnActivateParticle: TgxParticleEvent;
  53. FOnKillParticle: TgxParticleEvent;
  54. FOnDestroyParticle: TgxParticleEvent;
  55. FOnBeforeRenderParticles, FOnAfterRenderParticles: TDirectRenderEvent;
  56. protected
  57. procedure SetCubeSize(const val: Single);
  58. procedure SetEdgeColor(const val: TgxColor);
  59. procedure SetVisibleAtRunTime(const val: Boolean);
  60. procedure SetParticlePoolSize(val: Integer);
  61. procedure ClearParticlePool;
  62. public
  63. constructor Create(AOwner: TComponent); override;
  64. destructor Destroy; override;
  65. procedure Assign(Source: TPersistent); override;
  66. procedure BuildList(var ARci: TgxRenderContextInfo); override;
  67. procedure DoRender(var ARci: TgxRenderContextInfo;
  68. ARenderSelf, ARenderChildren: Boolean); override;
  69. procedure DoProgress(const progressTime: TgxProgressTimes); override;
  70. (* Request creation of a new particle.
  71. Particle will be either created or retrieved from the particlePool. *)
  72. function CreateParticle: TgxBaseSceneObject;
  73. (* Kill given particle.
  74. If particlePool is not full, particle will be sent to the pool,
  75. if not, it will be freed. *)
  76. procedure KillParticle(aParticle: TgxBaseSceneObject);
  77. // Kill all particles.
  78. procedure KillParticles;
  79. published
  80. property CubeSize: Single read FCubeSize write SetCubeSize;
  81. property EdgeColor: TgxColor read FEdgeColor write SetEdgeColor;
  82. property VisibleAtRunTime: Boolean read FVisibleAtRunTime write SetVisibleAtRunTime default False;
  83. (* Size of the particle pool (for storing killed particles).
  84. Default size is zero, meaning the particlePool is disabled. *)
  85. property ParticlePoolSize: Integer read FParticlePoolSize write SetParticlePoolSize default 0;
  86. (* Fired a particle has been created as a template duplicate.
  87. When the event is triggered, the particle has yet been added to the scene. *)
  88. property OnCreateParticle: TgxParticleEvent read FOnCreateParticle write FOnCreateParticle;
  89. (* Fired when a particle will get in the "live" list.
  90. The particle has just been "Assigned" with the template, may happen
  91. after a creation or a pick from the particle pool. *)
  92. property OnActivateParticle: TgxParticleEvent read FOnActivateParticle write FOnActivateParticle;
  93. (* Triggered when a particle is killed.
  94. When the event is fired, the particle is still parented, after this
  95. event, the particle will either go to the pool or be destroyed if the pool is full. *)
  96. property OnKillParticle: TgxParticleEvent read FOnKillParticle write FOnKillParticle;
  97. (* Triggered just before destroying a particle.
  98. The particle can be in the pool (ie. not parented). *)
  99. property OnDestroyParticle: TgxParticleEvent read FOnDestroyParticle write FOnDestroyParticle;
  100. // Fired before rendering the first of the particles.
  101. property OnBeforeRenderParticles: TDirectRenderEvent read FOnBeforeRenderParticles write FOnBeforeRenderParticles;
  102. // Fired after rendering the last of the particles.
  103. property OnAfterRenderParticles: TDirectRenderEvent read FOnAfterRenderParticles write FOnAfterRenderParticles;
  104. end;
  105. implementation //-------------------------------------------------------------
  106. //----------------- TgxParticles --------------------------------------
  107. //---------------------------------------------------------------------
  108. constructor TgxParticles.Create(AOwner: TComponent);
  109. begin
  110. inherited;
  111. ObjectStyle := ObjectStyle + [osDirectDraw, osNoVisibilityCulling];
  112. FCubeSize := 1;
  113. FEdgeColor := TgxColor.Create(Self);
  114. FEdgeColor.Initialize(clrWhite);
  115. particlePool := TList.Create;
  116. end;
  117. destructor TgxParticles.Destroy;
  118. begin
  119. FEdgeColor.Free;
  120. ClearParticlePool;
  121. particlePool.Free;
  122. inherited;
  123. end;
  124. procedure TgxParticles.Assign(Source: TPersistent);
  125. begin
  126. if Source is TgxParticles then
  127. begin
  128. FCubeSize := TgxParticles(Source).FCubeSize;
  129. FEdgeColor.Color := TgxParticles(Source).FEdgeColor.Color;
  130. FVisibleAtRunTime := TgxParticles(Source).FVisibleAtRunTime;
  131. ClearParticlePool;
  132. FParticlePoolSize := TgxParticles(Source).FParticlePoolSize;
  133. FOnCreateParticle := TgxParticles(Source).FOnCreateParticle;
  134. FOnActivateParticle := TgxParticles(Source).FOnActivateParticle;
  135. FOnKillParticle := TgxParticles(Source).FOnKillParticle;
  136. FOnDestroyParticle := TgxParticles(Source).FOnDestroyParticle;
  137. end;
  138. inherited Assign(Source);
  139. end;
  140. procedure TgxParticles.ClearParticlePool;
  141. var
  142. particle: TgxBaseSceneObject;
  143. i: Integer;
  144. begin
  145. if Assigned(FOnDestroyParticle) then
  146. begin
  147. for i := 0 to particlePool.Count - 1 do
  148. begin
  149. particle := TgxBaseSceneObject(particlePool[i]);
  150. FOnDestroyParticle(Self, particle);
  151. particle.Free;
  152. end;
  153. end
  154. else
  155. for i := 0 to particlePool.Count - 1 do
  156. TgxBaseSceneObject(particlePool[i]).Free;
  157. particlePool.Clear;
  158. end;
  159. procedure TgxParticles.BuildList(var ARci: TgxRenderContextInfo);
  160. var
  161. mi, ma: Single;
  162. begin
  163. ARci.gxStates.Disable(stLighting);
  164. ARci.gxStates.Enable(stLineStipple);
  165. ARci.gxStates.Enable(stLineSmooth);
  166. ARci.gxStates.Enable(stBlend);
  167. ARci.gxStates.SetBlendFunc(bfSrcAlpha, bfOneMinusSrcAlpha);
  168. ARci.gxStates.LineWidth := 1;
  169. ARci.gxStates.LineStippleFactor := 1;
  170. ARci.gxStates.LineStipplePattern := $AAAA;
  171. ma := FCubeSize * 0.5;
  172. mi := -ma;
  173. with EdgeColor do
  174. glColor3f(Color.X, Color.Y, Color.Z);
  175. glBegin(GL_LINE_STRIP);
  176. // front face
  177. glVertex3f(ma, mi, mi);
  178. glVertex3f(ma, ma, mi);
  179. glVertex3f(ma, ma, ma);
  180. glVertex3f(ma, mi, ma);
  181. glVertex3f(ma, mi, mi);
  182. // partial up back fac
  183. glVertex3f(mi, mi, mi);
  184. glVertex3f(mi, mi, ma);
  185. glVertex3f(mi, ma, ma);
  186. glVertex3f(mi, ma, mi);
  187. // right side low
  188. glVertex3f(ma, ma, mi);
  189. glEnd;
  190. glBegin(GL_LINES);
  191. // right high
  192. glVertex3f(ma, ma, ma);
  193. glVertex3f(mi, ma, ma);
  194. // back low
  195. glVertex3f(mi, mi, mi);
  196. glVertex3f(mi, ma, mi);
  197. // left high
  198. glVertex3f(ma, mi, ma);
  199. glVertex3f(mi, mi, ma);
  200. glEnd;
  201. end;
  202. procedure TgxParticles.DoRender(var ARci: TgxRenderContextInfo;
  203. ARenderSelf, ARenderChildren: Boolean);
  204. begin
  205. if (csDesigning in ComponentState) or (FVisibleAtRunTime) then
  206. BuildList(ARci);
  207. if Assigned(FOnBeforeRenderParticles) then
  208. FOnBeforeRenderParticles(Self, ARci);
  209. if csDesigning in ComponentState then
  210. begin
  211. // design-time, everything is visible for user convenience
  212. if Count > 0 then
  213. Self.RenderChildren(0, Count - 1, ARci);
  214. end
  215. else
  216. begin
  217. // run-time, template is NOT visible
  218. if Count > 1 then
  219. Self.RenderChildren(1, Count - 1, ARci);
  220. end;
  221. if Assigned(FOnAfterRenderParticles) then
  222. FOnAfterRenderParticles(Self, ARci);
  223. end;
  224. procedure TgxParticles.DoProgress(const progressTime: TgxProgressTimes);
  225. var
  226. i: Integer;
  227. begin
  228. for i := Count - 1 downto 1 do
  229. Children[i].DoProgress(progressTime);
  230. Behaviours.DoProgress(progressTime);
  231. if Assigned(OnProgress) then
  232. with progressTime do
  233. OnProgress(Self, deltaTime, newTime);
  234. end;
  235. procedure TgxParticles.SetCubeSize(const val: Single);
  236. begin
  237. if val <> FCubeSize then
  238. begin
  239. FCubeSize := val;
  240. StructureChanged;
  241. end;
  242. end;
  243. procedure TgxParticles.SetEdgeColor(const val: TgxColor);
  244. begin
  245. if val <> FEdgeColor then
  246. begin
  247. FEdgeColor.Assign(val);
  248. StructureChanged;
  249. end;
  250. end;
  251. procedure TgxParticles.SetVisibleAtRunTime(const val: Boolean);
  252. begin
  253. if val <> FVisibleAtRunTime then
  254. begin
  255. FVisibleAtRunTime := val;
  256. StructureChanged;
  257. end;
  258. end;
  259. procedure TgxParticles.SetParticlePoolSize(val: Integer);
  260. var
  261. particle: TgxBaseSceneObject;
  262. begin
  263. if val < 0 then
  264. val := 0;
  265. if FParticlePoolSize <> val then
  266. begin
  267. FParticlePoolSize := val;
  268. with particlePool do
  269. while Count > val do
  270. begin
  271. particle := TgxBaseSceneObject(Items[Count - 1]);
  272. if Assigned(FOnDestroyParticle) then
  273. FOnDestroyParticle(Self, particle);
  274. particle.Free;
  275. Delete(Count - 1);
  276. end;
  277. end;
  278. end;
  279. function TgxParticles.CreateParticle: TgxBaseSceneObject;
  280. begin
  281. if Count > 0 then
  282. begin
  283. if particlePool.Count > 0 then
  284. begin
  285. Result := TgxBaseSceneObject(particlePool[particlePool.Count - 1]);
  286. particlePool.Delete(particlePool.Count - 1);
  287. Result.Assign(Children[0]);
  288. end
  289. else
  290. begin
  291. Result := TgxSceneObjectClass(Children[0].ClassType).Create(Self);
  292. Result.Assign(Children[0]);
  293. if Assigned(FOnCreateParticle) then
  294. FOnCreateParticle(Self, Result);
  295. end;
  296. AddChild(Result);
  297. if Assigned(FOnActivateParticle) then
  298. FOnActivateParticle(Self, Result);
  299. end
  300. else
  301. Result := nil;
  302. end;
  303. procedure TgxParticles.KillParticle(aParticle: TgxBaseSceneObject);
  304. begin
  305. Assert(aParticle.Parent = Self, 'KillParticle : particle is not mine !');
  306. if Assigned(FOnKillParticle) then
  307. FOnKillParticle(Self, aParticle);
  308. if particlePool.Count < FParticlePoolSize then
  309. begin
  310. Remove(aParticle, False);
  311. particlePool.Add(aParticle)
  312. end
  313. else
  314. begin
  315. if Assigned(FOnDestroyParticle) then
  316. FOnDestroyParticle(Self, aParticle);
  317. aParticle.Free;
  318. end;
  319. end;
  320. procedure TgxParticles.KillParticles;
  321. begin
  322. while Count > 1 do
  323. KillParticle(Children[Count - 1]);
  324. end;
  325. initialization // ------------------------------------------------------------
  326. RegisterClass(TgxParticles);
  327. end.