|
| 1 | +# The Particle System |
| 2 | + |
| 3 | +class ParticleSystem |
| 4 | + include Processing::Proxy |
| 5 | + |
| 6 | + attr_reader :particles, :particle_shape |
| 7 | + |
| 8 | + def initialize(width, height, sprite, n) |
| 9 | + @particles = [] |
| 10 | + # The PShape is a group |
| 11 | + @particle_shape = create_shape(GROUP) |
| 12 | + |
| 13 | + # Make all the Particles |
| 14 | + n.times do |i| |
| 15 | + particles << Particle.new(width, height, sprite) |
| 16 | + # Each particle's PShape gets added to the System PShape |
| 17 | + particle_shape.add_child(particles[i].s_shape) |
| 18 | + end |
| 19 | + end |
| 20 | + |
| 21 | + def update |
| 22 | + particles.each do |p| |
| 23 | + p.update |
| 24 | + end |
| 25 | + end |
| 26 | + |
| 27 | + def set_emitter(x, y) |
| 28 | + particles.each do |p| |
| 29 | + # Each particle gets reborn at the emitter location |
| 30 | + p.rebirth(x, y) if (p.dead?) |
| 31 | + end |
| 32 | + end |
| 33 | + |
| 34 | + def display |
| 35 | + shape(particle_shape) |
| 36 | + end |
| 37 | +end |
| 38 | + |
| 39 | +# An individual Particle |
| 40 | + |
| 41 | +class Particle |
| 42 | + include Processing::Proxy |
| 43 | + |
| 44 | + GRAVITY = Vec2D.new(0, 0.1) |
| 45 | + |
| 46 | + attr_reader :center, :velocity, :lifespan, :s_shape, :part_size, |
| 47 | + :boundary_x, :boundary_y, :sprite |
| 48 | + |
| 49 | + def initialize width, height, sprite |
| 50 | + @sprite = sprite |
| 51 | + @boundary_x = Boundary.new(0, width) |
| 52 | + @boundary_y = Boundary.new(0, height) |
| 53 | + part_size = rand(10..60) |
| 54 | + # The particle is a textured quad |
| 55 | + @s_shape = create_shape |
| 56 | + s_shape.begin_shape(QUAD) |
| 57 | + s_shape.no_stroke |
| 58 | + s_shape.texture(sprite) |
| 59 | + s_shape.normal(0, 0, 1) |
| 60 | + s_shape.vertex(-part_size / 2.0, -part_size / 2.0, 0, 0) |
| 61 | + s_shape.vertex(+part_size / 2.0, -part_size / 2.0, sprite.width, 0) |
| 62 | + s_shape.vertex(+part_size / 2.0, +part_size / 2.0, sprite.width, sprite.height) |
| 63 | + s_shape.vertex(-part_size / 2.0, +part_size / 2.0, 0, sprite.height) |
| 64 | + s_shape.end_shape |
| 65 | + # Initialize center vector |
| 66 | + @center = Vec2D.new |
| 67 | + # Set the particle starting location |
| 68 | + rebirth(width / 2.0, height / 2.0) |
| 69 | + end |
| 70 | + |
| 71 | + def rebirth(x, y) |
| 72 | + theta = rand(-PI .. PI) |
| 73 | + speed = rand(0.5 .. 4) |
| 74 | + # A velocity with random angle and magnitude |
| 75 | + @velocity = Vec2D.from_angle(theta) |
| 76 | + @velocity *= speed |
| 77 | + # Set lifespan |
| 78 | + @lifespan = 255 |
| 79 | + # Set location using translate |
| 80 | + s_shape.reset_matrix |
| 81 | + s_shape.translate(x, y) |
| 82 | + # Update center vector |
| 83 | + center.x, center.y = x, y |
| 84 | + end |
| 85 | + |
| 86 | + # Is it off the screen, or its lifespan is over? |
| 87 | + def dead? |
| 88 | + return true if lifespan < 0 |
| 89 | + return true if boundary_y.exclude? center.y |
| 90 | + return true if boundary_x.exclude? center.x |
| 91 | + false |
| 92 | + end |
| 93 | + |
| 94 | + def update |
| 95 | + # Decrease life |
| 96 | + @lifespan = lifespan - 1 |
| 97 | + # Apply gravity |
| 98 | + @velocity += GRAVITY |
| 99 | + s_shape.set_tint(color(255, lifespan)) |
| 100 | + # Move the particle according to its velocity |
| 101 | + s_shape.translate(velocity.x, velocity.y) |
| 102 | + # and also update the center location |
| 103 | + @center += velocity |
| 104 | + end |
| 105 | +end |
| 106 | + |
| 107 | +# unusually in this case we are looking for excluded values |
| 108 | + |
| 109 | +Boundary = Struct.new(:lower, :upper) do |
| 110 | + def exclude? val |
| 111 | + true unless (lower...upper).cover? val |
| 112 | + end |
| 113 | +end |
0 commit comments