# sparkles

I would like to speed up the following up, if possible. Basically it creates several fading “sparks” on the screen spawning them every xx seconds (based on “respawn” parameter). Each spark is following a cubic bezier curve with poles created in either random way or pre-defined.
If I put a respawn at 0.01 then my frame rate will drop to 30FPS. I have tried to use mesh and use local functions and parameters but the barrier of 30 FPS is still there.

``````spark = class()

function spark:init(length,dA,sA,mode,p0,p1,p2,p3)

-- Cache functions
local random = math.random
local cos = math.cos
local sin = math.sin
local pi = math.pi

-- you can accept and set parameters here
self.m = mesh()
local angle = sA or random(0,360) -- starting angle
self.n = random(1,20)/100  -- tail length

--define bezier points
if mode == "random" then
local margin = 100
self.p0 = {x = random(0-margin,WIDTH+margin),y = random(0-margin,HEIGHT+margin)}
angle = angle + random(-dA,dA)
self.p1 = {x = self.p0.x + cos(angle*2*pi/360)*length, y = self.p0.y + sin(angle*2*pi/360)*length}
angle = angle + random(-dA,dA)
self.p2 = {x = self.p1.x + cos(angle*2*pi/360)*length, y = self.p1.y + sin(angle*2*pi/360)*length}
angle = angle + random(-dA,dA)
self.p3 = {x = self.p2.x + cos(angle*2*pi/360)*length, y = self.p2.y + sin(angle*2*pi/360)*length}
else
self.p0 = p0
self.p1 = p1
self.p2 = p2
self.p3 = p3
end

self.time = ElapsedTime
self.active = true
end

function spark:draw()
-- Codea does not automatically call this method

-- Cache functions
local atan2 = math.atan2
local sqrt = math.sqrt
local abs = math.abs
local bez = bezier
self.m:clear()
local p0 = self.p0
local p1 = self.p1
local p2 = self.p2
local p3 = self.p3
local n = self.n

if show_poles then
pushStyle()
fill(255, 0, 0, 100)
ellipse(p0.x,p0.y,8)
ellipse(p1.x,p1.y,8)
ellipse(p2.x,p2.y,8)
ellipse(p3.x,p3.y,8)
strokeWidth(1)
stroke(255,0,0,100)
line(p0.x,p0.y,p1.x,p1.y)
line(p1.x,p1.y,p2.x,p2.y)
line(p2.x,p2.y,p3.x,p3.y)
popStyle()
end

local i = (ElapsedTime - self.time) / 4 -- sparkle life ... 4 seconds
if i < 1 then
for k = 0,n, 0.01 do
if i+k < 1 then
local alpha = k/n*90*(-abs(0.5-i)*2+1) -- max alpha at i = 0.5, max alpha value = 90
local s = bez(p0,p1,p2,p3,i+k)
local f = bez(p0,p1,p2,p3,i+k+0.01)
local dx = f.x - s.x
local dy = f.y - s.y
local d = sqrt(dx * dx + dy * dy)
local a = atan2(dy, dx)
local idx = self.m:addRect(s.x + dx / 2, s.y + dy / 2, d, 2, a)
self.m:setRectColor(idx,22,141,175,alpha)
end
end
self.m:draw()
else
self.active = nil
end
end

function spark:touched(touch)
-- Codea does not automatically call this method
end

-- cubic bezier
function bezier(p0,p1,p2,p3,mu)
local c = {}
c.x = 3 * (p1.x - p0.x)
c.y = 3 * (p1.y - p0.y)
local b = {}
b.x = 3 * (p2.x - p1.x) - c.x
b.y = 3 * (p2.y - p1.y) - c.y
local a = {}

a.x = p3.x - p0.x - c.x - b.x
a.y = p3.y - p0.y - c.y - b.y

local p={}
p.x = a.x * mu * mu * mu + b.x * mu * mu + c.x * mu + p0.x
p.y = a.y * mu * mu * mu + b.y * mu * mu + c.y * mu + p0.y

return(p)
end
``````

here is instead the call in main tab.
In setup():

``````    -- setup sparkles for background
show_poles = nil
length = 200    -- tail length
dA = 10         -- angle variation
sA = 45         -- starting angle
respawn = 0.01   -- respawn frequence in seconds
sparkles = {}
spawncounter=0
``````

and in draw():

