www.pudn.com > XParticles.rar > XParticles.pas
unit XParticles;
//---------------------------------------------------------------------------
// XParticles.pas
// XParticles System Version 1.1
//---------------------------------------------------------------------------
// The contents of this file are subject to the Mozilla Public License
// Version 1.1 (the "License"); you may not use this file except in
// compliance with the License. You may obtain a copy of the License at
// http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS"
// basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
// License for the specific language governing rights and limitations
// under the License.
//---------------------------------------------------------------------------
interface
uses Classes, Types, AsphyreDef, AsphyreImages, AsphyreCanvas, Vectors2, Math,
SmoothColorUnit;
//------------------------------------------------------------------------------
const
MAX_PARTICLES = 5000;
MAX_PSYSTEMS = 100;
PI_2 = 1.57079632;
//------------------------------------------------------------------------------
type
TPatternChangeType = (prFixed, ptInc, ptDec);
//------------------------------------------------------------------------------
TXParticleSprite = record
Pattern: integer;
PatternType: TPatternChangeType;
PatternsPerSec: integer;
DrawFx: Cardinal;
end;
//------------------------------------------------------------------------------
PXParticle = ^TXParticle;
TXParticle = record
Location,
Displacement: TPoint2; // Start location displacement
Velocity: TPoint2;
Gravity: Single;
RadialAccel,
TangentialAccel: Single;
Angle,
AngleDelta: Single;
Scale,
ScaleDelta: Single;
Color,
ColorDelta: TSmoothColor;
Age,
TerminalAge: Single;
end;
//------------------------------------------------------------------------------
{TAreaShape = (asRect, asEllipse, asPoint);
//------------------------------------------------------------------------------
TArea = record
Rect: TPoint4;
Shape: TAreaShape;
end;
//------------------------------------------------------------------------------
TSpawnArea = record
Area: TArea;
InnerArea: TArea;
end;}
//------------------------------------------------------------------------------
PXParticleSystemSettings = ^TXParticleSystemSettings;
TXParticleSystemSettings = record
Sprite: TXParticleSprite; // Pointer to particle image
EmissionRate: integer; // Particles per second
LifeTime: integer;
//DrawArea: TArea;
//SpawnArea: TSpawnArea;
ParticleLifeMin,
ParticleLifeMax: integer;
Direction,
Spread: Single;
Relative: boolean;
SpeedMin,
SpeedMax: Single;
GravityMin,
GravityMax: Single;
RadialAccelMin,
RadialAccelMax: Single;
TangentialAccelMin,
TangentialAccelMax: Single;
ScaleStart,
ScaleEnd,
ScaleRnd: Single;
SpinStart,
SpinEnd,
SpinRnd: Single;
ColorStart,
ColorEnd: Cardinal;
ColorRnd,
AlphaRnd: Single;
end;
//------------------------------------------------------------------------------
TEmitters = array of TPoint;
//------------------------------------------------------------------------------
TXParticleSystem = class
private
FTexture: TAsphyreImage;
FParticles: array[0..MAX_PARTICLES-1] of TXParticle;
FSettings: TXParticleSystemSettings;
FEmitters: TEmitters;
FUpdSpeed,
FResidue: Single;
FAge,
FEmissionResidue: Single;
FPrevLocation,
FLocation: TPoint2;
FTx,
FTy: Single;
FParticlesAlive: integer;
function GetTransposition(): TPoint2;
protected
procedure UpdateSys(const DeltaTime: integer);
procedure RenderSprite(const Sprite: TXParticleSprite; const Canvas: TAsphyreCanvas;
const Color: Cardinal; const X, Y, Angle, Scale: Single);
public
constructor Create(const Stream: TStream; const ParticleSprite: TXParticleSprite); overload;
constructor Create(PSS: TXParticleSystemSettings); overload;
property Texture: TAsphyreImage read FTexture write FTexture;
function SettingsLoadFromStream(const Stream: TStream): boolean;
function SettingsSaveFromStream(const Stream: TStream): boolean;
procedure Render(Canvas: TAsphyreCanvas);
procedure StartAt(x, y: Single);
procedure Start();
procedure Stop(KillParticles: boolean = false);
procedure Update(const DeltaTime: integer);
procedure MoveTo(const x, y: Single; MoveParticles: boolean = false);
procedure Transpose(const x, y: Single);
procedure AddParticle(const x, y: integer);
procedure AddEmitters(const Emitters: array of TPoint);
procedure AddEmittersFromImage(const Image: TAsphyreImage; const Color: Cardinal);
procedure RemoveAllEmitters();
procedure ScaleEmitters(Scale: Single);
property Emitters: TEmitters read FEmitters;
property Settings: TXParticleSystemSettings read FSettings write FSettings;
property ParticlesAlive: integer read FParticlesAlive;
property Age: Single read fAge;
property Position: TPoint2 read FLocation;
property Transposition: TPoint2 read GetTransposition;
end;
//------------------------------------------------------------------------------
TXParticleManager = class
private
FCanvas: TAsphyreCanvas;
FCount: integer;
FX,
FY: Single;
FItems: array[0..MAX_PSYSTEMS-1] of TXParticleSystem;
function GetTransposition: TPoint2;
function GetItem(id: cardinal): TXParticleSystem;
function GetParticlesAlive(): integer;
public
constructor Create();
procedure Update(const DeltaTime: integer);
procedure Render();
function SpawnPS(PSS: TXParticleSystemSettings; Texture: TAsphyreImage; X, Y: Single): TXParticleSystem;
function IsPSAlive(PS: TXParticleSystem): boolean;
procedure Transpose(X, Y: Single);
procedure KillPS(PS: TXParticleSystem);
procedure KillAll();
property Items[id: cardinal]: TXParticleSystem read GetItem;
property Count: integer read FCount;
property ParticlesAlive: integer read GetParticlesAlive;
property Transposition: TPoint2 read GetTransposition;
published
property Canvas: TAsphyreCanvas read FCanvas write FCanvas;
end;
//------------------------------------------------------------------------------
var
RRSeed: integer = 0;
function RandomSingle(const Min, Max: Single): Single;
implementation
uses DXTextures, AsphyreDevices;
//------------------------------------------------------------------------------
// Some funtions...
//------------------------------------------------------------------------------
function VecMultSingle(const a: TPoint2; const val: Single): TPoint2;
begin
Result.x:= a.x * val;
Result.y:= a.y * val;
end;
//------------------------------------------------------------------------------
function VecAngle(const v1,v2: TPoint2): Single;
begin
Result:= ArcTan2(v2.y - v1.y, v2.x - v1.x);
end;
//------------------------------------------------------------------------------
function RandomSingle(const Min, Max: Single): Single;
var
Mi, Ma: Single;
begin
Mi:= Min;
Ma:= Max;
if (Min > Max) then
begin
Mi:= Max;
Ma:= Min;
end;
RRSeed:= 214013*RRSeed + 2531011;
Result:= Mi + (RRSeed shr 16) * (1.0 / 65535.0) * (Ma - Mi);
end;
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// TXParticleSystem.
//------------------------------------------------------------------------------
constructor TXParticleSystem.Create(const Stream: TStream;
const ParticleSprite: TXParticleSprite);
begin
if (Stream = nil){or(ParticleSprite = nil)} then
Exit;
Stream.Read(FSettings, SizeOf(TXParticleSystemSettings));
FSettings.Sprite:= ParticleSprite;
FLocation:= Point2(0.0, 0.0);
FPrevLocation:= Point2(0.0, 0.0);
FTx:= 0;
FTy:= 0;
FEmissionResidue:= 0.0;
FParticlesAlive:= 0;
FAge:= -2.0;
FUpdSpeed:= 0.0;
FResidue:= 0.0;
SetLength(FEmitters, 0);
AddEmitters([Point(0, 0)]);
end;
//------------------------------------------------------------------------------
constructor TXParticleSystem.Create(PSS: TXParticleSystemSettings);
begin
FSettings:= PSS;
FLocation:= Point2(0.0, 0.0);
FPrevLocation:= Point2(0.0, 0.0);
FTx:= 0.0;
FTy:= 0.0;
FEmissionResidue:= 0.0;
FParticlesAlive:= 0;
FAge:= -2.0;
FUpdSpeed:= 0.0;
FResidue:= 0.0;
SetLength(FEmitters, 0);
AddEmitters([Point(0, 0)]);
end;
//------------------------------------------------------------------------------
function TXParticleSystem.SettingsLoadFromStream(const Stream: TStream): boolean;
begin
if (Stream <> nil) then
Result:= (Stream.Read(FSettings, SizeOf(TXParticleSystemSettings)) > 0)
else
Result:= false;
end;
//------------------------------------------------------------------------------
function TXParticleSystem.SettingsSaveFromStream(const Stream: TStream): boolean;
begin
if (Stream <> nil) then
Result:= (Stream.Write(FSettings, SizeOf(TXParticleSystemSettings)) > 0)
else
Result:= false;
end;
//------------------------------------------------------------------------------
procedure TXParticleSystem.Render(Canvas: TAsphyreCanvas);
var
i: integer;
Par: PXParticle;
begin
if (FParticlesAlive <= 0) then
Exit;
for i:= 0 to FParticlesAlive-1 do
begin
if (i > MAX_PARTICLES-1) then
Break;
Par:= @FParticles[i];
//FSettings.Sprite.
RenderSprite(FSettings.Sprite, Canvas, FromSmoothColor(Par.Color), Par.Location.x + FTx, Par.Location.y + FTy,
Par.Angle{ * Par.Age}, Par.Scale);
end;
end;
//------------------------------------------------------------------------------
procedure TXParticleSystem.RenderSprite(const Sprite: TXParticleSprite; const Canvas: TAsphyreCanvas;
const Color: Cardinal; const X, Y, Angle, Scale: Single);
var
Tex: TAsphyreImage;
begin
Tex:= FTexture;
if (Tex = nil)or
(X + (Tex.PatternSize.X div 2) < 0)or
(Y + (Tex.PatternSize.Y div 2) < 0)or
(X - (Tex.PatternSize.X div 2) > Canvas.Device.Width)or
(Y - (Tex.PatternSize.Y div 2) > Canvas.Device.Height) then
Exit;
//Canvas.Draw(Tex, X, Y, FPattern, FDrawFx);
Canvas.DrawRot(Tex, X, Y, Angle, Scale, Color, Sprite.Pattern, Sprite.DrawFx);
end;
//------------------------------------------------------------------------------
procedure TXParticleSystem.Update(const DeltaTime: integer);
begin
UpdateSys(DeltaTime);
{if (FUpdSpeed <= 0.00) then
UpdateSys(DeltaTime)
else
begin
FResidue:= FResidue + DeltaTime;
if (fResidue >= FUpdSpeed) then
begin
UpdateSys(DeltaTime);
while(FResidue >= FUpdSpeed) do
FResidue:= FResidue - FUpdSpeed;
end;
end;}
end;
//------------------------------------------------------------------------------
procedure TXParticleSystem.UpdateSys(const DeltaTime: integer);
var
i: integer;
ang, Delta: Single;
Par: PXParticle;
AccelVec, AccelVec2: TPoint2;
ParticlesNeeded: Single;
ParticlesCreated: integer;
begin
Delta:= DeltaTime / 1000;
if (FAge >= 0) then
begin
FAge:= FAge + DeltaTime;
if (fAge >= FSettings.LifeTime) then
FAge:= -2.0;
end;
// Update all ALIVE particles
i:= 0;
while i <= FParticlesAlive-1 do
begin
if (i > MAX_PARTICLES-1) then
Break;
Par:= @FParticles[i];
Inc(i);
Par.Age:= Par.Age + DeltaTime;
if (Par.Age >= Par.TerminalAge) then
begin
Dec(FParticlesAlive);
// Move last particle to the free cell
Par^:= FParticles[FParticlesAlive];
Dec(i);
Continue;
end;
AccelVec:= VecSub2(Par.Location, VecAdd2(FLocation, Par.Displacement));
AccelVec:= VecNorm2(AccelVec);
AccelVec2:= AccelVec;
AccelVec:= VecMultSingle(AccelVec, Par.RadialAccel);
// Rotating...
ang:= AccelVec2.x;
AccelVec2.x:= -AccelVec2.y;
AccelVec2.y:= ang;
AccelVec2:= VecMultSingle(AccelVec2, Par.TangentialAccel);
Par.Velocity:= VecAdd2(Par.Velocity, VecMultSingle(VecAdd2(AccelVec, AccelVec2), DeltaTime));
Par.Velocity.y:= Par.Velocity.y + Par.Gravity * Delta;
Par.Location:= VecAdd2(Par.Location, VecMultSingle(Par.Velocity, Delta));
Par.Angle:= Par.Angle + Par.AngleDelta * DeltaTime;
Par.Scale:= Par.Scale + Par.ScaleDelta * DeltaTime;
//Par.Color:= NormSmoothColor(Par.Color + Par.ColorDelta * DeltaTime);
Par.Color:= NormSmoothColor(AddSmColors(Par.Color, MultSmColor(Par.ColorDelta, DeltaTime)));
end;
// Generate NEW particles
if(FAge > -2.0) then
begin
ParticlesNeeded:= FSettings.EmissionRate * Delta + FEmissionResidue;
ParticlesCreated:= Trunc(ParticlesNeeded);
FEmissionResidue:= ParticlesNeeded - ParticlesCreated;
for i:= 0 to ParticlesCreated-1 do
begin
if (FParticlesAlive >= MAX_PARTICLES) then Break;
AddParticle(0, 0);
end;
end;
FPrevLocation:= FLocation;
end;
//------------------------------------------------------------------------------
procedure TXParticleSystem.AddParticle(const x, y: integer);
var
ang: Single;
Par: PXParticle;
r, g, b, a: Single;
Pnt: TPoint;
begin
if (FParticlesAlive >= MAX_PARTICLES)or(Length(FEmitters) = 0) then Exit;
Par:= @FParticles[FParticlesAlive];
Par.Age:= 0.0;
Par.TerminalAge:= RandomSingle(FSettings.ParticleLifeMin, FSettings.ParticleLifeMax);
// Spawn locations
Par.Location:=
VecAdd2(FPrevLocation, VecMultSingle(VecSub2(FLocation, FPrevLocation), RandomSingle(0.0, 1.0)));
Pnt:= FEmitters[Random(Length(FEmitters))];
Par.Location.x:= Par.Location.x + Pnt.X + RandomSingle(-1.0, 1.0) + x;
Par.Location.y:= Par.Location.y + Pnt.Y + RandomSingle(-1.0, 1.0) + y;
Par.Displacement:= VecSub2(FLocation, Par.Location);
//Particles direction and velocity
ang:= FSettings.Direction - PI_2 + RandomSingle(0.0, FSettings.Spread) - FSettings.Spread / 2.0;
if (FSettings.Relative) then
ang:= ang + VecAngle(Point2(0, 0), VecSub2(FPrevLocation, FLocation)) + PI_2;
Par.Velocity.x:= Cos(ang);
Par.Velocity.y:= sin(ang);
Par.Velocity:= VecMultSingle(Par.Velocity, RandomSingle(FSettings.SpeedMin, FSettings.SpeedMax));
Par.Gravity:= RandomSingle(FSettings.GravityMin, FSettings.GravityMax);
// Accelerations
Par.RadialAccel:= RandomSingle(FSettings.RadialAccelMin, FSettings.RadialAccelMax);
Par.TangentialAccel:= RandomSingle(FSettings.TangentialAccelMin, FSettings.TangentialAccelMax);
// SCALE
Par.Scale:= RandomSingle(FSettings.ScaleStart,
FSettings.ScaleStart + (FSettings.ScaleEnd - FSettings.ScaleStart)* FSettings.ScaleRnd);
Par.ScaleDelta:= (FSettings.ScaleEnd - Par.Scale) / Par.TerminalAge;
// SPIN
Par.Angle:= RandomSingle(FSettings.SpinStart,
FSettings.SpinStart + (FSettings.SpinEnd - FSettings.SpinStart) * FSettings.SpinRnd);
Par.AngleDelta:= (FSettings.SpinEnd - Par.Angle) / Par.TerminalAge;
// Define start color
// Par.Color:= ToSmoothColor(FSettings.ColorStart);// + Random(trunc((FSettings.ColorEnd - FSettings.ColorStart) * FSettings.ColorRnd));
r:= (FSettings.ColorStart and $FF);
g:= (FSettings.ColorStart shr 8) and $FF;
b:= (FSettings.ColorStart shr 16) and $FF;
a:= (FSettings.ColorStart shr 24) and $FF;
Par.Color:= SmoothRGBA(
RandomSingle(r, (r + ((FSettings.ColorEnd and $FF - r) * FSettings.ColorRnd))),
RandomSingle(g, (g + (((FSettings.ColorEnd shr 8) and $FF - g) * FSettings.ColorRnd))),
RandomSingle(b, (b + (((FSettings.ColorEnd shr 16) and $FF - b) * FSettings.ColorRnd))),
RandomSingle(a, (a + (((FSettings.ColorEnd shr 24) and $FF - a) * FSettings.AlphaRnd))),
true);
// Define color delta
// Par.ColorDelta:= FSettings.ColorRnd;//trunc((FSettings.ColorEnd - Par.Color) / Par.TerminalAge);
Par.ColorDelta:= SmoothRGBA(
(FSettings.ColorEnd and $FF - Par.Color.R) / Par.TerminalAge,
((FSettings.ColorEnd shr 8) and $FF - Par.Color.G) / Par.TerminalAge,
((FSettings.ColorEnd shr 16) and $FF - Par.Color.B) / Par.TerminalAge,
((FSettings.ColorEnd shr 24) and $FF - Par.Color.A) / Par.TerminalAge,
false);
{}
Inc(FParticlesAlive);
end;
//------------------------------------------------------------------------------
procedure TXParticleSystem.AddEmitters(const Emitters: array of TPoint);
var
Size, i: integer;
begin
for i:= 0 to Length(Emitters)-1 do
begin
Size:= Length(FEmitters);
SetLength(FEmitters, Size+1);
FEmitters[Size]:= Emitters[i];
end;
end;
//------------------------------------------------------------------------------
procedure TXParticleSystem.AddEmittersFromImage(const Image: TAsphyreImage; const Color: Cardinal);
var
x, y, Points: integer;
begin
if (not Assigned(Image)) then Exit;
SetLength(FEmitters, 0);
Points:= 0;
for x := 0 to Image.PatternSize.X - 1 do
for y := 0 to Image.PatternSize.Y - 1 do
if (Image.Pixels[x, y, 0] = Color) then
begin
Inc(Points);
AddEmitters([Point(x, y)]);
end;
// if Image has no emitters
//if (Points = 0) then
// AddEmitters([Point(trunc(FLocation.X), trunc(FLocation.Y))]);
end;
//------------------------------------------------------------------------------
procedure TXParticleSystem.ScaleEmitters(Scale: Single);
var
i: integer;
begin
for i:= 0 to Length(FEmitters)-1 do
begin
FEmitters[i].X:= Round(FEmitters[i].X * Scale);
FEmitters[i].Y:= Round(FEmitters[i].Y * Scale);
end;
end;
//------------------------------------------------------------------------------
procedure TXParticleSystem.RemoveAllEmitters();
begin
SetLength(FEmitters, 0);
end;
//------------------------------------------------------------------------------
procedure TXParticleSystem.StartAt(X, Y: Single);
begin
Stop();
MoveTo(x, y);
Start();
end;
//------------------------------------------------------------------------------
procedure TXParticleSystem.Start();
begin
if (Length(FEmitters) = 0) then Exit;
if (FSettings.Lifetime <= -0.9) then
FAge:= -1.0
else
fAge:= 0.0;
FResidue:= 0.0;
end;
//------------------------------------------------------------------------------
procedure TXParticleSystem.Stop(KillParticles: boolean = false);
begin
FAge:= -2.0;
if (KillParticles) then
FParticlesAlive:= 0;
end;
//------------------------------------------------------------------------------
procedure TXParticleSystem.MoveTo(const x, y: Single; MoveParticles: boolean = false);
var
i: integer;
dx, dy: Single;
begin
if (MoveParticles) then
begin
dx:= x - FLocation.x;
dy:= y - FLocation.y;
for i:=0 to FParticlesAlive-1 do
begin
FParticles[i].Location.x:= FParticles[i].Location.x + dx;
FParticles[i].Location.y:= FParticles[i].Location.y + dy;
end;
FPrevLocation.x:= FPrevLocation.x + dx;
FPrevLocation.y:= FPrevLocation.y + dy;
end
else
begin
if (FAge = -2.0) then
begin
FPrevLocation.x:= x;
FPrevLocation.y:= y;
end
else
begin
FPrevLocation.x:= FLocation.x;
FPrevLocation.y:= FLocation.y;
end;
end;
FLocation.x:= x;
FLocation.y:= y;
end;
//------------------------------------------------------------------------------
procedure TXParticleSystem.Transpose(const x, y: Single);
begin
FTx:= x;
FTy:= y;
end;
//------------------------------------------------------------------------------
function TXParticleSystem.GetTransposition(): TPoint2;
begin
Result.x:= FTx;
Result.y:= FTy;
end;
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
// TXParticleManager.
//------------------------------------------------------------------------------
constructor TXParticleManager.Create();
begin
FCount:= 0;
Fx:= 0.0;
Fy:= 0.0;
end;
//------------------------------------------------------------------------------
procedure TXParticleManager.Update(const DeltaTime: integer);
var
i: integer;
begin
//for i:= FCount-1 downto 0 do
for i:= 0 to FCount-1 do
begin
FItems[i].Update(DeltaTime);
if (FItems[i] <> nil)and
(FItems[i].Age <= -1.9)and
(FItems[i].ParticlesAlive = 0) then
begin
FItems[i].Free();
FItems[i]:= FItems[FCount-1];
Dec(FCount);
end;
end;
end;
//------------------------------------------------------------------------------
procedure TXParticleManager.Render();
var
i: integer;
begin
for i:= 0 to FCount-1 do
FItems[i].Render(FCanvas);
end;
//------------------------------------------------------------------------------
function TXParticleManager.SpawnPS(PSS: TXParticleSystemSettings;
Texture: TAsphyreImage; X, Y: Single): TXParticleSystem;
begin
if (FCount >= MAX_PSYSTEMS) then
begin
Result:= nil;
Exit;
end;
FItems[FCount]:= TXParticleSystem.Create(PSS);
FItems[FCount].Texture:= Texture;
FItems[FCount].StartAt(x, y);
FItems[FCount].Transpose(FX, FY);
Result:= FItems[FCount];
Inc(FCount);
end;
//------------------------------------------------------------------------------
function TXParticleManager.IsPSAlive(PS: TXParticleSystem): boolean;
var
i: integer;
begin
Result:= false;
for i:= 0 to FCount-1 do
if (FItems[i] = PS) then
begin
Result:= true;
Break;
end;
end;
//------------------------------------------------------------------------------
procedure TXParticleManager.Transpose(X, Y: Single);
var
i: integer;
begin
for i:= 0 to FCount-1 do
FItems[i].Transpose(x, y);
FX:= x;
FY:= y;
end;
//------------------------------------------------------------------------------
procedure TXParticleManager.KillPS(PS: TXParticleSystem);
var
i: integer;
begin
for i:= 0 to FCount-1 do
begin
if(FItems[i] = PS) then
begin
FItems[i].Free();
//FCountList[i]:= FCountList[FCount-1];
Dec(FCount);
Break;
end;
end;
end;
//------------------------------------------------------------------------------
procedure TXParticleManager.KillAll();
var
i: integer;
begin
for i:=0 to FCount-1 do
FItems[i].Free();
FCount:= 0;
end;
//------------------------------------------------------------------------------
function TXParticleManager.GetTransposition: TPoint2;
begin
Result.x:= FX;
Result.y:= FY;
end;
//------------------------------------------------------------------------------
function TXParticleManager.GetItem(id: cardinal): TXParticleSystem;
begin
Result:= FItems[id];
end;
//------------------------------------------------------------------------------
function TXParticleManager.GetParticlesAlive(): integer;
var
i: integer;
begin
Result:= 0;
for i:= 0 to FCount - 1 do
Result:= Result + FItems[i].ParticlesAlive;
end;
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
end.