Visualize particle paths


With the use of Better Particles plugin, this white path is visualized and calculated based on the particle emitters’ properties. How would I achieve this? What I’ve done so far is calculating the end position of where the particle will decay, however my solution isn’t compatible with acceleration for since I’m not sure on how to calculate it.

How do I achieve:

  • The end position of where the particle will decay accounting the acceleration property
  • The curve/path and it’s segments on where the particle moves along
  • ^^ Everything above + the account for drag
2 Likes

Based on the screenshot, the plugin just samples different time positions then calculates their positions from there, using straight lines to connect them up (as indicated by the sudden changes in angle shown below

So basically just have a loop like this:

for t = 0, MAXTIME, TIMESTEP do
   -- calculate position at time t
end

To calculate the position at time t, you should make use of the formula
image
s = ut + 1/2 at^2
where s is the displacement, u is the initial velocity, t is the time, and a is the acceleration.

Adding this into the loop, using variables from the particle emitter, you get the following:

local Part = game.Workspace.Part -- This is the part where the particles originate at
local Emitter = Part.ParticleEmitter
local Step = 0.1

local u = Emitter.Speed.Max * Vector3.up -- only works for flat spawners, that are set to the top emission direction
local a = Emitter.Acceleration

for t = 0, Emitter.Lifetime.Max, Step do
	local Position = Part.Position + u*t + (1/2)*a*t^2
	-- Do what you want with the position at this timestamp
end

If you want it to have less variables:

local Part = game.Workspace.Part -- This is the part where the particles originate at
local Emitter = Part.ParticleEmitter

for t = 0, Emitter.Lifetime.Max, 0.1 do
	local Position = Part.Position + Emitter.Velocity*t + (1/2)*Emitter.Acceleration*t^2
	-- Do what you want with the position at this timestamp
end

The final position then would be

local Position = Part.Position + Emitter.Velocity*Emitter.Lifetime.Max + (1/2)*Emitter.Acceleration*Emitter.Lifetime.Max^2

To account for drag, it becomes a lot more complicated. I’m still trying to derive the correct formula for it, so I will edit this post later on if I come up with a working solution.

2 Likes

OK, regarding drag, it is probably easier just to ‘simulate’ it, rather than trying to derive a formula for it.

local Part = game.Workspace.Part -- This is the part where the particles originate at
local Emitter = Part.ParticleEmitter

local SimulationStep = 0.05

local Velocity = Vector3.yAxis * Emitter.Speed.Max
local Position = Part.Position

local a = Emitter.Acceleration
local drag = Emitter.Drag

for t = 0, Emitter.Lifetime.Max, SimulationStep do	
	Position +=  Velocity * SimulationStep

	Velocity -= Velocity * (math.log(2)) * drag * SimulationStep
	Velocity += a * SimulationStep

	-- This is from testing. You can remove it if you want to do something else with the position:
	-- Spawn a part at the position so you can see it.
	local p = Instance.new("Part", workspace.Folder)
	p.Size = Vector3.new(0.2,0.2,0.2)
	p.Position = Position
end

Note that this only works when the part is flat on the ground, with the emission direction set to top. You’ll need to modify it a little bit to get it to work for other ones.

Aim to have a low simulation step for accuracy. If you don’t need that many steps, then you should only spawn a part every n cycles (e.g. 10)

2 Likes

Huh? This breaks it for me, unlike the first code that you gave, it worked perfectly fine although it doesn’t account for drag. The Part Instances jumbles up into one spot and doesn’t really go anywhere far from the Origin Part; I assume this is because you didn’t use the variable t anywhere in the calculation

	for t = 0, math.max(tonumber(Lifetime[1]), tonumber(Lifetime[2])), Segments do	
		local Velocity = Vector3.new(0, Speed, 0)
		local NewPosition =  Part.Position + (Velocity * Segments)
		
		Velocity -= (Vector3.new(0, Speed, 0) * (math.log(2))) * Drag * Segments
		Velocity += Acceleration * Segments
		
		local NewPosition =  Part.Position + (Velocity * Segments)

		local p = Instance.new("Part", VisualFolder)
		p.Anchored = true
		p.Size = Vector3.one/5
		p.Position = NewPosition
	end
	```
1 Like

How are you assigning some of those variables?

Sorry for the late respond - but basically I didn’t get the actual properties like Lifetime and such from a particle emitter, I made a part have these custom attributes

local Play = Part:GetAttribute("Play") -- true
local Lifetime = {5, 10} -- original lifetime, but updates with the attribute
local Rate = Part:GetAttribute("Rate") -- 10
local Speed = Part:GetAttribute("Speed") -- 1
local Spread = {15, 10} -- original spread but updates with the attribute
local Acceleration = Part:GetAttribute("Acceleration") -- Vector3.new(0, 0, -1)
local Drag = 0
local Segments = 0.1

Then just get the highest value of the lifetime and used that in place of what I put. Same for some of the other components.

Spread is harder to implement, as it results in an area rather than a line.

Turns out the created Part Instances is jumbled up because I put the Velocity and the Position instances inside of the for loops itself → to fix this I just placed it outside of the scope

local Velocity = Vector3.new(0, Speed, 0)
local NewPosition =  Part.Position + (Velocity * Segments)

for t = 0, math.max(tonumber(Lifetime[1]), tonumber(Lifetime[2])), Segments do	
		--local Velocity = Vector3.new(0, Speed, 0)
		--local NewPosition =  Part.Position + (Velocity * Segments)
		
		Velocity -= (Vector3.new(0, Speed, 0) * (math.log(2))) * Drag * Segments
		Velocity += Acceleration * Segments
		
		local NewPosition =  Part.Position + (Velocity * Segments)

		local p = Instance.new("Part", VisualFolder)
		p.Anchored = true
		p.Size = Vector3.one/5
		p.Position = NewPosition
	end

Thanks for the help!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.