Files
prosperon/particles2d.cm
2026-01-19 18:57:25 -06:00

141 lines
3.6 KiB
Plaintext

// particles2d.cm - Particle system factory
//
// Creates particle data objects that register with film2d
// Also provides emitter update logic
var film2d = use('film2d')
var random = use('random').random
var particles2d_proto = {
type: 'particles',
destroy: function() {
film2d.unregister(this._id)
}
}
// Emitter management
var emitters = {
// Spawn a particle for an emitter
spawn: function(emitter) {
emitter.particles.push({
pos: {
x: emitter.pos.x + (random() - 0.5) * emitter.spawn_area.width,
y: emitter.pos.y + (random() - 0.5) * emitter.spawn_area.height
},
velocity: {
x: emitter.velocity.x + (random() - 0.5) * emitter.velocity_var.x,
y: emitter.velocity.y + (random() - 0.5) * emitter.velocity_var.y
},
life: emitter.life,
time: 0,
max_scale: emitter.scale + (random() - 0.5) * emitter.scale_var,
scale: 0,
color: {
r: emitter.color.r,
g: emitter.color.g + (random() - 0.5) * 0.3,
b: emitter.color.b,
a: 1
}
})
},
// Update an emitter and its particles
update: function(emitter, dt) {
// Spawn new particles
if (emitter.rate > 0) {
emitter.spawn_timer = (emitter.spawn_timer || 0) + dt
var pp = 1 / emitter.rate
while (emitter.spawn_timer > pp) {
emitter.spawn_timer -= pp
emitters.spawn(emitter)
}
}
// Update existing particles
for (var i = length(emitter.particles) - 1; i >= 0; i--) {
var p = emitter.particles[i]
p.time += dt
p.pos.x += p.velocity.x * dt
p.pos.y += p.velocity.y * dt
// Scale animation
var grow_for = emitter.grow_for || 0.3
var shrink_for = emitter.shrink_for || 0.5
if (p.time < grow_for) {
p.scale = lerp(0, p.max_scale, p.time / grow_for)
} else if (p.time > p.life - shrink_for) {
p.scale = lerp(0, p.max_scale, (p.life - p.time) / shrink_for)
} else {
p.scale = p.max_scale
}
// Alpha fade
var alpha = 1
if (p.time > p.life * 0.7) {
alpha = 1 - (p.time - p.life * 0.7) / (p.life * 0.3)
}
p.color.a = alpha
// Remove dead particles
if (p.time >= p.life) {
emitter.particles.splice(i, 1)
}
}
// Sync to film2d if handle provided
if (emitter.handle) {
emitter.handle.particles = emitter.particles
}
},
// Create an emitter config
create: function(config) {
return {
pos: config.pos || {x: 0, y: 0},
spawn_area: config.spawn_area || {width: 10, height: 10},
velocity: config.velocity || {x: 0, y: 100},
velocity_var: config.velocity_var || {x: 20, y: 20},
life: config.life || 2,
rate: config.rate || 10,
scale: config.scale || 1,
scale_var: config.scale_var || 0.3,
grow_for: config.grow_for || 0.3,
shrink_for: config.shrink_for || 0.5,
color: config.color || {r: 1, g: 1, b: 1, a: 1},
spawn_timer: 0,
particles: [],
handle: config.handle || null
}
}
}
function lerp(a, b, t) { return a + (b - a) * t }
// Factory function - auto-registers with film2d
var factory = function(props) {
var defaults = {
type: 'particles',
pos: {x: 0, y: 0},
image: null,
width: 16,
height: 16,
plane: 'default',
layer: 0,
groups: [],
particles: [],
opacity: 1,
tint: {r: 1, g: 1, b: 1, a: 1}
}
var data = object(defaults, props)
var newparticles = meme(particles2d_proto, data)
film2d.register(newparticles)
return newparticles
}
// Attach emitter helpers to factory
factory.emitters = emitters
return factory