fpv/source/libraries/pdParticles.lua
2024-06-01 16:52:11 +03:00

626 lines
17 KiB
Lua

local particles = {}
local precision = 1
-- base particle class
class("Particle").extends()
function Particle:init(x, y)
self.x = x or 0
self.y = y or 0
self.size = {1,1}
self.spread = {0,359}
self.speed = {1,1}
self.acceleration = {0, 0}
self.thickness = {0,0}
self.lifespan = {1,1}
self.decay = 1
self.particles = {}
self.colour = playdate.graphics.kColorBlack
self.bounds = {0,0,0,0}
self.mode = 0
if self.type == 2 then -- polys
self.points={3,3}
self.angular={0,0}
self.rotation={0,359}
elseif self.type == 3 then -- images
self.angular={0,0}
self.image = playdate.graphics.image.new(1,1)
self.table = nil
self.rotation = {0,359}
end
particles[#particles+1] = self
end
function Particle:remove()
for i = 1, #particles, 1 do
if particles[i] == self then
table.remove(particles, i)
break
end
end
end
-- [[ SETTERS AND GETTERS ]] --
-- movement
function Particle:moveTo(x,y)
self.x = x
self.y = y
end
function Particle:moveBy(x,y)
self.x += x
self.y += y
end
function Particle:getPos()
return self.x, self.y
end
-- size
function Particle:setSize(min, max)
self.size = {min, max or min}
end
function Particle:getSize()
return self.size[1], self.size[2]
end
-- mode
function Particle:setMode(mode)
self.mode = mode
end
function Particle:getMode()
return self.mode
end
-- spread
function Particle:setSpread(min,max)
self.spread = {min, max or min}
end
function Particle:getSpread()
return self.spread[1], self.spread[2]
end
-- speed
function Particle:setSpeed(min,max)
self.speed = {min, max or min}
end
function Particle:getSpeed()
return self.speed[1], self.speed[2]
end
-- acceleration
function Particle:setAcceleration(min,max)
self.acceleration = {min, max or min}
end
function Particle:getAcceleration()
return self.acceleration[1], self.acceleration[2]
end
-- thickness
function Particle:setThickness(min, max)
self.thickness = {min, max or min}
end
function Particle:getThickness()
return self.thickness[1], self.thickness[2]
end
-- lifespan
function Particle:setLifespan(min,max)
self.lifespan = {min, max or min}
end
function Particle:getLifespan()
return self.lifespan[1], self.lifespan[2]
end
-- colour
function Particle:setColor(colour)
self.colour = colour
end
function Particle:getColor()
return self.colour
end
function Particle:setColour(colour)
self.colour = colour
end
function Particle:getColour()
return self.colour
end
-- bounds
function Particle:setBounds(x1, y1, x2, y2)
self.bounds = {x1,y1,x2,y2}
end
function Particle:getBounds()
return self.bounds[1],self.bounds[2],self.bounds[3],self.bounds[4]
end
-- decay
function Particle:setDecay(decay)
self.decay = decay
end
function Particle:getDecay()
return self.decay
end
-- particles
function Particle:getParticles()
return self.particles
end
function Particle:clearParticles()
self.particles = {}
end
function Particle:update()
end
-- [[ PARTICLE MODES ]] --
local function decay(partlist, decay)
for part = 1, #partlist, 1 do
local particle = partlist[part]
particle.size -= decay
if particle.size <= 0 then
particle.size = 0
end
partlist[part] = particle
end
for part = #partlist, 1, -1 do
local particle = partlist[part]
if particle.size <= 0 then
table.remove(partlist,part)
end
end
return partlist
end
local function disappear(partlist)
for part = 1, #partlist, 1 do
local particle = partlist[part]
particle.lifespan -= .1
end
for part = #partlist, 1, -1 do
local particle = partlist[part]
if particle.lifespan <= 0 then
table.remove(partlist,part)
end
end
return partlist
end
local function loop(partlist, bounds)
if bounds[3] > bounds[1] and bounds[4] > bounds[2] then
local xDif , yDif = bounds[3] - bounds[1], bounds[4] - bounds[2]
for part = 1, #partlist, 1 do
local particle = partlist[part]
if particle.x > bounds[3] then particle.x -= xDif
elseif particle.x < bounds[1] then particle.x += xDif end
if particle.y > bounds[4] then particle.y -= yDif
elseif particle.y < bounds[2] then particle.y += yDif end
end
end
return partlist
end
local function stay(partlist, bounds)
if bounds[3] > bounds[1] and bounds[4] > bounds[2] then
local xDif , yDif = bounds[3] - bounds[1], bounds[4] - bounds[2]
for part = #partlist, 1, -1 do
local particle = partlist[part]
if particle.x > bounds[3] then table.remove(partlist,part)
elseif particle.x < bounds[1] then table.remove(partlist,part)
elseif particle.y > bounds[4] then table.remove(partlist,part)
elseif particle.y < bounds[2] then table.remove(partlist,part) end
end
end
return partlist
end
class("ParticleCircle", {type = 1}).extends(Particle)
function ParticleCircle:create(amount)
for i = 1, amount, 1 do
local part = {
x = self.x,
y = self.y,
dir = math.random(self.spread[1],self.spread[2]),
size = math.random(self.size[1],self.size[2]) * precision,
speed = math.random(self.speed[1],self.speed[2]) * precision,
acceleration = math.random(self.acceleration[1],self.acceleration[2]) * precision,
lifespan = math.random(self.lifespan[1],self.lifespan[2]) * precision,
thickness = math.random(self.thickness[1],self.thickness[2]),
decay = self.decay
}
self.particles[#self.particles+1] = part
end
end
function ParticleCircle:add(amount)
self:create(amount)
end
function ParticleCircle:update()
local w = playdate.graphics.getLineWidth()
local c = playdate.graphics.getColor()
playdate.graphics.setColor(self.colour)
for part = 1, #self.particles, 1 do
local circ = self.particles[part]
if circ.thickness < 1 then
playdate.graphics.fillCircleAtPoint(circ.x,circ.y,circ.size)
else
playdate.graphics.setLineWidth(circ.thickness)
playdate.graphics.drawCircleAtPoint(circ.x, circ.y, circ.size)
end
circ.x += math.sin(math.rad(circ.dir)) * circ.speed
circ.y -= math.cos(math.rad(circ.dir)) * circ.speed
circ.speed += circ.acceleration / 100
self.particles[part] = circ
end
playdate.graphics.setLineWidth(w)
playdate.graphics.setColor(c)
if self.mode == 1 then
decay(self.particles, self.decay)
elseif self.mode == 0 then
disappear(self.particles)
elseif self.mode == 2 then
loop(self.particles, self.bounds)
else
stay(self.particles, self.bounds)
end
end
class("ParticlePoly", {type = 2}).extends(Particle)
function ParticlePoly:getPoints()
return self.points[1], self.points[2]
end
function ParticlePoly:setPoints(min,max)
self.points = {min, max or min}
end
-- angular
function ParticlePoly:getAngular()
return self.angular[1], self.angular[2]
end
function ParticlePoly:setAngular(min,max)
self.angular = {min, max or min}
end
function ParticlePoly:getRotation()
return self.rotation[1], self.rotation[2]
end
function ParticlePoly:setRotation(min,max)
self.rotation = {min, max or min}
end
function ParticlePoly:create(amount)
for i = 1, amount, 1 do
local part = {
x = self.x,
y = self.y,
dir = math.random(self.spread[1],self.spread[2]),
size = math.random(self.size[1],self.size[2]) * precision,
speed = math.random(self.speed[1],self.speed[2]) * precision,
acceleration = math.random(self.acceleration[1],self.acceleration[2]) * precision,
lifespan = math.random(self.lifespan[1],self.lifespan[2]) * precision,
thickness = math.random(self.thickness[1],self.thickness[2]) * precision,
angular = math.random(self.angular[1],self.angular[2]) * precision,
points = math.random(self.points[1], self.points[2]),
decay = self.decay,
rotation = math.random(self.rotation[1],self.rotation[2])
}
self.particles[#self.particles+1] = part
end
end
function ParticlePoly:add(amount)
self:create(amount)
end
function ParticlePoly:update()
local w = playdate.graphics.getLineWidth()
local c = playdate.graphics.getColor()
playdate.graphics.setColor(self.colour)
for part = 1, #self.particles, 1 do
local poly = self.particles[part]
local polygon = {}
local degrees = 360 / poly.points
for point = 1, poly.points, 1 do
polygon[#polygon+1] = poly.x + math.sin(math.rad(degrees * point + poly.rotation)) * poly.size
polygon[#polygon+1] = poly.y - math.cos(math.rad(degrees * point + poly.rotation)) * poly.size
end
if poly.thickness < 1 then
playdate.graphics.fillPolygon(table.unpack(polygon))
else
playdate.graphics.setLineWidth(poly.thickness)
playdate.graphics.drawPolygon(table.unpack(polygon))
end
poly.x += math.sin(math.rad(poly.dir)) * poly.speed
poly.y = poly.y - math.cos(math.rad(poly.dir)) * poly.speed
poly.rotation += poly.angular
poly.speed += poly.acceleration / 100
self.particles[part] = poly
end
playdate.graphics.setLineWidth(w)
playdate.graphics.setColor(c)
if self.mode == 1 then
decay(self.particles, self.decay)
elseif self.mode == 0 then
disappear(self.particles)
elseif self.mode == 2 then
loop(self.particles, self.bounds)
else
stay(self.particles, self.bounds)
end
end
class("ParticleImage", {type = 3}).extends(Particle)
function ParticleImage:getAngular()
return self.angular[1], self.angular[2]
end
function ParticleImage:setAngular(min,max)
self.angular = {min, max or min}
end
function ParticleImage:getRotation()
return self.rotation[1], self.rotation[2]
end
function ParticleImage:setRotation(min,max)
self.rotation = {min, max or min}
end
function ParticleImage:setImage(image)
self.image = image
self.table = nil
end
function ParticleImage:setImageTable(image)
self.image = nil
self.table = image
end
function ParticleImage:getImage()
return self.image
end
function ParticleImage:getImageTable()
return self.table
end
function ParticleImage:create(amount)
if self.image ~= nil then
for i = 1, amount, 1 do
local part = {
x = self.x,
y = self.y,
dir = math.random(self.spread[1],self.spread[2]),
size = math.random(self.size[1],self.size[2]) * precision,
speed = math.random(self.speed[1],self.speed[2]) * precision,
acceleration = math.random(self.acceleration[1],self.acceleration[2]) * precision,
lifespan = math.random(self.lifespan[1],self.lifespan[2]) * precision,
thickness = math.random(self.thickness[1],self.thickness[2]),
angular = math.random(self.angular[1],self.angular[2]) * precision,
decay = self.decay,
image = self.image,
rotation = math.random(self.rotation[1],self.rotation[2])
}
self.particles[#self.particles+1] = part
end
else
for i = 1, amount, 1 do
local part = {
x = self.x,
y = self.y,
dir = math.random(self.spread[1],self.spread[2]),
size = math.random(self.size[1],self.size[2]) * precision,
speed = math.random(self.speed[1],self.speed[2]) * precision,
acceleration = math.random(self.acceleration[1],self.acceleration[2]) * precision,
lifespan = math.random(self.lifespan[1],self.lifespan[2]) * precision,
thickness = math.random(self.thickness[1],self.thickness[2]),
angular = math.random(self.angular[1],self.angular[2]) * precision,
decay = self.decay,
image = self.table[math.random(#self.table)],
rotation = math.random(self.rotation[1],self.rotation[2])
}
self.particles[#self.particles+1] = part
end
end
end
function ParticleImage:add(amount)
self:create(amount)
end
function ParticleImage:update()
for part = 1, #self.particles, 1 do
local img = self.particles[part]
img.image:drawRotated(img.x,img.y,img.rotation,img.size)
img.rotation += img.angular
img.x += math.sin(math.rad(img.dir)) * img.speed
img.y = img.y - math.cos(math.rad(img.dir)) * img.speed
img.speed += img.acceleration / 100
self.particles[part] = img
end
if self.mode == 1 then
decay(self.particles, self.decay)
elseif self.mode == 0 then
disappear(self.particles)
elseif self.mode == 2 then
loop(self.particles, self.bounds)
else
stay(self.particles, self.bounds)
end
end
class("ParticleImageBasic", {type = 3}).extends(ParticleImage)
function ParticleImageBasic:update()
for part = 1, #self.particles, 1 do
local img = self.particles[part]
img.image:drawScaled(img.x,img.y,img.size)
img.rotation += img.angular
img.x += math.sin(math.rad(img.dir)) * img.speed
img.y = img.y - math.cos(math.rad(img.dir)) * img.speed
img.speed += img.acceleration / 100
self.particles[part] = img
end
if self.mode == 1 then
decay(self.particles, self.decay)
elseif self.mode == 0 then
disappear(self.particles)
elseif self.mode == 2 then
loop(self.particles, self.bounds)
else
stay(self.particles, self.bounds)
end
end
class("ParticlePixel").extends(Particle)
function ParticlePixel:create(amount)
for i = 1, amount, 1 do
local part = {
x = self.x,
y = self.y,
dir = math.random(self.spread[1],self.spread[2]),
speed = math.random(self.speed[1],self.speed[2]) * precision,
acceleration = math.random(self.acceleration[1],self.acceleration[2]) * precision,
lifespan = math.random(self.lifespan[1],self.lifespan[2]) * precision,
}
self.particles[#self.particles+1] = part
end
end
function ParticlePixel:add(amount)
self:create(amount)
end
function ParticlePixel:update()
local c = playdate.graphics.getColor()
playdate.graphics.setColor(self.colour)
for part = 1, #self.particles, 1 do
local pix = self.particles[part]
playdate.graphics.drawPixel(pix.x,pix.y,pix.size)
pix.x += math.sin(math.rad(pix.dir)) * pix.speed
pix.y -= math.cos(math.rad(pix.dir)) * pix.speed
pix.speed += pix.acceleration / 100
self.particles[part] = pix
end
playdate.graphics.setColor(c)
if self.mode == 0 then
disappear(self.particles)
elseif self.mode == 2 then
loop(self.particles, self.bounds)
else
stay(self.particles, self.bounds)
end
end
-- [[ GLOBAL PARTICLE STUFF ]] --
class("Particles").extends()
Particles.modes = {DISAPPEAR = 0, DECAY = 1, LOOP = 2, STAY = 3}
function Particles:update()
for particle = 1, #particles, 1 do
particles[particle]:update()
end
end
function Particles:clearAll()
for part = 1, #particles, 1 do
particles[part]:clearParticles()
end
end
function Particles:removeAll()
local lb = #particles
for part = 1, #particles, 1 do
particles[part] = nil
end
end
function Particles:setPrecision(prec)
precision = prec
end
function Particles:getPrecision()
return precision
end