Class: Quake::Renderer::GLParticles
- Inherits:
-
Object
- Object
- Quake::Renderer::GLParticles
- Defined in:
- lib/quake/renderer/gl_particles.rb
Overview
Simple particle system for visual effects. Matches Quake's GL R_DrawParticles (r_part.c).
Defined Under Namespace
Classes: Particle
Constant Summary collapse
- GRAVITY =
800.0- MAX_PARTICLES =
2048- PARTICLE_TEXTURE_SIZE =
8- PARTICLE_BILLBOARD_SIZE =
1.5- PARTICLE_SCALE_DISTANCE =
20.0- PARTICLE_SCALE_FACTOR =
0.004- DOT_TEXTURE =
[ [0, 1, 1, 0, 0, 0, 0, 0], [1, 1, 1, 1, 0, 0, 0, 0], [1, 1, 1, 1, 0, 0, 0, 0], [0, 1, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0] ].freeze
- RAMP1 =
Quake ramp tables for color animation (palette indices)
[0x6f, 0x6d, 0x6b, 0x69, 0x67, 0x65, 0x63, 0x61].freeze
- RAMP2 =
[0x6f, 0x6e, 0x6d, 0x6c, 0x6b, 0x6a, 0x68, 0x66].freeze
- RAMP3 =
explosion
[0x6d, 0x6b, 0x06, 0x05, 0x04, 0x03, 0x00, 0x00].freeze
Instance Method Summary collapse
-
#blob_explosion(pos) ⇒ Object
Quake temp entity TE_TAREXPLOSION: tarbaby blob explosion.
-
#blood(pos, count: 20) ⇒ Object
QuakeWorld TE_BLOOD uses R_RunParticleEffect with blood palette color 73.
-
#color_mapped_explosion(pos, color_start:, color_length:) ⇒ Object
Quake temp entity TE_EXPLOSION2: color-mapped particle explosion.
-
#entity_particles(pos, time:) ⇒ Object
Quake R_EntityParticles: EF_BRIGHTFIELD particles orbit each alias normal.
-
#explosion(pos) ⇒ Object
Explosion burst.
-
#gunshot(pos, count: 20) ⇒ Object
Quake temp entity TE_GUNSHOT: a small wall puff at bullet impact.
-
#initialize(palette) ⇒ GLParticles
constructor
A new instance of GLParticles.
-
#lava_splash(pos) ⇒ Object
Quake temp entity TE_LAVASPLASH.
-
#lightning_blood(pos) ⇒ Object
QuakeWorld TE_LIGHTNINGBLOOD.
- #particle_count ⇒ Object
-
#pickup_effect(pos) ⇒ Object
Item pickup sparkle.
- #render(camera: nil) ⇒ Object
-
#rocket_trail(start_pos, end_pos, type:) ⇒ Object
Quake R_RocketTrail: projectile, blood, tracer, and voor trails.
-
#run_particle_effect(pos, color:, count:, dir: nil) ⇒ Object
Quake R_RunParticleEffect: used by gunshots and spike impact puffs.
-
#teleport_splash(pos) ⇒ Object
Teleporter sparkle effect at a position.
- #update(dt) ⇒ Object
Constructor Details
#initialize(palette) ⇒ GLParticles
Returns a new instance of GLParticles.
38 39 40 41 |
# File 'lib/quake/renderer/gl_particles.rb', line 38 def initialize(palette) @palette = palette @particles = [] end |
Instance Method Details
#blob_explosion(pos) ⇒ Object
Quake temp entity TE_TAREXPLOSION: tarbaby blob explosion.
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/quake/renderer/gl_particles.rb', line 273 def blob_explosion(pos) 1024.times do |i| color_idx = i.odd? ? 66 + (rand * 6).to_i : 150 + (rand * 6).to_i color = @palette.rgb(color_idx) emit( x: pos.x + (rand * 32).to_i - 16, y: pos.y + (rand * 32).to_i - 16, z: pos.z + (rand * 32).to_i - 16, vx: (rand * 512).to_i - 256, vy: (rand * 512).to_i - 256, vz: (rand * 512).to_i - 256, r: color[0] / 255.0, g: color[1] / 255.0, b: color[2] / 255.0, life: 1.0 + (((rand * 256).to_i & 8) * 0.05), ramp_type: i.odd? ? :blob : :blob2 ) end end |
#blood(pos, count: 20) ⇒ Object
QuakeWorld TE_BLOOD uses R_RunParticleEffect with blood palette color 73.
292 293 294 |
# File 'lib/quake/renderer/gl_particles.rb', line 292 def blood(pos, count: 20) run_particle_effect(pos, color: 73, count: count) end |
#color_mapped_explosion(pos, color_start:, color_length:) ⇒ Object
Quake temp entity TE_EXPLOSION2: color-mapped particle explosion.
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 |
# File 'lib/quake/renderer/gl_particles.rb', line 252 def color_mapped_explosion(pos, color_start:, color_length:) color_mod = 0 512.times do color_idx = color_start + (color_mod % color_length) color_mod += 1 color = @palette.rgb(color_idx) emit( x: pos.x + (rand * 32).to_i - 16, y: pos.y + (rand * 32).to_i - 16, z: pos.z + (rand * 32).to_i - 16, vx: (rand * 512).to_i - 256, vy: (rand * 512).to_i - 256, vz: (rand * 512).to_i - 256, r: color[0] / 255.0, g: color[1] / 255.0, b: color[2] / 255.0, life: 0.3, ramp_type: :blob ) end end |
#entity_particles(pos, time:) ⇒ Object
Quake R_EntityParticles: EF_BRIGHTFIELD particles orbit each alias normal.
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/quake/renderer/gl_particles.rb', line 111 def entity_particles(pos, time:) entity_particle_avelocities.each_with_index do |velocities, index| yaw = time.to_f * velocities[0] pitch = time.to_f * velocities[1] roll = time.to_f * velocities[2] sy = ::Math.sin(yaw) cy = ::Math.cos(yaw) sp = ::Math.sin(pitch) cp = ::Math.cos(pitch) sr = ::Math.sin(roll) cr = ::Math.cos(roll) forward = [cp * cy, cp * sy, -sp] normal = GLAliasModel::ANORMS[index] color = @palette.rgb(0x6f) emit( x: pos.x + normal[0] * 64.0 + forward[0] * 16.0, y: pos.y + normal[1] * 64.0 + forward[1] * 16.0, z: pos.z + normal[2] * 64.0 + forward[2] * 16.0, vx: 0.0, vy: 0.0, vz: 0.0, r: color[0] / 255.0, g: color[1] / 255.0, b: color[2] / 255.0, life: 0.01, ramp_type: :explosion ) end end |
#explosion(pos) ⇒ Object
Explosion burst
245 246 247 248 249 |
# File 'lib/quake/renderer/gl_particles.rb', line 245 def explosion(pos) 1024.times do |i| emit_explosion_particle(pos, index: i) end end |
#gunshot(pos, count: 20) ⇒ Object
Quake temp entity TE_GUNSHOT: a small wall puff at bullet impact.
328 329 330 |
# File 'lib/quake/renderer/gl_particles.rb', line 328 def gunshot(pos, count: 20) run_particle_effect(pos, color: 0, count: count) end |
#lava_splash(pos) ⇒ Object
Quake temp entity TE_LAVASPLASH.
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/quake/renderer/gl_particles.rb', line 199 def lava_splash(pos) (-16...16).each do |i| (-16...16).each do |j| color_idx = 224 + (rand * 8).to_i color = @palette.rgb(color_idx) dir_x = j * 8.0 + (rand * 8).to_i dir_y = i * 8.0 + (rand * 8).to_i dir_z = 256.0 length = ::Math.sqrt(dir_x * dir_x + dir_y * dir_y + dir_z * dir_z) vel = 50.0 + (rand * 64).to_i scale = vel / length emit( x: pos.x + dir_x, y: pos.y + dir_y, z: pos.z + (rand * 64).to_i, vx: dir_x * scale, vy: dir_y * scale, vz: dir_z * scale, r: color[0] / 255.0, g: color[1] / 255.0, b: color[2] / 255.0, life: 2.0 + (rand * 32).to_i * 0.02, gravity_scale: 1.0 ) end end end |
#lightning_blood(pos) ⇒ Object
QuakeWorld TE_LIGHTNINGBLOOD.
297 298 299 |
# File 'lib/quake/renderer/gl_particles.rb', line 297 def lightning_blood(pos) run_particle_effect(pos, color: 225, count: 50) end |
#particle_count ⇒ Object
332 333 334 |
# File 'lib/quake/renderer/gl_particles.rb', line 332 def particle_count @particles.size end |
#pickup_effect(pos) ⇒ Object
Item pickup sparkle
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/quake/renderer/gl_particles.rb', line 226 def pickup_effect(pos) 20.times do angle = rand * ::Math::PI * 2.0 speed = 50.0 + rand * 50.0 color = @palette.rgb(0x6f) # orange/yellow emit( x: pos.x, y: pos.y, z: pos.z + 16.0, vx: ::Math.cos(angle) * speed, vy: ::Math.sin(angle) * speed, vz: 80.0 + rand * 60.0, r: color[0] / 255.0, g: color[1] / 255.0, b: color[2] / 255.0, life: 0.3 + rand * 0.3, gravity_scale: 0.5, ramp_type: :fire ) end end |
#render(camera: nil) ⇒ Object
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/quake/renderer/gl_particles.rb', line 74 def render(camera: nil) return if @particles.empty? upload_particle_texture unless @particle_texture view_origin = camera&.position || Math::Vec3::ORIGIN view_forward = camera&.forward || Math::Vec3.new(1.0, 0.0, 0.0) up = (camera&.up || Math::Vec3.new(0.0, 0.0, 1.0)) * PARTICLE_BILLBOARD_SIZE right = (camera&.right || Math::Vec3.new(0.0, -1.0, 0.0)) * PARTICLE_BILLBOARD_SIZE GL.Enable(GL::TEXTURE_2D) GL.BindTexture(GL::TEXTURE_2D, @particle_texture) GL.Disable(GL::ALPHA_TEST) GL.Enable(GL::BLEND) GL.TexEnvi(GL::TEXTURE_ENV, GL::TEXTURE_ENV_MODE, GL::MODULATE) GL.BlendFunc(GL::SRC_ALPHA, GL::ONE_MINUS_SRC_ALPHA) GL.DepthMask(GL::FALSE) GL.Begin(GL::TRIANGLES) @particles.each do |p| scale = particle_scale(p, view_origin, view_forward) GL.Color3f(p.r, p.g, p.b) GL.TexCoord2f(0.0, 0.0) GL.Vertex3f(p.x, p.y, p.z) GL.TexCoord2f(1.0, 0.0) GL.Vertex3f(p.x + up.x * scale, p.y + up.y * scale, p.z + up.z * scale) GL.TexCoord2f(0.0, 1.0) GL.Vertex3f(p.x + right.x * scale, p.y + right.y * scale, p.z + right.z * scale) end GL.End GL.DepthMask(GL::TRUE) GL.Disable(GL::BLEND) GL.TexEnvi(GL::TEXTURE_ENV, GL::TEXTURE_ENV_MODE, GL::REPLACE) GL.Color3f(1.0, 1.0, 1.0) end |
#rocket_trail(start_pos, end_pos, type:) ⇒ Object
Quake R_RocketTrail: projectile, blood, tracer, and voor trails.
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 |
# File 'lib/quake/renderer/gl_particles.rb', line 139 def rocket_trail(start_pos, end_pos, type:) dir_x = end_pos.x - start_pos.x dir_y = end_pos.y - start_pos.y dir_z = end_pos.z - start_pos.z len = ::Math.sqrt(dir_x * dir_x + dir_y * dir_y + dir_z * dir_z) return if len.zero? dir_x /= len dir_y /= len dir_z /= len dec = 3.0 if type >= 128 dec = 1.0 type -= 128 end x = start_pos.x y = start_pos.y z = start_pos.z while len.positive? len -= dec emit_rocket_trail_particle(x, y, z, dir_x, dir_y, dir_z, type) len -= 3.0 if type == 4 x += dir_x y += dir_y z += dir_z end end |
#run_particle_effect(pos, color:, count:, dir: nil) ⇒ Object
Quake R_RunParticleEffect: used by gunshots and spike impact puffs.
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 |
# File 'lib/quake/renderer/gl_particles.rb', line 302 def run_particle_effect(pos, color:, count:, dir: nil) if count == 1024 count.times { |i| emit_explosion_particle(pos, index: i) } return end color_base = color & ~7 dir_x = dir ? dir.x : 0.0 dir_y = dir ? dir.y : 0.0 dir_z = dir ? dir.z : 0.0 count.times do color = @palette.rgb(color_base + (rand * 8).to_i) emit( x: pos.x + (rand * 16).to_i - 8, y: pos.y + (rand * 16).to_i - 8, z: pos.z + (rand * 16).to_i - 8, vx: dir_x * 15.0, vy: dir_y * 15.0, vz: dir_z * 15.0, r: color[0] / 255.0, g: color[1] / 255.0, b: color[2] / 255.0, life: (rand * 5).to_i * 0.1, gravity_scale: 1.0 ) end end |
#teleport_splash(pos) ⇒ Object
Teleporter sparkle effect at a position
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/quake/renderer/gl_particles.rb', line 170 def teleport_splash(pos) (-16...16).step(4) do |i| (-16...16).step(4) do |j| (-24...32).step(4) do |k| color_idx = 7 + (rand * 8).to_i color = @palette.rgb(color_idx) dir_x = j * 8.0 dir_y = i * 8.0 dir_z = k * 8.0 length = ::Math.sqrt(dir_x * dir_x + dir_y * dir_y + dir_z * dir_z) vel = 50.0 + (rand * 64).to_i scale = length.zero? ? 0.0 : vel / length emit( x: pos.x + i + (rand * 4).to_i, y: pos.y + j + (rand * 4).to_i, z: pos.z + k + (rand * 4).to_i, vx: dir_x * scale, vy: dir_y * scale, vz: dir_z * scale, r: color[0] / 255.0, g: color[1] / 255.0, b: color[2] / 255.0, life: 0.2 + (rand * 8).to_i * 0.02, gravity_scale: 1.0 ) end end end end |
#update(dt) ⇒ Object
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/quake/renderer/gl_particles.rb', line 43 def update(dt) @particles.reject! do |p| p.life -= dt next true if p.life <= 0 # Apply velocity p.x += p.vx * dt p.y += p.vy * dt p.z += p.vz * dt # Color ramp animation if p.ramp_type && (ramp = ramp_for_type(p.ramp_type)) p.ramp += dt * ramp_rate(p.ramp_type) idx = p.ramp.to_i if idx >= ramp_expiry_size(p.ramp_type, ramp) next true # particle expired end color = @palette.rgb(ramp[idx]) p.r = color[0] / 255.0 p.g = color[1] / 255.0 p.b = color[2] / 255.0 end update_velocity(p, dt) false end end |