Here's another system. It's pretty straightforward, single-surface, very fast:
Const EMITTER_BUFFERSIZE=68
Const EMITTER_ENTITY=0
Const EMITTER_VELOCITYX=4
Const EMITTER_VELOCITYY=8
Const EMITTER_VELOCITYZ=12
Const EMITTER_DVELOCITY=16
Const EMITTER_SIZE=20
Const EMITTER_DSIZE=24
Const EMITTER_INTENSITY=28
Const EMITTER_LIFE=32
Const EMITTER_PARTICLEBUFFER=36
Const EMITTER_SPREADX=40
Const EMITTER_SPREADY=44
Const EMITTER_SPREADZ=48
Const EMITTER_COLOR=52
Const EMITTER_ACCELERATIONX=56
Const EMITTER_ACCELERATIONY=60
Const EMITTER_ACCELERATIONZ=64
Global EMITTER_BUFFER=CreateBank()
Function CountEmitters()
Return BankSize(emitter_BUFFER)/4
End Function
Function GetEmitter(n)
Return PeekInt(emitter_BUFFER,(n-1)*4)
End Function
Function UpdateEmitters(speed#=1)
For n=1 To countemitters()
updateemitter getemitter(n),speed#
Next
End Function
Function FindEmitter(entity)
For n=1 To countemitters()
buffer=getemitter(n)
If entity=PeekInt(buffer,EMITTER_ENTITY) Return buffer
Next
End Function
Function EmitterSize(entity,size#,dsize#=-1)
buffer=FindEmitter(entity)
PokeFloat buffer,EMITTER_SIZE,size
If dsize<>-1 PokeFloat buffer,EMITTER_DSIZE,dsize
End Function
Function EmitterVelocity(entity,vx#,vy#,vz#,ax#=43.63,ay#=43.63,az#=43.63,dv#=-1)
buffer=FindEmitter(entity)
PokeFloat buffer,EMITTER_VELOCITYX,vx
PokeFloat buffer,EMITTER_VELOCITYY,vy
PokeFloat buffer,EMITTER_VELOCITYZ,vz
If ax<>43.63 PokeFloat buffer,EMITTER_ACCELERATIONX,ax
If ay<>43.63 PokeFloat buffer,EMITTER_ACCELERATIONY,ay
If az<>43.63 PokeFloat buffer,EMITTER_ACCELERATIONZ,az
If dv<>-1.0 PokeFloat buffer,EMITTER_DVELOCITY,dv
End Function
Function EmitterSpread(entity,x#,y#,z#)
buffer=FindEmitter(entity)
PokeFloat buffer,EMITTER_SPREADX,x
PokeFloat buffer,EMITTER_SPREADY,y
PokeFloat buffer,EMITTER_SPREADZ,z
End Function
Function CreateEmitter(intensity=10,life=100)
buffer=CreateBank(EMITTER_BUFFERSIZE)
bsize=BankSize(EMITTER_BUFFER)
ResizeBank EMITTER_BUFFER,bsize+4
PokeInt EMITTER_BUFFER,bsize,buffer
entity=CreateMesh()
EntityBlend entity,3
EntityFX entity,1+2+8+32
PokeInt buffer,EMITTER_ENTITY,entity
PokeFloat buffer,EMITTER_VELOCITYX,0
PokeFloat buffer,EMITTER_VELOCITYY,1
PokeFloat buffer,EMITTER_VELOCITYZ,0
PokeFloat buffer,EMITTER_ACCELERATIONX,0
PokeFloat buffer,EMITTER_ACCELERATIONY,0
PokeFloat buffer,EMITTER_ACCELERATIONZ,0
PokeFloat buffer,EMITTER_DVELOCITY,0.01
PokeFloat buffer,EMITTER_SIZE,10
PokeFloat buffer,EMITTER_DSIZE,0.1
PokeFloat buffer,EMITTER_LIFE,life
PokeFloat buffer,EMITTER_SPREADX,5
PokeFloat buffer,EMITTER_SPREADY,5
PokeFloat buffer,EMITTER_SPREADZ,5
PokeInt buffer,EMITTER_COLOR,RGB(255,255,255)
PokeInt buffer,EMITTER_INTENSITY,intensity
surf=CreateSurface(entity)
For n=0 To intensity-1
angle#=Rnd(360)
p=CreatePivot()
RotateEntity p,0,0,angle
TFormVector 1,1,0,p,0
AddVertex surf,0,0,0,(TFormedX()+1.0)/2.0,(TFormedY()+1.0)/2.0
TFormVector 1,-1,0,p,0
AddVertex surf,0,0,0,(TFormedX()+1.0)/2.0,(TFormedY()+1.0)/2.0
TFormVector -1,-1,0,p,0
AddVertex surf,0,0,0,(TFormedX()+1.0)/2.0,(TFormedY()+1.0)/2.0
TFormVector -1,1,0,p,0
AddVertex surf,0,0,0,(TFormedX()+1.0)/2.0,(TFormedY()+1.0)/2.0
FreeEntity p
AddTriangle surf,n*4,n*4+2,n*4+1
AddTriangle surf,n*4+2,n*4+0,n*4+3
Next
pbuffer=CreateBank(4*intensity)
PokeInt buffer,EMITTER_PARTICLEBUFFER,pbuffer
offset#=Rnd(0,1)
For n=0 To intensity-1
bank=CreateBank(36)
PokeInt pbuffer,n*4,bank
PokeFloat bank,28,(offset+Float(n))/Float(intensity)+offset
PokeFloat bank,32,Rnd(360)
Next
Return entity
End Function
Function UpdateEmitter(buffer,speed#=1)
entity=PeekInt(buffer,EMITTER_ENTITY)
pbuffer=PeekInt(buffer,EMITTER_PARTICLEBUFFER)
life#=PeekFloat(buffer,EMITTER_LIFE)
size#=PeekFloat(buffer,EMITTER_SIZE)
dsize#=PeekFloat(buffer,EMITTER_DSIZE)
velx#=PeekFloat(buffer,EMITTER_VELOCITYX)
vely#=PeekFloat(buffer,EMITTER_VELOCITYY)
velz#=PeekFloat(buffer,EMITTER_VELOCITYZ)
ax#=PeekFloat(buffer,EMITTER_ACCELERATIONX)
ay#=PeekFloat(buffer,EMITTER_ACCELERATIONY)
az#=PeekFloat(buffer,EMITTER_ACCELERATIONZ)
dv#=PeekFloat(buffer,EMITTER_DVELOCITY)
surf=GetSurface(entity,1)
spreadx#=PeekFloat(buffer,EMITTER_SPREADX)
spready#=PeekFloat(buffer,EMITTER_SPREADY)
spreadz#=PeekFloat(buffer,EMITTER_SPREADZ)
hue=PeekInt(buffer,EMITTER_COLOR)
r=Red(hue)
g=Green(hue)
b=Blue(hue)
;Calculate particle orientation
TFormVector 1,1,0,cam,entity
x0#=TFormedX()
y0#=TFormedY()
z0#=TFormedZ()
TFormVector -1,1,0,cam,entity
x1#=TFormedX()
y1#=TFormedY()
z1#=TFormedZ()
TFormVector -1,-1,0,cam,entity
x2#=TFormedX()
y2#=TFormedY()
z2#=TFormedZ()
TFormVector 1,-1,0,cam,entity
x3#=TFormedX()
y3#=TFormedY()
z3#=TFormedZ()
surf=GetSurface(entity,1)
;Update particles
For n=0 To BankSize(pbuffer)/4-1
;Get info
bank=PeekInt(pbuffer,n*4)
x#=PeekFloat(bank,0)
y#=PeekFloat(bank,4)
z#=PeekFloat(bank,8)
vx#=PeekFloat(bank,12)
vy#=PeekFloat(bank,16)
vz#=PeekFloat(bank,20)
scale#=PeekFloat(bank,24)
alpha#=PeekFloat(bank,28)
angle#=PeekFloat(bank,32)
;Process info
alpha=alpha-1.0/life*speed
scale=scale+dsize*speed
vx=vx+Rnd(-dv,dv)*speed+ax
vy=vy+Rnd(-dv,dv)*speed+ay
vz=vz+Rnd(-dv,dv)*speed+az
x=x+vx
y=y+vy
z=z+vz
If alpha<0
alpha=1
x=Rnd(-spreadx,spreadx)
y=Rnd(-spready,spready)
z=Rnd(-spreadz,spreadz)
vx=velx
vy=vely
vz=velz
scale=size
EndIf
;Store info
PokeFloat bank,0,x
PokeFloat bank,4,y
PokeFloat bank,8,z
PokeFloat bank,12,vx
PokeFloat bank,16,vy
PokeFloat bank,20,vz
PokeFloat bank,24,scale
PokeFloat bank,28,alpha
;Update mesh
VertexCoords surf,n*4,x+x0*scale,y+y0*scale,z+z0*scale
VertexCoords surf,n*4+1,x+x1*scale,y+y1*scale,z+z1*scale
VertexCoords surf,n*4+2,x+x2*scale,y+y2*scale,z+z2*scale
VertexCoords surf,n*4+3,x+x3*scale,y+y3*scale,z+z3*scale
VertexColor surf,n*4,r,g,b,alpha
VertexColor surf,n*4+1,r,g,b,alpha
VertexColor surf,n*4+2,r,g,b,alpha
VertexColor surf,n*4+3,r,g,b,alpha
Next
End Function
Const EMITTER_BUFFERSIZE=68
Const EMITTER_ENTITY=0
Const EMITTER_VELOCITYX=4
Const EMITTER_VELOCITYY=8
Const EMITTER_VELOCITYZ=12
Const EMITTER_DVELOCITY=16
Const EMITTER_SIZE=20
Const EMITTER_DSIZE=24
Const EMITTER_INTENSITY=28
Const EMITTER_LIFE=32
Const EMITTER_PARTICLEBUFFER=36
Const EMITTER_SPREADX=40
Const EMITTER_SPREADY=44
Const EMITTER_SPREADZ=48
Const EMITTER_COLOR=52
Const EMITTER_ACCELERATIONX=56
Const EMITTER_ACCELERATIONY=60
Const EMITTER_ACCELERATIONZ=64
Global EMITTER_BUFFER=CreateBank()
Function CountEmitters()
Return BankSize(emitter_BUFFER)/4
End Function
Function GetEmitter(n)
Return PeekInt(emitter_BUFFER,(n-1)*4)
End Function
Function UpdateEmitters(speed#=1)
For n=1 To countemitters()
updateemitter getemitter(n),speed#
Next
End Function
Function FindEmitter(entity)
For n=1 To countemitters()
buffer=getemitter(n)
If entity=PeekInt(buffer,EMITTER_ENTITY) Return buffer
Next
End Function
Function EmitterSize(entity,size#,dsize#=-1)
buffer=FindEmitter(entity)
PokeFloat buffer,EMITTER_SIZE,size
If dsize<>-1 PokeFloat buffer,EMITTER_DSIZE,dsize
End Function
Function EmitterVelocity(entity,vx#,vy#,vz#,ax#=43.63,ay#=43.63,az#=43.63,dv#=-1)
buffer=FindEmitter(entity)
PokeFloat buffer,EMITTER_VELOCITYX,vx
PokeFloat buffer,EMITTER_VELOCITYY,vy
PokeFloat buffer,EMITTER_VELOCITYZ,vz
If ax<>43.63 PokeFloat buffer,EMITTER_ACCELERATIONX,ax
If ay<>43.63 PokeFloat buffer,EMITTER_ACCELERATIONY,ay
If az<>43.63 PokeFloat buffer,EMITTER_ACCELERATIONZ,az
If dv<>-1.0 PokeFloat buffer,EMITTER_DVELOCITY,dv
End Function
Function EmitterSpread(entity,x#,y#,z#)
buffer=FindEmitter(entity)
PokeFloat buffer,EMITTER_SPREADX,x
PokeFloat buffer,EMITTER_SPREADY,y
PokeFloat buffer,EMITTER_SPREADZ,z
End Function
Function CreateEmitter(intensity=10,life=100)
buffer=CreateBank(EMITTER_BUFFERSIZE)
bsize=BankSize(EMITTER_BUFFER)
ResizeBank EMITTER_BUFFER,bsize+4
PokeInt EMITTER_BUFFER,bsize,buffer
entity=CreateMesh()
EntityBlend entity,3
EntityFX entity,1+2+8+32
PokeInt buffer,EMITTER_ENTITY,entity
PokeFloat buffer,EMITTER_VELOCITYX,0
PokeFloat buffer,EMITTER_VELOCITYY,1
PokeFloat buffer,EMITTER_VELOCITYZ,0
PokeFloat buffer,EMITTER_ACCELERATIONX,0
PokeFloat buffer,EMITTER_ACCELERATIONY,0
PokeFloat buffer,EMITTER_ACCELERATIONZ,0
PokeFloat buffer,EMITTER_DVELOCITY,0.01
PokeFloat buffer,EMITTER_SIZE,10
PokeFloat buffer,EMITTER_DSIZE,0.1
PokeFloat buffer,EMITTER_LIFE,life
PokeFloat buffer,EMITTER_SPREADX,5
PokeFloat buffer,EMITTER_SPREADY,5
PokeFloat buffer,EMITTER_SPREADZ,5
PokeInt buffer,EMITTER_COLOR,RGB(255,255,255)
PokeInt buffer,EMITTER_INTENSITY,intensity
surf=CreateSurface(entity)
For n=0 To intensity-1
angle#=Rnd(360)
p=CreatePivot()
RotateEntity p,0,0,angle
TFormVector 1,1,0,p,0
AddVertex surf,0,0,0,(TFormedX()+1.0)/2.0,(TFormedY()+1.0)/2.0
TFormVector 1,-1,0,p,0
AddVertex surf,0,0,0,(TFormedX()+1.0)/2.0,(TFormedY()+1.0)/2.0
TFormVector -1,-1,0,p,0
AddVertex surf,0,0,0,(TFormedX()+1.0)/2.0,(TFormedY()+1.0)/2.0
TFormVector -1,1,0,p,0
AddVertex surf,0,0,0,(TFormedX()+1.0)/2.0,(TFormedY()+1.0)/2.0
FreeEntity p
AddTriangle surf,n*4,n*4+2,n*4+1
AddTriangle surf,n*4+2,n*4+0,n*4+3
Next
pbuffer=CreateBank(4*intensity)
PokeInt buffer,EMITTER_PARTICLEBUFFER,pbuffer
offset#=Rnd(0,1)
For n=0 To intensity-1
bank=CreateBank(36)
PokeInt pbuffer,n*4,bank
PokeFloat bank,28,(offset+Float(n))/Float(intensity)+offset
PokeFloat bank,32,Rnd(360)
Next
Return entity
End Function
Function UpdateEmitter(buffer,speed#=1)
entity=PeekInt(buffer,EMITTER_ENTITY)
pbuffer=PeekInt(buffer,EMITTER_PARTICLEBUFFER)
life#=PeekFloat(buffer,EMITTER_LIFE)
size#=PeekFloat(buffer,EMITTER_SIZE)
dsize#=PeekFloat(buffer,EMITTER_DSIZE)
velx#=PeekFloat(buffer,EMITTER_VELOCITYX)
vely#=PeekFloat(buffer,EMITTER_VELOCITYY)
velz#=PeekFloat(buffer,EMITTER_VELOCITYZ)
ax#=PeekFloat(buffer,EMITTER_ACCELERATIONX)
ay#=PeekFloat(buffer,EMITTER_ACCELERATIONY)
az#=PeekFloat(buffer,EMITTER_ACCELERATIONZ)
dv#=PeekFloat(buffer,EMITTER_DVELOCITY)
surf=GetSurface(entity,1)
spreadx#=PeekFloat(buffer,EMITTER_SPREADX)
spready#=PeekFloat(buffer,EMITTER_SPREADY)
spreadz#=PeekFloat(buffer,EMITTER_SPREADZ)
hue=PeekInt(buffer,EMITTER_COLOR)
r=Red(hue)
g=Green(hue)
b=Blue(hue)
;Calculate particle orientation
TFormVector 1,1,0,cam,entity
x0#=TFormedX()
y0#=TFormedY()
z0#=TFormedZ()
TFormVector -1,1,0,cam,entity
x1#=TFormedX()
y1#=TFormedY()
z1#=TFormedZ()
TFormVector -1,-1,0,cam,entity
x2#=TFormedX()
y2#=TFormedY()
z2#=TFormedZ()
TFormVector 1,-1,0,cam,entity
x3#=TFormedX()
y3#=TFormedY()
z3#=TFormedZ()
surf=GetSurface(entity,1)
;Update particles
For n=0 To BankSize(pbuffer)/4-1
;Get info
bank=PeekInt(pbuffer,n*4)
x#=PeekFloat(bank,0)
y#=PeekFloat(bank,4)
z#=PeekFloat(bank,8)
vx#=PeekFloat(bank,12)
vy#=PeekFloat(bank,16)
vz#=PeekFloat(bank,20)
scale#=PeekFloat(bank,24)
alpha#=PeekFloat(bank,28)
angle#=PeekFloat(bank,32)
;Process info
alpha=alpha-1.0/life*speed
scale=scale+dsize*speed
vx=vx+Rnd(-dv,dv)*speed+ax
vy=vy+Rnd(-dv,dv)*speed+ay
vz=vz+Rnd(-dv,dv)*speed+az
x=x+vx
y=y+vy
z=z+vz
If alpha<0
alpha=1
x=Rnd(-spreadx,spreadx)
y=Rnd(-spready,spready)
z=Rnd(-spreadz,spreadz)
vx=velx
vy=vely
vz=velz
scale=size
EndIf
;Store info
PokeFloat bank,0,x
PokeFloat bank,4,y
PokeFloat bank,8,z
PokeFloat bank,12,vx
PokeFloat bank,16,vy
PokeFloat bank,20,vz
PokeFloat bank,24,scale
PokeFloat bank,28,alpha
;Update mesh
VertexCoords surf,n*4,x+x0*scale,y+y0*scale,z+z0*scale
VertexCoords surf,n*4+1,x+x1*scale,y+y1*scale,z+z1*scale
VertexCoords surf,n*4+2,x+x2*scale,y+y2*scale,z+z2*scale
VertexCoords surf,n*4+3,x+x3*scale,y+y3*scale,z+z3*scale
VertexColor surf,n*4,r,g,b,alpha
VertexColor surf,n*4+1,r,g,b,alpha
VertexColor surf,n*4+2,r,g,b,alpha
VertexColor surf,n*4+3,r,g,b,alpha
Next
End Function