``````    spawncounter = spawncounter + DeltaTime
if spawncounter>respawn then
table.insert(sparkles,spark(length,dA,sA,"random"))
spawncounter=0
end
for k,s in pairs(sparkles) do
if s.active then
s:draw()
else
table.remove(sparkles,k)
end
end
``````

Could you post a single copy-paste-run code?

just a little thing

you are repeating most of the work in bez when you don’t need to, and if you put s and f into a vec2, you could use the dist function to calculate the distance between them

Why not calc both s and f in bez, and use vec2 throughout, eg

``````--add delta parameter (0.01) *****
function bezier(p0,p1,p2,p3,mu,delta)
--assuming p0, p1, p2, p3 are vec2 *****
local c = 3*p1 - p0
local b = 3*p2 - c
local a = p3-p0-c-b

p1=vec2(
((a.x * mu + b.x) * mu+ c.x) * mu + p0.x,
((a.y * mu + b.y )* mu + c.y )* mu + p0.y
)
--calculate again after adding delta *****
mu=mu+delta
p2=vec2(
((a.x * mu + b.x) * mu+ c.x) * mu + p0.x,
((a.y * mu + b.y )* mu + c.y )* mu + p0.y
)
--return p1 and distance from p2 ****
return p1, p1:dist(p2)
end
``````

then back in draw, this

``````local s = bez(p0,p1,p2,p3,i+k)
local f = bez(p0,p1,p2,p3,i+k+0.01)
local dx = f.x - s.x
local dy = f.y - s.y
local d = sqrt(dx * dx + dy * dy)
``````

``````local s,d = bez(p0,p1,p2,p3,i+k,0.01)
``````

I have put everything in a single copy-paste-run file and I have done the modifications suggested by Ignatz.
Only thing different is that I return p1 and p2 instead of the distance because i need the angle as well to align the mesh. It seems anyway I made a small mistake because the result is not exactly the same as before but I can correct it later. Anyway also with these modifications it’s still not improving that much (2-3 FPS).

``````function setup()
show_poles = nil
length = 200    -- tail length
dA = 10         -- angle variation
sA = 45         -- starting angle
respawn = 0.01   -- respawn frequence in seconds
sparkles = {}
spawncounter=0

-- setup FPS for debugging
local x,y = WIDTH-30,HEIGHT-20
local col = color(255)
FPS(nil,col,x,y)

end

function draw()
-- This sets a dark background color
background(40, 40, 50)

-- This sets the line thickness
strokeWidth(5)

spawncounter = spawncounter + DeltaTime
if spawncounter>respawn then
table.insert(sparkles,spark(length,dA,sA,"random"))
spawncounter=0
end
for k,s in pairs(sparkles) do
if s.active then
s:draw()
else
table.remove(sparkles,k)
end
end
end

spark = class()

function spark:init(length,dA,sA,mode,p0,p1,p2,p3)

-- Cache functions
local random = math.random
local cos = math.cos
local sin = math.sin
local pi = math.pi

-- you can accept and set parameters here
self.m = mesh()
local angle = sA or random(0,360) -- starting angle
self.n = random(1,20)/100  -- tail length

--define bezier points
if mode == "random" then
local margin = 100
self.p0 = vec2 (
random(0-margin,WIDTH+margin),
random(0-margin,HEIGHT+margin)
)
angle = angle + random(-dA,dA)
self.p1 = vec2 (
self.p0.x + cos(angle*2*pi/360)*length,
self.p0.y + sin(angle*2*pi/360)*length
)
angle = angle + random(-dA,dA)
self.p2 = vec2 (
self.p1.x + cos(angle*2*pi/360)*length,
self.p1.y + sin(angle*2*pi/360)*length
)
angle = angle + random(-dA,dA)
self.p3 = vec2 (
self.p2.x + cos(angle*2*pi/360)*length,
self.p2.y + sin(angle*2*pi/360)*length
)
else
self.p0 = p0
self.p1 = p1
self.p2 = p2
self.p3 = p3
end

self.time = ElapsedTime
self.active = true
end

function spark:draw()
-- Codea does not automatically call this method

-- Cache functions
local atan2 = math.atan2
local sqrt = math.sqrt
local abs = math.abs
local bez = bezier
self.m:clear()
local p0 = self.p0
local p1 = self.p1
local p2 = self.p2
local p3 = self.p3
local n = self.n

