123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 |
- //
- // The graphics engine GLXEngine. The unit of GXScene for Delphi
- //
- unit GXS.Particles;
- (* Particle systems based on replication of full-featured scene objects *)
- interface
- {$I Stage.Defines.inc}
- uses
- Winapi.OpenGL,
- System.Classes,
- System.SysUtils,
- GXS.XCollection,
- GXS.BaseClasses,
- GXS.PersistentClasses,
- Stage.VectorGeometry,
- GXS.Scene,
- GXS.Context,
- GXS.Color,
- GXS.RenderContextInfo,
- GXS.State;
- type
- TgxParticleEvent = procedure(Sender: TObject; particle: TgxBaseSceneObject) of object;
- (* Manager object of a particle system.
- Particles in a TgxParticles system are described as normal scene objects,
- however their children are to be :
- "particle template" : the first object (index=0), this one will be
- duplicated to create new particles, it does not receive progression
- events and is visible at design-time only.
- "live particle" : the other objects (index>0), this ones are rendered
- and receive progression events.
- TgxParticles may also maintain an internal, non-persistent
- ("freezed") set of objects : the allocated objects pool. Why ? Creating
- and freeing objects takes cpu-cycles, especially for the TComponent class,
- and objects are TComponent. To reduce this load (and at the expense
- of memory space), the particle systems can move "dead" particles to a pool
- instead of freeing them, and will pick in the pool instead of creating
- new objects when new particles are requested. To take advantage of this
- behaviour, you should set the ParticlePoolSize property to a non-null
- value and use the KillParticle function instead of "Free" to kill a particle.
- All direct access to a TgxParticles children should be avoided.
- For high-performance particle systems of basic particles, you should
- look into GXS.ParticleFX instead, TgxParticles being rather focused on
- complex particles. *)
- TgxParticles = class(TgxImmaterialSceneObject)
- private
- FCubeSize: Single;
- FEdgeColor: TgxColor;
- FVisibleAtRunTime: Boolean;
- particlePool: TList;
- FParticlePoolSize: Integer;
- FOnCreateParticle: TgxParticleEvent;
- FOnActivateParticle: TgxParticleEvent;
- FOnKillParticle: TgxParticleEvent;
- FOnDestroyParticle: TgxParticleEvent;
- FOnBeforeRenderParticles, FOnAfterRenderParticles: TDirectRenderEvent;
- protected
- procedure SetCubeSize(const val: Single);
- procedure SetEdgeColor(const val: TgxColor);
- procedure SetVisibleAtRunTime(const val: Boolean);
- procedure SetParticlePoolSize(val: Integer);
- procedure ClearParticlePool;
- public
- constructor Create(AOwner: TComponent); override;
- destructor Destroy; override;
- procedure Assign(Source: TPersistent); override;
- procedure BuildList(var ARci: TgxRenderContextInfo); override;
- procedure DoRender(var ARci: TgxRenderContextInfo;
- ARenderSelf, ARenderChildren: Boolean); override;
- procedure DoProgress(const progressTime: TgxProgressTimes); override;
- (* Request creation of a new particle.
- Particle will be either created or retrieved from the particlePool. *)
- function CreateParticle: TgxBaseSceneObject;
- (* Kill given particle.
- If particlePool is not full, particle will be sent to the pool,
- if not, it will be freed. *)
- procedure KillParticle(aParticle: TgxBaseSceneObject);
- // Kill all particles.
- procedure KillParticles;
- published
- property CubeSize: Single read FCubeSize write SetCubeSize;
- property EdgeColor: TgxColor read FEdgeColor write SetEdgeColor;
- property VisibleAtRunTime: Boolean read FVisibleAtRunTime write SetVisibleAtRunTime default False;
- (* Size of the particle pool (for storing killed particles).
- Default size is zero, meaning the particlePool is disabled. *)
- property ParticlePoolSize: Integer read FParticlePoolSize write SetParticlePoolSize default 0;
- (* Fired a particle has been created as a template duplicate.
- When the event is triggered, the particle has yet been added to the scene. *)
- property OnCreateParticle: TgxParticleEvent read FOnCreateParticle write FOnCreateParticle;
- (* Fired when a particle will get in the "live" list.
- The particle has just been "Assigned" with the template, may happen
- after a creation or a pick from the particle pool. *)
- property OnActivateParticle: TgxParticleEvent read FOnActivateParticle write FOnActivateParticle;
- (* Triggered when a particle is killed.
- When the event is fired, the particle is still parented, after this
- event, the particle will either go to the pool or be destroyed if the pool is full. *)
- property OnKillParticle: TgxParticleEvent read FOnKillParticle write FOnKillParticle;
- (* Triggered just before destroying a particle.
- The particle can be in the pool (ie. not parented). *)
- property OnDestroyParticle: TgxParticleEvent read FOnDestroyParticle write FOnDestroyParticle;
- // Fired before rendering the first of the particles.
- property OnBeforeRenderParticles: TDirectRenderEvent read FOnBeforeRenderParticles write FOnBeforeRenderParticles;
- // Fired after rendering the last of the particles.
- property OnAfterRenderParticles: TDirectRenderEvent read FOnAfterRenderParticles write FOnAfterRenderParticles;
- end;
- implementation //-------------------------------------------------------------
- //----------------- TgxParticles --------------------------------------
- //---------------------------------------------------------------------
- constructor TgxParticles.Create(AOwner: TComponent);
- begin
- inherited;
- ObjectStyle := ObjectStyle + [osDirectDraw, osNoVisibilityCulling];
- FCubeSize := 1;
- FEdgeColor := TgxColor.Create(Self);
- FEdgeColor.Initialize(clrWhite);
- particlePool := TList.Create;
- end;
- destructor TgxParticles.Destroy;
- begin
- FEdgeColor.Free;
- ClearParticlePool;
- particlePool.Free;
- inherited;
- end;
- procedure TgxParticles.Assign(Source: TPersistent);
- begin
- if Source is TgxParticles then
- begin
- FCubeSize := TgxParticles(Source).FCubeSize;
- FEdgeColor.Color := TgxParticles(Source).FEdgeColor.Color;
- FVisibleAtRunTime := TgxParticles(Source).FVisibleAtRunTime;
- ClearParticlePool;
- FParticlePoolSize := TgxParticles(Source).FParticlePoolSize;
- FOnCreateParticle := TgxParticles(Source).FOnCreateParticle;
- FOnActivateParticle := TgxParticles(Source).FOnActivateParticle;
- FOnKillParticle := TgxParticles(Source).FOnKillParticle;
- FOnDestroyParticle := TgxParticles(Source).FOnDestroyParticle;
- end;
- inherited Assign(Source);
- end;
- procedure TgxParticles.ClearParticlePool;
- var
- particle: TgxBaseSceneObject;
- i: Integer;
- begin
- if Assigned(FOnDestroyParticle) then
- begin
- for i := 0 to particlePool.Count - 1 do
- begin
- particle := TgxBaseSceneObject(particlePool[i]);
- FOnDestroyParticle(Self, particle);
- particle.Free;
- end;
- end
- else
- for i := 0 to particlePool.Count - 1 do
- TgxBaseSceneObject(particlePool[i]).Free;
- particlePool.Clear;
- end;
- procedure TgxParticles.BuildList(var ARci: TgxRenderContextInfo);
- var
- mi, ma: Single;
- begin
- ARci.gxStates.Disable(stLighting);
- ARci.gxStates.Enable(stLineStipple);
- ARci.gxStates.Enable(stLineSmooth);
- ARci.gxStates.Enable(stBlend);
- ARci.gxStates.SetBlendFunc(bfSrcAlpha, bfOneMinusSrcAlpha);
- ARci.gxStates.LineWidth := 1;
- ARci.gxStates.LineStippleFactor := 1;
- ARci.gxStates.LineStipplePattern := $AAAA;
- ma := FCubeSize * 0.5;
- mi := -ma;
- with EdgeColor do
- glColor3f(Color.X, Color.Y, Color.Z);
- glBegin(GL_LINE_STRIP);
- // front face
- glVertex3f(ma, mi, mi);
- glVertex3f(ma, ma, mi);
- glVertex3f(ma, ma, ma);
- glVertex3f(ma, mi, ma);
- glVertex3f(ma, mi, mi);
- // partial up back fac
- glVertex3f(mi, mi, mi);
- glVertex3f(mi, mi, ma);
- glVertex3f(mi, ma, ma);
- glVertex3f(mi, ma, mi);
- // right side low
- glVertex3f(ma, ma, mi);
- glEnd;
- glBegin(GL_LINES);
- // right high
- glVertex3f(ma, ma, ma);
- glVertex3f(mi, ma, ma);
- // back low
- glVertex3f(mi, mi, mi);
- glVertex3f(mi, ma, mi);
- // left high
- glVertex3f(ma, mi, ma);
- glVertex3f(mi, mi, ma);
- glEnd;
- end;
- procedure TgxParticles.DoRender(var ARci: TgxRenderContextInfo;
- ARenderSelf, ARenderChildren: Boolean);
- begin
- if (csDesigning in ComponentState) or (FVisibleAtRunTime) then
- BuildList(ARci);
- if Assigned(FOnBeforeRenderParticles) then
- FOnBeforeRenderParticles(Self, ARci);
- if csDesigning in ComponentState then
- begin
- // design-time, everything is visible for user convenience
- if Count > 0 then
- Self.RenderChildren(0, Count - 1, ARci);
- end
- else
- begin
- // run-time, template is NOT visible
- if Count > 1 then
- Self.RenderChildren(1, Count - 1, ARci);
- end;
- if Assigned(FOnAfterRenderParticles) then
- FOnAfterRenderParticles(Self, ARci);
- end;
- procedure TgxParticles.DoProgress(const progressTime: TgxProgressTimes);
- var
- i: Integer;
- begin
- for i := Count - 1 downto 1 do
- Children[i].DoProgress(progressTime);
- Behaviours.DoProgress(progressTime);
- if Assigned(OnProgress) then
- with progressTime do
- OnProgress(Self, deltaTime, newTime);
- end;
- procedure TgxParticles.SetCubeSize(const val: Single);
- begin
- if val <> FCubeSize then
- begin
- FCubeSize := val;
- StructureChanged;
- end;
- end;
- procedure TgxParticles.SetEdgeColor(const val: TgxColor);
- begin
- if val <> FEdgeColor then
- begin
- FEdgeColor.Assign(val);
- StructureChanged;
- end;
- end;
- procedure TgxParticles.SetVisibleAtRunTime(const val: Boolean);
- begin
- if val <> FVisibleAtRunTime then
- begin
- FVisibleAtRunTime := val;
- StructureChanged;
- end;
- end;
- procedure TgxParticles.SetParticlePoolSize(val: Integer);
- var
- particle: TgxBaseSceneObject;
- begin
- if val < 0 then
- val := 0;
- if FParticlePoolSize <> val then
- begin
- FParticlePoolSize := val;
- with particlePool do
- while Count > val do
- begin
- particle := TgxBaseSceneObject(Items[Count - 1]);
- if Assigned(FOnDestroyParticle) then
- FOnDestroyParticle(Self, particle);
- particle.Free;
- Delete(Count - 1);
- end;
- end;
- end;
- function TgxParticles.CreateParticle: TgxBaseSceneObject;
- begin
- if Count > 0 then
- begin
- if particlePool.Count > 0 then
- begin
- Result := TgxBaseSceneObject(particlePool[particlePool.Count - 1]);
- particlePool.Delete(particlePool.Count - 1);
- Result.Assign(Children[0]);
- end
- else
- begin
- Result := TgxSceneObjectClass(Children[0].ClassType).Create(Self);
- Result.Assign(Children[0]);
- if Assigned(FOnCreateParticle) then
- FOnCreateParticle(Self, Result);
- end;
- AddChild(Result);
- if Assigned(FOnActivateParticle) then
- FOnActivateParticle(Self, Result);
- end
- else
- Result := nil;
- end;
- procedure TgxParticles.KillParticle(aParticle: TgxBaseSceneObject);
- begin
- Assert(aParticle.Parent = Self, 'KillParticle : particle is not mine !');
- if Assigned(FOnKillParticle) then
- FOnKillParticle(Self, aParticle);
- if particlePool.Count < FParticlePoolSize then
- begin
- Remove(aParticle, False);
- particlePool.Add(aParticle)
- end
- else
- begin
- if Assigned(FOnDestroyParticle) then
- FOnDestroyParticle(Self, aParticle);
- aParticle.Free;
- end;
- end;
- procedure TgxParticles.KillParticles;
- begin
- while Count > 1 do
- KillParticle(Children[Count - 1]);
- end;
- initialization // ------------------------------------------------------------
- RegisterClass(TgxParticles);
- end.
|