GLS.Particles.pas 11 KB

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