if show_poles then
pushStyle()
fill(255, 0, 0, 100)
ellipse(p0.x,p0.y,8)
ellipse(p1.x,p1.y,8)
ellipse(p2.x,p2.y,8)
ellipse(p3.x,p3.y,8)
strokeWidth(1)
stroke(255,0,0,100)
line(p0.x,p0.y,p1.x,p1.y)
line(p1.x,p1.y,p2.x,p2.y)
line(p2.x,p2.y,p3.x,p3.y)
popStyle()
end

local maxAlpha = 200    -- max alpha at i = 0.5, max alpha value = 90
local i = (ElapsedTime - self.time) / 4 -- sparkle life ... 4 seconds
if i < 1 then
for k = 0,n, 0.01 do
if i+k < 1 then
local alpha = k/n*maxAlpha*(-abs(0.5-i)*2+1)
local s,f = bez(p0,p1,p2,p3,i+k,0.01)
local d = s:dist(f)
local a = s:angleBetween(f)
local idx = self.m:addRect(s.x + d / 2, s.y + d / 2, d, 2, a)
self.m:setRectColor(idx,22,141,175,alpha)
end
end
self.m:draw()
else
self.active = nil
end
end

function spark:touched(touch)
-- Codea does not automatically call this method
end

-- cubic bezier
function bezier(p0,p1,p2,p3,mu,delta)
local c = 3*p1 - p0
local b = 3*(p2-p1) - c
local a = p3-p0-c-b

local p1=vec2(
((a.x * mu + b.x) * mu + c.x) * mu + p0.x,
((a.y * mu + b.y) * mu + c.y) * mu + p0.y
)
mu = mu + delta
local p2=vec2(
((a.x * mu + b.x) * mu + c.x) * mu + p0.x,
((a.y * mu + b.y) * mu + c.y) * mu + p0.y
)
return p1, p2
end
``````

You are drawing 4 ellipses and three lines for each spark, in each frame

It should be faster if you make a sprite (using setContext) for each spark when you create it, requiring only one drawing call per spark. This will require more memory, but I don’t think that should be a problem. Worth a try, anyway.

In general, I suggest what you should do is look for performance bottlenecks. For example, before trying my suggestion above, comment out all except one of the ellipses and lines, so you are only drawing one item per frame, run it, and if it makes a big difference, you know it is worth cutting down the drawing calls.

Similarly, to check if the bez function is the problem, replace it temporarily with some constant values to see the difference.

If you go through your code like this, taking things out and testing, you should identify the speed problem.

Thanks for your help! I’ll try step by step and see what can make a difference.

@Kotas btw you can automatically combine all the tabs into a single item on the clipboard by long-pressing the project on the project screen and selecting “copy”

I made a few small improvements. The biggest one, I think, is that instead of effectively remaking the mesh every frame, then it sets up the right number of rectangles and adjusts them. It’s not a huge gain, but it gains a few fps.

Other changes: shifted the function caching outside the draw function since doing it inside the draw function doesn’t save anything (you’re still doing the lookup when making the cache so if you only use it once, you’ve not saved any time). I’ve also cleaned up your use of vec2s: the point of using vec2s is to use their native methods. The last main change is to your for loop: you should loop over integers and adjust afterwards.

(Oh, and your FPS code was missing so I just put in a simple watch.)

That done, it’s still not much of an improvement. Much more is possible if you shift your code to a shader. Your code settles at about 90 sparkles, and each will on average have 10 trails. So there will be about 900 rectangles in your mesh. My explosion shader does something quite similar to what you’re doing, but with 9000 rectangles then the frame rate doesn’t drop at all. You can find an explanation of my explosion shader here.

``````function setup()
show_poles = nil
length = 200    -- tail length
dA = math.pi/18         -- angle variation
sA = math.pi/4         -- starting angle
respawn = 0.01   -- respawn frequence in seconds
sparkles = {}
spawncounter=0

-- setup FPS for debugging
local x,y = WIDTH-30,HEIGHT-20
local col = color(255)
-- FPS(nil,col,x,y)
parameter.watch("math.floor(1/DeltaTime)")
parameter.watch("#sparkles")
end

function draw()
-- This sets a dark background color
background(40, 40, 50)

-- This sets the line thickness
strokeWidth(5)

