|
| 1 | +from mewnala import * |
| 2 | +import math |
| 3 | + |
| 4 | +field_obj = None |
| 5 | +particle = None |
| 6 | +mat = None |
| 7 | +spawn = None |
| 8 | +motion = None |
| 9 | + |
| 10 | +CAPACITY = 40000 |
| 11 | +BURST = 120 |
| 12 | +DT = 1.0 / 60.0 |
| 13 | +TTL = 2.5 |
| 14 | +GRAVITY = 9.8 |
| 15 | +SPEED = 5.0 |
| 16 | + |
| 17 | +SPAWN_SHADER = """ |
| 18 | +struct Spawn { |
| 19 | + pos: vec4<f32>, |
| 20 | + speed: vec4<f32>, |
| 21 | +} |
| 22 | +
|
| 23 | +@group(0) @binding(0) var<storage, read_write> position: array<f32>; |
| 24 | +@group(0) @binding(1) var<storage, read_write> velocity: array<f32>; |
| 25 | +@group(0) @binding(2) var<storage, read_write> color: array<f32>; |
| 26 | +@group(0) @binding(3) var<storage, read_write> scale: array<f32>; |
| 27 | +@group(0) @binding(4) var<storage, read_write> age: array<f32>; |
| 28 | +@group(0) @binding(5) var<storage, read_write> dead: array<f32>; |
| 29 | +@group(0) @binding(6) var<uniform> spawn: Spawn; |
| 30 | +@group(0) @binding(7) var<uniform> emit_range: vec4<f32>; |
| 31 | +
|
| 32 | +fn hash(n: u32) -> u32 { |
| 33 | + var x = n; |
| 34 | + x = (x ^ 61u) ^ (x >> 16u); |
| 35 | + x = x + (x << 3u); |
| 36 | + x = x ^ (x >> 4u); |
| 37 | + x = x * 0x27d4eb2du; |
| 38 | + x = x ^ (x >> 15u); |
| 39 | + return x; |
| 40 | +} |
| 41 | +
|
| 42 | +fn hash_unit(n: u32) -> f32 { |
| 43 | + return f32(hash(n)) / f32(0xffffffffu); |
| 44 | +} |
| 45 | +
|
| 46 | +@compute @workgroup_size(64) |
| 47 | +fn main(@builtin(global_invocation_id) gid: vec3<u32>) { |
| 48 | + let local_i = gid.x; |
| 49 | + if local_i >= u32(emit_range.y) { return; } |
| 50 | + let base = u32(emit_range.x); |
| 51 | + let cap = u32(emit_range.z); |
| 52 | + let slot = (base + local_i) % cap; |
| 53 | +
|
| 54 | + let seed = base + local_i; |
| 55 | +
|
| 56 | + let theta = hash_unit(seed) * 6.2831853; |
| 57 | + let r = sqrt(hash_unit(seed * 2u + 1u)); |
| 58 | + let dirxz = vec2<f32>(cos(theta), sin(theta)) * r; |
| 59 | + let dy = 0.7 + 0.3 * hash_unit(seed * 3u + 7u); |
| 60 | + let v = vec3<f32>(dirxz.x, dy, dirxz.y) * spawn.speed.x; |
| 61 | +
|
| 62 | + position[slot * 3u + 0u] = spawn.pos.x; |
| 63 | + position[slot * 3u + 1u] = spawn.pos.y; |
| 64 | + position[slot * 3u + 2u] = spawn.pos.z; |
| 65 | +
|
| 66 | + velocity[slot * 3u + 0u] = v.x; |
| 67 | + velocity[slot * 3u + 1u] = v.y; |
| 68 | + velocity[slot * 3u + 2u] = v.z; |
| 69 | +
|
| 70 | + let h = fract(hash_unit(seed * 5u + 11u)); |
| 71 | + color[slot * 4u + 0u] = 0.5 + 0.5 * sin(h * 6.28); |
| 72 | + color[slot * 4u + 1u] = 0.5 + 0.5 * sin(h * 6.28 + 2.094); |
| 73 | + color[slot * 4u + 2u] = 0.5 + 0.5 * sin(h * 6.28 + 4.189); |
| 74 | + color[slot * 4u + 3u] = 1.0; |
| 75 | +
|
| 76 | + scale[slot * 3u + 0u] = 1.0; |
| 77 | + scale[slot * 3u + 1u] = 1.0; |
| 78 | + scale[slot * 3u + 2u] = 1.0; |
| 79 | +
|
| 80 | + age[slot] = 0.0; |
| 81 | + dead[slot] = 0.0; |
| 82 | +} |
| 83 | +""" |
| 84 | + |
| 85 | +MOTION_SHADER = """ |
| 86 | +struct Params { |
| 87 | + dt: f32, |
| 88 | + ttl: f32, |
| 89 | + gravity: f32, |
| 90 | + _pad: f32, |
| 91 | +} |
| 92 | +
|
| 93 | +@group(0) @binding(0) var<storage, read_write> position: array<f32>; |
| 94 | +@group(0) @binding(1) var<storage, read_write> velocity: array<f32>; |
| 95 | +@group(0) @binding(2) var<storage, read_write> scale: array<f32>; |
| 96 | +@group(0) @binding(3) var<storage, read_write> age: array<f32>; |
| 97 | +@group(0) @binding(4) var<storage, read_write> dead: array<f32>; |
| 98 | +@group(0) @binding(5) var<uniform> params: Params; |
| 99 | +
|
| 100 | +@compute @workgroup_size(64) |
| 101 | +fn main(@builtin(global_invocation_id) gid: vec3<u32>) { |
| 102 | + let i = gid.x; |
| 103 | + let count = arrayLength(&age); |
| 104 | + if i >= count { return; } |
| 105 | + if dead[i] != 0.0 { return; } |
| 106 | +
|
| 107 | + age[i] = age[i] + params.dt; |
| 108 | +
|
| 109 | + velocity[i * 3u + 1u] = velocity[i * 3u + 1u] - params.gravity * params.dt; |
| 110 | +
|
| 111 | + position[i * 3u + 0u] = position[i * 3u + 0u] + velocity[i * 3u + 0u] * params.dt; |
| 112 | + position[i * 3u + 1u] = position[i * 3u + 1u] + velocity[i * 3u + 1u] * params.dt; |
| 113 | + position[i * 3u + 2u] = position[i * 3u + 2u] + velocity[i * 3u + 2u] * params.dt; |
| 114 | +
|
| 115 | + let life = clamp(1.0 - age[i] / params.ttl, 0.0, 1.0); |
| 116 | + let s = life * life; |
| 117 | + scale[i * 3u + 0u] = s; |
| 118 | + scale[i * 3u + 1u] = s; |
| 119 | + scale[i * 3u + 2u] = s; |
| 120 | +
|
| 121 | + if age[i] > params.ttl { dead[i] = 1.0; } |
| 122 | +} |
| 123 | +""" |
| 124 | + |
| 125 | + |
| 126 | +def setup(): |
| 127 | + global field_obj, particle, mat, spawn, motion |
| 128 | + |
| 129 | + size(900, 700) |
| 130 | + mode_3d() |
| 131 | + |
| 132 | + create_directional_light((0.95, 0.9, 0.85), 800.0) |
| 133 | + |
| 134 | + particle = Geometry.sphere(0.12, 8, 6) |
| 135 | + |
| 136 | + velocity_attr = Attribute("velocity", AttributeFormat.Float3) |
| 137 | + age_attr = Attribute("age", AttributeFormat.Float) |
| 138 | + |
| 139 | + field_obj = Field( |
| 140 | + capacity=CAPACITY, |
| 141 | + attributes=[ |
| 142 | + Attribute.position(), |
| 143 | + Attribute.color(), |
| 144 | + Attribute.scale(), |
| 145 | + Attribute.dead(), |
| 146 | + velocity_attr, |
| 147 | + age_attr, |
| 148 | + ], |
| 149 | + ) |
| 150 | + |
| 151 | + # Mark all unemitted slots dead so they don't render at origin. |
| 152 | + dead_buf = field_obj.buffer(Attribute.dead()) |
| 153 | + dead_buf.write([1.0] * CAPACITY) |
| 154 | + |
| 155 | + color_buf = field_obj.buffer(Attribute.color()) |
| 156 | + mat = Material.field_pbr(color_buf) |
| 157 | + |
| 158 | + spawn = Compute(Shader(SPAWN_SHADER)) |
| 159 | + motion = Compute(Shader(MOTION_SHADER)) |
| 160 | + |
| 161 | + |
| 162 | +def draw(): |
| 163 | + camera_position(0.0, 4.0, 16.0) |
| 164 | + camera_look_at(0.0, 2.0, 0.0) |
| 165 | + background(10, 10, 18) |
| 166 | + |
| 167 | + use_material(mat) |
| 168 | + draw_field(field_obj, particle) |
| 169 | + |
| 170 | + # Animate spawn point in a small circle so the fountain meanders. |
| 171 | + t = elapsed_time |
| 172 | + sx = math.cos(t) * 0.4 |
| 173 | + sz = math.sin(t) * 0.4 |
| 174 | + spawn.set(pos=[sx, 7.0, sz, 0.0], speed=[SPEED, 0.0, 0.0, 0.0]) |
| 175 | + field_obj.emit_gpu(BURST, spawn) |
| 176 | + |
| 177 | + motion.set(dt=DT, ttl=TTL, gravity=GRAVITY) |
| 178 | + field_obj.apply(motion) |
| 179 | + |
| 180 | + |
| 181 | +run() |
0 commit comments