// 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) { push(emitter.particles, { 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 } emitter.particles = filter(emitter.particles, p => p.time < p.life) // 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