spawncounter = spawncounter + DeltaTime
if spawncounter>respawn then
table.insert(sparkles,spark(length,dA,sA,"random"))
spawncounter=0
end
local rem,n = {},0
for k,s in ipairs(sparkles) do
if s.active then
s:draw()
else
table.insert(rem,k)
n = n + 1
end
end
for k=n,1,-1 do
table.remove(sparkles,rem[k])
end
end

-- cubic bezier
function bezier(p0,p1,p2,p3,t)
--[[
local c = 3*p1 - p0
local b = 3*(p2-p1) - c
local a = p3-p0-c-b

local p1=((a*mu +b)*mu +c)*mu +p0
mu = mu + delta
local p2=((a*mu +b)*mu +c)*mu +p0
--]]
local s = 1-t

return t*t*t*p3 +3*s*t*t*p2 + 3*s*s*t*p1 + s*s*s*p0
end

spark = class()

local random = math.random
local cos = math.cos
local sin = math.sin
local pi = math.pi
local atan2 = math.atan2
local sqrt = math.sqrt
local abs = math.abs
local bez = bezier
local maxAlpha = 200    -- max alpha at i = 0.5, max alpha value = 90

function spark:init(length,dA,sA,mode,p0,p1,p2,p3)
self.m = mesh()
local angle = sA or random()*2*pi -- starting angle
self.n = random(1,20)  -- tail length
for k=1,self.n do
end

--define bezier points
if mode == "random" then
local margin = 100
self.p0 = vec2 (
random(0-margin,WIDTH+margin),
random(0-margin,HEIGHT+margin)
)
angle = angle + 2*random()*dA-dA
self.p1 = self.p0 + vec2(length,0):rotate(angle)
angle = angle + 2*random()*dA-dA
self.p2 = self.p1 + vec2(length,0):rotate(angle)
angle = angle + 2*random()*dA-dA
self.p3 = self.p2 + vec2(length,0):rotate(angle)
else
self.p0 = p0
self.p1 = p1
self.p2 = p2
self.p3 = p3
end

self.time = ElapsedTime
self.active = true
end

function spark:draw()
local p0 = self.p0
local p1 = self.p1
local p2 = self.p2
local p3 = self.p3
local n = self.n

if show_poles then
pushStyle()
fill(255, 0, 0, 100)
ellipse(p0.x,p0.y,8)
ellipse(p1.x,p1.y,8)
ellipse(p2.x,p2.y,8)
ellipse(p3.x,p3.y,8)
strokeWidth(1)
stroke(255,0,0,100)
line(p0.x,p0.y,p1.x,p1.y)
line(p1.x,p1.y,p2.x,p2.y)
line(p2.x,p2.y,p3.x,p3.y)
popStyle()
end

local i = 100*(ElapsedTime - self.time) / 4 -- sparkle life ... 4 seconds
local alpha,s,f,d,a
if i < 100 then
for k = 1,n do
if i+k < 100 then
alpha = k/n*maxAlpha*(-abs(0.5-i/100)*2+1)
s,f = bez(p0,p1,p2,p3,(i+k)/100),bez(p0,p1,p2,p3,(i+k+1)/100)
d = s:dist(f)
a = s:angleBetween(f)
self.m:setRect(k,s.x + d / 2, s.y + d / 2, d, 2, a)
self.m:setRectColor(k,22,141,175,alpha)
else
self.m:setRect(k,0,0,0,0)
end
end
self.m:draw()
else
self.active = nil
end
end
``````

nice?like meteor firework

@loopspace … thanks. I used your version and I see actually improvements even though as you said it’s not terrific.
I guess my next step is to look at shaders. I’m a beginner in programming so understanding shaders my take a while but your example seems pretty well explained.
I’ll give it a try!

The shader ebook here may help

https://www.dropbox.com/sh/mr2yzp07vffskxt/AACqVnmzpAKOkNDWENPmN4psa

I have tried some additional adjustment but at the end the main driver for speed is the for loop (with k parameter). If the step is changed from 1 to anything above the fps will increase substantially but I guess there is no easy way to optmize this.
So I’ll definitely need some study on shaders.
@Ignatz thanks for your ebook. I’ll do some training/learning on shaders and then I’ll try to solve this one.

Just a little thing: I changed the

``````a = s:angleBetween(f)
``````

to

``````a = math.atan2(f.y - s.y, f.x - sx)
``````

so that visually is better. Don’t know if there is a smarter way.

Ah yes, I should have put:

``````                    a = vec2(1,0):angleBetween(f-s)
``````