Single-Surface particle thingies

Blitz3D Forums/Blitz3D Programming/Single-Surface particle thingies

_PJ_(Posted 2010) [#1]
So, I thought I'd get to grips with a single-surface system, since it seems to be much more efficient all round.

However, I think I've got it wrong somewhere.

my trial is based on a snowfall routine.

'Usually' I would use TYPES and sprites for these kinda particles, but for the Single-Surface idea, I tried making a single mesh, with a single surface (of course) and multiple tris making quads at randomised locations.

I'm not 100% on what I'm doing here, I suspect, though that problems are with either my usage of the AddVertex is incorrect regardign the positioning of the vertices, or that I'm just not texturing correctly perhaps.

Here's the code:
Graphics3D 1024,768,32,2

Global Camera=CreateCamera()
Global Master_Snow=CreatePivot()

PositionEntity Master_Snow,EntityX(Camera),100,EntityZ(Camera,True)+1,True

InitialiseSnow(Master_Snow)

SetBuffer BackBuffer()

While Not KeyDown(True)
	Snowfall(Master_Snow,0.2)
	UpdateWorld
	RenderWorld
	Flip
Wend

Function InitialiseSnow(Parent,Flakes=16,Spread#=2.5)
	Mesh=CreateMesh(Parent)
	Local Index=CountChildren(Parent)
	EntityFX Mesh,54
	EntityAlpha Mesh,0.75
	EntityAutoFade Mesh,(Spread*0.5),(Spread*2.0)
	Surface=CreateSurface(Mesh)
	Local IterFlakes
	For IterFlakes=1 To Flakes
		CreateSnowflake(Parent,Spread,Index)
	Next
	Local Texture=CreateSnowFlakeTexture()
	EntityTexture Mesh,Texture
	FreeTexture texture	
End Function

Function CreateSnowflake(Snow_Parent,Spread#,Index=1)
	Local Mesh=GetChild(Snow_Parent,Index)
	Local Surface=GetSurface(Mesh,True)
	Local Vertices=CountVertices(Surface)

	Spread=(Spread*0.5)
	
	Local PositionX#=EntityX(Snow_Parent,True)+Rand(0-Spread,Spread)
	Local PositionY#=EntityY(Snow_Parent,True)+Rand(0-Spread,Spread)
	Local PositionZ#=EntityZ(Snow_Parent,True)+Rand(0-Spread,Spread)
	
	Local V1 = AddVertex(Surface, PositionX, PositionY, PositionZ, 0  , 0)
	Local V2 = AddVertex(Surface, PositionX, PositionY, PositionZ, 1.0, 0.0)
	Local V3 = AddVertex(Surface, PositionX, PositionY, PositionZ, 0  , 1.0)
	Local V4 = AddVertex(Surface, PositionX, PositionY, PositionZ, 1.0, 1.0)
	
	AddTriangle(Surface, V1, V2, V3)
	AddTriangle(Surface, V3, V2, V4)
	
	VertexColor Surface,Vertices+1,0,0,0,True
	
End Function
	
Function CreateSnowFlakeTexture()
	Local Texture=CreateTexture(128,128,262)
	SetBuffer TextureBuffer(Texture)
	Local IterCrystal
	Local IterGrowth=Rand(8,64)
	For IterCrystal=1 To 6
		Line 64,64,Sin(IterCrystal*60)*64,Cos(IterCrystal*60)*64
		Oval 64+Sin(IterCrystal*60)*IterGrowth,64+Cos(IterCrystal*60)*IterGrowth,IterGrowth,IterGrowth,False
	Next
	Return Texture
End Function

Function Snowfall(Parent,Speed#,FloorLevel=0, CeilingLevel=50)
	Local IterSnow, Mesh
	For IterSnow=1 To CountChildren(Parent)
		Mesh=GetChild(Parent,IterSnow)
		TurnEntity Mesh,-1,Sin(MilliSecs() Mod 100),Sin(MilliSecs() Mod 200),True
		TranslateEntity Mesh,Rand((0-Speed# * 0.5),(Speed# * 0.5)),0-Speed#,Rand((0-Speed# * 0.5),(Speed# * 0.5)),True
	
	If (EntityY(Mesh,True)+MeshHeight(Mesh) < FloorLevel) Then TranslateEntity Mesh,0,CeilingLevel,0,True
	
	Next
		
End Function



Stevie G(Posted 2010) [#2]
You are positioning all the vertices for each quad in the same place.

Something like this should work ..

Local V1 = AddVertex(Surface, PositionX-1, PositionY+1, PositionZ, 0  , 0)
Local V2 = AddVertex(Surface, PositionX+1, PositionY+1, PositionZ, 1.0, 0.0)
Local V3 = AddVertex(Surface, PositionX+1, PositionY-1, PositionZ, 1.0  , 1.0)
Local V4 = AddVertex(Surface, PositionX-1, PositionY-1, PositionZ, .0, 1.0)


If you want to colour the vertices you need to iterate through V1 to V4. As you have it you are only colouring the last vertice.

for v  = V1 to V4
  vertexcolor surface, v, 0,0,0
next


Note that the last parameter for vertexcolor is an alpha value between 0 and 1.0 and the entityfx for the mesh must be set to 2+32 for that to work.

Local Surface=GetSurface(Mesh,True)


Also, strickly speaking getsurface's second paramented is the surface number, although true will return surface 1.

I don't think you've quite grasped the idea of single surface. Movement and rotation of the individual particles are done using the vertex commands rather than the entitycommands.

Hope some of this helps.

Stevie


_PJ_(Posted 2010) [#3]
Thanks for spotting thae errors, Stevie!

That really clears it up a lot. I dunno what I was thinking for some of it ;)

Incidentally, my use of True / False instead of 1 and 0 is because I dislike seeing pure numbers in my code... just a personal quirk :)

The EntityFX parameter must be able to set to other values, provided x AND 34 = 34 surely?


Stevie G(Posted 2010) [#4]
The EntityFX parameter must be able to set to other values, provided x AND 34 = 34 surely?


Of course.


_PJ_(Posted 2010) [#5]
okay, I've tried to work with the vertices correctly, and I hope I've gotten the grasp of the general idea a bit better, however, when I run it, I still don't see anything :(

I took out thr Vertex colour stuff, since I am texturing the flakes anyway (hopefully).

Maybe the flakes just aren't at a suitable distance, I dunno - Before I start debuglogging all the coords and laying with the CameraRange factors, can someone just check that the Vertex manipulation stuf is pretty much how it should be?

Graphics3D 1024,768,32,2

Global Camera=CreateCamera()
Global Master_Snow=CreatePivot()

PositionEntity Master_Snow,EntityX(Camera),100,EntityZ(Camera,True)+1,True

InitialiseSnow(Master_Snow)

SetBuffer BackBuffer()

While Not KeyDown(True)
	Snowfall(Master_Snow,0.2)
	UpdateWorld
	RenderWorld
	Flip
Wend

Function InitialiseSnow(Parent,Flakes=16,Spread#=2.5)
	Mesh=CreateMesh(Parent)
	Local Index=CountChildren(Parent)
	EntityFX Mesh,52
	EntityAlpha Mesh,0.75
	EntityAutoFade Mesh,(Spread*0.5),(Spread*2.0)
	Surface=CreateSurface(Mesh)
	Local IterFlakes
	For IterFlakes=1 To Flakes
		CreateSnowflake(Parent,Spread,Index)
	Next
	Local Texture=CreateSnowFlakeTexture()
	EntityTexture Mesh,Texture
	FreeTexture texture	
End Function

Function CreateSnowflake(Snow_Parent,Spread#,Index=1)
	Local Mesh=GetChild(Snow_Parent,Index)
	Local Surface=GetSurface(Mesh,True)
	Local Vertices=CountVertices(Surface)

	Spread=(Spread*0.5)
	
	Local PositionX#=EntityX(Snow_Parent,True)+Rand(0-Spread,Spread)
	Local PositionY#=EntityY(Snow_Parent,True)+Rand(0-Spread,Spread)
	Local PositionZ#=EntityZ(Snow_Parent,True)+Rand(0-Spread,Spread)
	
	Local V1 = AddVertex(Surface, PositionX-1, PositionY+1, PositionZ, 0  , 0)
	;VertexColor Surface,Vertices+1,0,0,0,0.5
	Local V2 = AddVertex(Surface, PositionX+1, PositionY+1, PositionZ, 1.0, 0.0)
	;VertexColor Surface,Vertices+2,0,0,0,True
	Local V3 = AddVertex(Surface, PositionX+1, PositionY-1, PositionZ, 1.0  , 1.0)
	;VertexColor Surface,Vertices+3,0,0,0,0.5
	Local V4 = AddVertex(Surface, PositionX-1, PositionY-1, PositionZ, .0, 1.0)
	;VertexColor Surface,Vertices+4,0,0,0,0.5

	
	AddTriangle(Surface, V1, V2, V3)
	AddTriangle(Surface, V3, V2, V4)
		
End Function
	
Function CreateSnowFlakeTexture()
	Local Texture=CreateTexture(128,128,262)
	SetBuffer TextureBuffer(Texture)
	Local IterCrystal
	Local IterGrowth=Rand(8,64)
	For IterCrystal=1 To 6
		Line 64,64,Sin(IterCrystal*60)*64,Cos(IterCrystal*60)*64
		Oval 64+Sin(IterCrystal*60)*IterGrowth,64+Cos(IterCrystal*60)*IterGrowth,IterGrowth,IterGrowth,False
	Next
	Return Texture
End Function

Function Snowfall(Parent,Speed#,FloorLevel=0, CeilingLevel=50)
	Local IterSnow, IterFlakes,Mesh,Surface
	For IterSnow=1 To CountChildren(Parent)
		Mesh=GetChild(Parent,IterSnow)
		Surface=GetSurface(Mesh,True)
		For IterFlakes=0 To CountVertices(Surface)-1
			
			If (VertexY(Surface,IterFlakes)>FloorLevel)
				VertexCoords Surface,IterFlakes,VertexX(Surface,IterFlakes)+Rand(-0.1,0.1),VertexY(Surface, Iterflakes)-Speed#,VertexZ(Surface,IterFlakes)+Rand(-0.1,0.1)
			Else
				Local PositionX#=EntityX(Parent,True)+Rand(0-Spread,Spread)
				Local PositionZ#=EntityZ(Parent,True)+Rand(0-Spread,Spread)
				VertexCoords Surface,IterFlakes,PositionX,CeilingLevel,PositionZ
			End If
		Next
	Next
			
End Function



Ross C(Posted 2010) [#6]
Are you creating the triangles the correct way round?


_PJ_(Posted 2010) [#7]
I disabled the backface culling with EntityFX hopefully to prevent this being an issue, and to allow tohe snowflakes to fully rotate independantly.

Assuming that should work?


Ross C(Posted 2010) [#8]
Hmmm...

	Local Texture=CreateSnowFlakeTexture()
	EntityTexture Mesh,Texture
	FreeTexture texture


Aren't you freeing the texture here? Once you free it, it shouldn't be applied to the mesh. I will run this when i get home. I remember my first time with a single surface particle system. not fun :)

A good way of rotating the particles to always face the camera easily btw, is to parent the entire mesh to the camera. That way, all you need to do, is position the vertices. (I know your probably not wanting that here, but just a little tip for speed that particular situation up)


Yasha(Posted 2010) [#9]
It should be safe to free a texture that's still applied to a mesh, actually, as long as you don't intend to use it on anything else after that; textures have an internal reference count and are freed when it drops to zero, not when you call FreeTexture (although FreeTexture will in many cases be the command that drops that reference count to zero).

'Course this may have changed.


_PJ_(Posted 2010) [#10]
Yeah it's still okay to free static textrues once they've been applied.

Thanks for the tip, Ross, though in this case, I am hoping that the snowflakes can face away from the camera and all over, jsut like real snowflakes drifting on the wind. Some may be seen edge on etc...

I'm wondering if it might be the size of the things, also, they do fall quite fast.

Currently I'm seeing what I can do to try and pinpoint just one flake with the camera and a sensible range and size. Still just a black screen though :S


Jiffy(Posted 2010) [#11]
You do realize you've 'local'ed everything except your camera & master_snow, right? Whatever else you create must be done every loop. I'd guess something's not getting done, but I'm a little distracted right now, so I'll leave it to you.

For later on:
Globals are good- All those locals inside your loops must be allocated/initialized _every_ time. Inside frequently called loops (CreateSnowFlake) those _will_ slow you down. Make a bunch of global temps you can trash. <global temp>=0 if you have to.


Yasha(Posted 2010) [#12]
I wish to jump in here with something that was hammered into me for several weeks solid at the start of my CS degree:

Globals are evil. Avoid them. They're not fast enough to be worth the pain they are absolutely guaranteed to cause you in terms of code organisation, and among strict academics are second only to Goto for ways-to-fail-your-programming-course.


Jiffy(Posted 2010) [#13]
So is 'goto'- but you see it in almost every language. They _should_ be avoided- except when they're useful, and when dealing with loops that happen thousands of time per pass, they are. This isn't a +2% increase 'bad practice' (though that might be enough in some cases). No- depending on code you can get 10-30% more performance switching from locals to globals inside frequently called loops. Particle engines? Probably want all you can get.

Seriously- try it.

Blitz isn't c, c# or c++. No namespaces. Programming theory is not always programming practice- otherwise both globals and 'goto' would be gone already. Use unique names- I like trailing underscores for this. Or don't. Make clean code that runs slower.

Academics (the people who graded you) are well known for parroting what they read- after all, you have to be a good little cog to fit into the machine of industry. Personally, I helped teach about 3 math teachers a lot that they needed to be CS teachers. I admired their courage- knowing how little they actually knew & standing up there, pretending.

There's an old story of an optimization book which was a standard for years- a required coursebook- till someone actually bench-marked the 'faster' optimized examples and found many were actually slower (though they should have been faster).

I'm just saying you're right, but this works. Maybe it's like being married.
Do you want to be right or happy?


Yasha(Posted 2010) [#14]

Seriously- try it.



'Kay.



I get:

Locals 1: 126
Globals 1: 3568
Locals 2: 126
Globals 2: 3572

If I comment out the lines with floats, I get:

Locals 1: 70
Globals 1: 68
Locals 2: 71
Globals 2: 68

...I certainly wasn't expecting that result, anyway! Am I missing something obvious? (Besides, apparently, a computer that doesn't suck...)

EDIT: In fact, if I comment out the integer section of DoItWithLocals, I get:

Locals 1: 94
Globals 1: 128
Locals 2: 94
Globals 2: 127

ie. Commenting things in one function affects the speed of things in a different function! ...I think the moral of the story is that I have absolutely no idea what's going on there.

Sorry to have interrupted with this.


Jiffy(Posted 2010) [#15]
No prob. I'm playing with your code. Blitz does some weird things behind the scenes. Debug off is important. Have no idea why comments change anything (sigh). I think '+1's are optimized, and the idea is initialization inside a repeatedly called loop. Like this:



Anyway, this code gives an idea- though honestly now I can't see from one change to the next what's 'going on' inside blitz while changing this thing.

I take it back. Do what you want- it won't make a difference... :P

[edit]
Best I can tell, global ints appear 15x faster in this example, while global floats appear 6x slower. No idea why- though in some earlier variants had globals at around the same speed as locals (?). Guess I'll have to go back & check some of my other code, though maybe I messed something up here I'm missing...
[/edit]


Jiffy(Posted 2010) [#16]
Another hour of testing, and all I can say is 'global ints= good', 'global floats= bad'. The numbers for global ints are either = to locals, 10% better- or a lot better. Global floats seem to muck up anything they touch- especially when used in calculations. I'm tempted to test const floats- but I wanna be able to sleep at night.

The fact that comments change compiled program speeds is... confusing.

Ah well.


_PJ_(Posted 2010) [#17]
I'm a little lost here with regards to these latest comments about Globals etc.
Surely temporary Variables used within functions ought to be local since using Globals could introduce a lot of bugs especially in nested kinda functions.
I tend to use the same names for local variables and handles such as 'IterFlakes' or 'Mesh' in the original code.

Are you (Yasha / Jiffy) suggesting this is wrong?


Jiffy(Posted 2010) [#18]
Yasha's not suggesting anything- he was testing what I've used.

Globals don't introduce anything if:
- You keep your variable names unique & track them.
- You don't assume the value of the variable on entry to be '0' (or '').

Look at at any random loop where a local var is initialized. What happens next? Is it used as if it is '0' or assigned a value? Rarely is '0' assumed, but you could assign it every time to be sure.

This is 'bad programming practice' in the conventional sense. Avoid it unless you need it and know what you're doing. Also, it seems floats as globals suck. Dunno about strings, though they're normally slower. types and arrays are implicitly global anyway. Dunno about blitz arrays.

The blitz compiler does some wonky things- somehow, comments change the compiled code speed (?)- so I dunno what's going on inside. All in all though, int globals are as fast as locals, a little faster or much faster- depending on blitz's whim- in the situation where a repeated loop normally initializes a local every time.

Course Mark could quietly 'optimize' this in the next update. I suspect the improved float accuracy and the pie-wise global float speed may be connected, but that's just speculation.


_PJ_(Posted 2010) [#19]
Well, I havent anywhere assumed a 0 value for any variabbles, local or otherwise, also, the Blitz compiler does always give a 0 value for undefined initialisation.
I know it's bad pracice, but if you know for sure how compiler will interpretr it, it's not so bad. Unlike, say, C where there are numerous different compilers that may be used, with blitz, there's just the one.

Overall, is the speed difference you speak of really enough of a hit, or significant enough compared to the use of the single-surface idea itself?
of course, the more efficient the better in most cases, but there's a fine line between efficiency and readability too. :)


Jiffy(Posted 2010) [#20]
That's what I mean- if you write like your variable will be implicitly 'local'ed and inited- if it's global you're in for a mess. For the most part, writing "Local var" is unnecessary unless you're doing a local override (except of course for good programming convention). The 'Local var' command is almost a comment- blitz will do it anyway. I put ";globaled var elsewhere" in the code whenever I do this so I know.

The speed difference is probably very much situational- at worst it seems to be the same speed for ints- usually a little better- at best much better. Don't do it for floats, though (don't know why).

Try it. If it makes it faster- leave it in. I've been doing this for a while & always got some speed increase- but ymmv.


_PJ_(Posted 2010) [#21]
Oh sure. I never used to declare Local, since theyre implicit anyway, it just has become a habit, although oveerall, it hasn't made much of a speed difference, but I do appreciate the points :)

I'm still unsure of what's gone on with my code, though... I might just start over!


_PJ_(Posted 2010) [#22]
After testing bits of my code separately, I think there may be (at east) something very wrong with my create quads routine.
Somehow, the geometry isnt the 'right' size, orientation or maybe due to being flat the lack of depth renders them invisible or something???

Even disabling backface culling, colouring them white, making them fullbright etc doesn't seem to show anythng on camera :(

Graphics3D 800,600,32,2
SetBuffer BackBuffer()

AmbientLight 160,160,160
Global Sun=CreateLight()
PositionEntity Sun,0,10,-5,True

Global Q=CreateQuad()
Global C=CreateCamera()
EntityColor Q,255,255,255

EntityFX Q,25				;Test

PositionEntity Q,0,0,0,True	;Just in case
PointEntity Q,C				;Just in case

While (Not(KeyDown(True)))
	If (Not(EntityInView(Q,C)))
		MoveEntity C,0,0,-0.1
	End If
	UpdateWorld
	RenderWorld
	Flip
Wend
ClearWorld
EndGraphics
End

Function CreateQuad(Parent=False)
	Local Mesh=CreateMesh(Parent)
	Local Surf=CreateSurface(Mesh)
	
	Local V1 = AddVertex(Surf, 0, 0, 0, 0, 0)
	Local V2 = AddVertex(Surf, 0, 0, 0, 1.0, 0.0)
	Local V3 = AddVertex(Surf, 0, 0, 0, 0, 1.0)
	Local V4 = AddVertex(Surf, 0, 0, 0, 1.0, 1.0)
	
	AddTriangle(Surf, V1, V2, V3)
	AddTriangle(Surf, V3, V2, V4)
	
	Return Mesh
End Function



Ross C(Posted 2010) [#23]
All your vertices are being created in the same place. Your only altering the vertex texture coords.


Kryzon(Posted 2010) [#24]
Globals don't introduce anything if:
- You keep your variable names unique & track them.


Could you explain this a bit differently? I didn't get it.


Jiffy(Posted 2010) [#25]
The main reason why globals are bad is:
Global i
...
i=Graphicswidth()
...
for i=1 to 20; this whole routine could be in a lib, or cut/pasted
   a_state=do_a_thing(i);unknowingly clobbering above
   if a_state exit
next
...
xw=i/2; assuming unclobbered
...

And you have ugly problems because you have a common name- stomping over your own stuff,
alternately:
global Sys_gfx_wi_

is pretty unlikely to be used on accident by anybody. Also:
function foo%(var)
   local athing%
   ;globaled Sys_gfx_wi_ elsewhere (or specifically where)
   athing= do_other_thinglet(var, Sys_gfx_wi_)
end function

removes questions as well.
unlikely<>impossible- but it helps.

I'm not encouraging this except when needed- it is a bad habit.
Hope that clarifies.


_PJ_(Posted 2010) [#26]
@Ross, thanks.. I see now, it's got the wrong coords filled in :)
I borrowed that function from a copy from a copy from one originally posted here, so I think Ive edited the 'original' along the way without realising :)


_______________
To be honest, Jiffy, and I hope Ive understood correctly, but using a common var name like "i" for globals, which later may be used in a for/next yes, this IS bad, but also pretty poor programming in my opinion.
Globals should be used for ONE purpose only.

Where Ive used C, T etc. above is PURELY for that sole, self-contained example, referring specifically to the problem I had with creating a quad mesh. I would not use such a method for a 'real program'

For example:
Global MyGlobalImage

and use very precise names. Any temporary local usage for For/Next identifiers, I tend to use my own (also specifically unique for the purpose) variables as
For iterCoordsX=0 to X
For iterCoordsY=0 to Y

[/code]


_PJ_(Posted 2010) [#27]
Okay, I've sorta got it working...

That is, my snowflakes are now visible and semi-transparent as intended.
However, if you seenby the code below, I think my attekmpt at randomising the respawn position and rotation of the flakes messes them up, so they're no longer rectangular, and the UV coords dont seem to match.

I'm not sure how to ensure the flakes to be placed spread over a certain radius (SNOWFALL_SPREAD) and with varied rotations, whilst preserving them as quads with specigfic UVs. Perhaps I would need to re-set the UV coords when I need to reposition the flakes when they hit the FLOOR_LEVEL ?




Jiffy(Posted 2010) [#28]

Globals should be used for ONE purpose only.


And goto should never be used, yada yada, girls like the 'bad boys'...

putting
local var

inside a repeatedly called sub takes a tiny bit of time. If your sub is called 10 times a sec- no biggie. 100? still, who cares? 1,000+? 10,1000+?'maybe I don't need to allocate and clear this same variable over and over if it's gonna cost me...'
because every +n fps is good.

Your call. Either way is fine by me- I didn't make blitz work this way, I just told how to get around it.


Noobody(Posted 2010) [#29]
putting

local var


inside a repeatedly called sub takes a tiny bit of time.

It is in fact the other way round - globals are significantly slower than locals. Just execute the following code:
Counter = MilliSecs()
For I = 0 To 10000000
	Local Foo = 5
Next
Print MilliSecs() - Counter


Global Bar

Counter = MilliSecs()
For I = 0 To 10000000
	Bar = 5
Next
Print MilliSecs() - Counter


WaitKey()
End

The loop with local is about 40 times faster than the loop that uses the global (at least on my machine).


_PJ_(Posted 2010) [#30]
Please can you discuss the efficiency/inefficiency of variable usage in another thread? I really want to get my issues with the vertices etc. sorted here. :)


Jiffy(Posted 2010) [#31]
Malice; sorry.

sorry- the vertex mess is still unresolved- I'm not sure what you're trying to do, so I can't fix it. Are they all supposed to be flat quads facing the camera?


Noobody(Posted 2010) [#32]
Sorry Malice - didn't mean to hijack your thread :)


_PJ_(Posted 2010) [#33]
It's okay, I appreciate the comments regarding the efficiency was trying to help, but that can wait until I get the quads working right. :)

Yeah, basically they should be flat quads, each with the same texture.
It's not important if they face the camera, since they should be rotating slowlyand the reverse faces should showthe tectur too. (Disable Backface Culling flag)

These quads should mimic snowflakes and fall gradually down to the ground around the camera (or at least, within a patticular radius specified by SNOWFALL_SPREAD)

They way I put it together is that the quads are all ADDED (with AddMesh) to a global Mesh (named Master_Snow) which acts as the 'parent' and this 'parent' is like the source of the snowflakes. When any of the flakes' Y vertex points hit the ground ( <= FLOOR_LEVEL), then that quad should be jumped back up to the origin point to fall again.

Hope that makes sense. Overall, maybe I'm doing it completely wriong, the purpose of this code is really just for me to learn how to make particle effects like this using single-surface meshes rather than a load of sprites etc.

As I write this, I just thought that perhaps the EntityX, EntityY and EnittyZ of the Master_Snow entity would change anyway due to the changing vertex positions?

Or maybe not (if not, that's good for how it's coded!)

oh I see I didn;t put the code in tags properly above. Il fix that now :) )