Skip to content

Commit fcfc1af

Browse files
committed
refactor: optimize lightning renderer, update shader SDF logic, and expose lamp mesh generator
1 parent 177949d commit fcfc1af

5 files changed

Lines changed: 136 additions & 57 deletions

File tree

src/engine.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ pub struct VulkanEngine<'a> {
278278
smooth_dt: f64,
279279
}
280280

281-
fn generate_lamp_mesh() -> (Vec<Vertex>, Vec<u32>) {
281+
pub(crate) fn generate_lamp_mesh() -> (Vec<Vertex>, Vec<u32>) {
282282
let mut vertices = Vec::new();
283283
let mut indices = Vec::new();
284284

src/shaders/vis_3drain.wgsl

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -37,46 +37,55 @@ fn project_3d(p3: vec3<f32>, ro: vec3<f32>, u: vec3<f32>, v_cam: vec3<f32>, w: v
3737
return vec3<f32>(proj_x, proj_y, dist_w);
3838
}
3939

40-
fn draw_3d_segment(p: vec2<f32>, ro: vec3<f32>, u: vec3<f32>, v_cam: vec3<f32>, w: vec3<f32>, start_pos: vec3<f32>, end_pos: vec3<f32>, thickness_base: f32, intensity: f32, is_branch: bool) -> f32 {
41-
var pA = project_3d(start_pos, ro, u, v_cam, w);
42-
var pB = project_3d(end_pos, ro, u, v_cam, w);
40+
fn draw_projected_segment(p: vec2<f32>, pA: vec3<f32>, pB: vec3<f32>, thickness_base: f32, intensity: f32, is_branch: bool) -> f32 {
41+
if (pA.z <= 0.1 || pB.z <= 0.1) {
42+
return 0.0;
43+
}
4344

44-
if (pA.z <= 0.0 && pB.z <= 0.0) { return 0.0; }
45+
let margin = select(0.08, 0.04, is_branch);
46+
let min_x = min(pA.x, pB.x) - margin;
47+
let max_x = max(pA.x, pB.x) + margin;
48+
let min_y = min(pA.y, pB.y) - margin;
49+
let max_y = max(pA.y, pB.y) + margin;
4550

46-
if (pA.z <= 0.0 && pB.z > 0.0) {
47-
let t = clamp(-pA.z / (pB.z - pA.z), 0.0, 1.0);
48-
let front_pos = start_pos + (end_pos - start_pos) * t;
49-
pA = project_3d(front_pos, ro, u, v_cam, w);
50-
} else if (pB.z <= 0.0 && pA.z > 0.0) {
51-
let t = clamp(-pA.z / (pB.z - pA.z), 0.0, 1.0);
52-
let front_pos = start_pos + (end_pos - start_pos) * t;
53-
pB = project_3d(front_pos, ro, u, v_cam, w);
51+
if (p.x < min_x || p.x > max_x || p.y < min_y || p.y > max_y) {
52+
return 0.0;
5453
}
5554

56-
if (pA.z > 0.0 || pB.z > 0.0) {
57-
let d = sd_segment(p, pA.xy, pB.xy);
58-
let depth = max(0.4, max(pA.z, pB.z));
59-
let thickness = thickness_base / depth;
60-
let core = smoothstep(thickness * 0.5, 0.0, d);
61-
let glow_radius = select(60.0, 120.0, is_branch); // Branches have a tighter, less intense glow
62-
let glow_mult = select(0.7, 0.3, is_branch);
63-
let glow = exp(-d * glow_radius) * glow_mult;
64-
return (core + glow) * intensity;
65-
}
66-
return 0.0;
55+
let d = sd_segment(p, pA.xy, pB.xy);
56+
let depth = max(0.4, max(pA.z, pB.z));
57+
let thickness = thickness_base / depth;
58+
let core = smoothstep(thickness * 0.5, 0.0, d);
59+
let glow_radius = select(60.0, 120.0, is_branch); // Branches have a tighter, less intense glow
60+
let glow_mult = select(0.7, 0.3, is_branch);
61+
let glow = exp(-d * glow_radius) * glow_mult;
62+
return (core + glow) * intensity;
6763
}
6864

6965
fn draw_3d_lightning_bolt(ro: vec3<f32>, u: vec3<f32>, v_cam: vec3<f32>, w: vec3<f32>, p: vec2<f32>, seed: f32, intensity: f32, origin: vec3<f32>, plane_z: f32) -> f32 {
7066
if (intensity < 0.01) { return 0.0; }
7167

72-
var bolt = 0.0;
73-
var current_pos = origin;
7468
let segments = 15;
7569
let frustum_height = plane_z - ro.z;
7670
let bolt_height = frustum_height * 2.4; // Ensure it reaches ground
7771
let seg_h = bolt_height / f32(segments);
7872
let j_scale = plane_z * 0.15; // Scale jitter with distance
7973

74+
// Bounding box check in screen space (Fast Pruning)
75+
let max_dev_3d = 6.5 * j_scale;
76+
let p_top = project_3d(origin, ro, u, v_cam, w);
77+
let p_bot = project_3d(origin - vec3<f32>(0.0, bolt_height, 0.0), ro, u, v_cam, w);
78+
79+
let depth_min = max(0.1, plane_z - max_dev_3d - ro.z);
80+
let margin = max_dev_3d / depth_min + 0.15;
81+
82+
let d_line = sd_segment(p, p_top.xy, p_bot.xy);
83+
if (d_line > margin) { return 0.0; }
84+
85+
var bolt = 0.0;
86+
var current_pos = origin;
87+
var p_curr_proj = p_top;
88+
8089
// Simulate dart leaders and return stroke pulsing via high-frequency flicker
8190
let flicker = 0.6 + 0.4 * sin(audio.smooth_time * 60.0 + seed);
8291
let main_intensity = intensity * flicker;
@@ -87,43 +96,51 @@ fn draw_3d_lightning_bolt(ro: vec3<f32>, u: vec3<f32>, v_cam: vec3<f32>, w: vec3
8796
let x_jitter = (hash11(seed + f_i * 7.13) - 0.5) * j_scale * 2.5;
8897
let z_jitter = (hash11(seed + f_i * 13.7) - 0.5) * j_scale * 2.5;
8998
let next_pos = current_pos + vec3<f32>(x_jitter, -seg_h, z_jitter);
99+
let p_next_proj = project_3d(next_pos, ro, u, v_cam, w);
90100

91101
// 1. Main Return Stroke Channel
92102
let thickness = 0.012 + main_intensity * 0.015;
93-
bolt += draw_3d_segment(p, ro, u, v_cam, w, current_pos, next_pos, thickness, main_intensity, false);
103+
bolt += draw_projected_segment(p, p_curr_proj, p_next_proj, thickness, main_intensity, false);
94104

95105
// 2. Stepped Leaders (Primary Branches)
96-
// Spawn branches from most nodes to simulate the searching fractal nature
97106
if (hash11(seed + f_i * 3.1) > 0.25) {
98107
let bx = (hash11(seed + f_i * 8.2) - 0.5) * j_scale * 4.0;
99108
let bz = (hash11(seed + f_i * 9.3) - 0.5) * j_scale * 4.0;
100109
let branch_pos = current_pos + vec3<f32>(bx, -seg_h * 1.5, bz);
110+
let p_branch_proj = project_3d(branch_pos, ro, u, v_cam, w);
101111

102-
bolt += draw_3d_segment(p, ro, u, v_cam, w, current_pos, branch_pos, 0.005, branch_intensity, true);
112+
bolt += draw_projected_segment(p, p_curr_proj, p_branch_proj, 0.005, branch_intensity, true);
103113

104114
// 3. Sub-branches (Secondary splits from the stepped leaders)
105115
if (hash11(seed + f_i * 2.4) > 0.35) {
106116
let bx2 = (hash11(seed + f_i * 1.2) - 0.5) * j_scale * 3.0;
107117
let bz2 = (hash11(seed + f_i * 4.3) - 0.5) * j_scale * 3.0;
108118
let branch_pos2 = branch_pos + vec3<f32>(bx2, -seg_h * 1.2, bz2);
109-
bolt += draw_3d_segment(p, ro, u, v_cam, w, branch_pos, branch_pos2, 0.003, branch_intensity * 0.6, true);
119+
let p_branch2_proj = project_3d(branch_pos2, ro, u, v_cam, w);
120+
121+
bolt += draw_projected_segment(p, p_branch_proj, p_branch2_proj, 0.003, branch_intensity * 0.6, true);
110122
}
111123
}
112124

113125
// 4. Major splits (connecting leaders that travel alongside the main channel)
114126
if (i == 3 || i == 8) {
115127
var split_pos = current_pos;
128+
var p_split_proj = p_curr_proj;
116129
for (var j = 0; j < 4; j = j + 1) {
117130
let f_j = f32(j);
118131
let sx = (hash11(seed + f_i * 5.0 + f_j) - 0.5) * j_scale * 3.0;
119132
let sz = (hash11(seed + f_i * 6.0 + f_j) - 0.5) * j_scale * 3.0;
120133
let next_split = split_pos + vec3<f32>(sx, -seg_h * 1.1, sz);
121-
bolt += draw_3d_segment(p, ro, u, v_cam, w, split_pos, next_split, 0.007, branch_intensity * 1.5, true);
134+
let p_next_split_proj = project_3d(next_split, ro, u, v_cam, w);
135+
136+
bolt += draw_projected_segment(p, p_split_proj, p_next_split_proj, 0.007, branch_intensity * 1.5, true);
122137
split_pos = next_split;
138+
p_split_proj = p_next_split_proj;
123139
}
124140
}
125141

126142
current_pos = next_pos;
143+
p_curr_proj = p_next_proj;
127144
}
128145

129146
return bolt;

src/shaders/vis_ferrofluid.wgsl

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,21 @@ fn smax(a: f32, b: f32, k: f32) -> f32 {
6262
}
6363

6464
// --- Speaker Layout (up to 7.1.4) ---
65+
const SPEAKER_DIR_2D = array<vec2<f32>, 12>(
66+
vec2<f32>(-0.5, -0.866),
67+
vec2<f32>(0.5, -0.866),
68+
vec2<f32>(0.0, -1.0),
69+
vec2<f32>(0.0, 0.0), // LFE (unused directionally)
70+
vec2<f32>(-0.94, 0.34),
71+
vec2<f32>(0.94, 0.34),
72+
vec2<f32>(-0.5, 0.866),
73+
vec2<f32>(0.5, 0.866),
74+
vec2<f32>(-0.70710678, -0.70710678),
75+
vec2<f32>(0.70710678, -0.70710678),
76+
vec2<f32>(-0.70710678, 0.70710678),
77+
vec2<f32>(0.70710678, 0.70710678)
78+
);
79+
6580
// Note: This shader maps channels[] (instrument/track data) to speaker positions.
6681
// For surround content where spatial_channels[] carries the speaker mix,
6782
// swap get_vu(i) for a spatial accessor if needed.
@@ -106,7 +121,7 @@ fn map_dist(p: vec3<f32>) -> f32 {
106121
alignment = 1.0;
107122
spike_pos_r = 0.0;
108123
} else {
109-
let dir2d = normalize(get_speaker_dir(i).xz);
124+
let dir2d = SPEAKER_DIR_2D[i];
110125
alignment = max(0.0, dot(p_xz_norm, dir2d));
111126
spike_pos_r = 1.5;
112127
}
@@ -152,29 +167,45 @@ struct MapData {
152167
}
153168

154169
fn map(p: vec3<f32>) -> MapData {
155-
// Delegate to the canonical SDF — single source of truth for the distance field.
156-
let d = map_dist(p);
157-
158-
// Glow computation (cosmetic only — uses channel alignment but does NOT
159-
// affect the SDF value, so the distance can never diverge from map_dist).
160170
let dist_xz = length(p.xz);
161-
let p_xz_norm = p.xz / max(dist_xz, 0.0001);
171+
172+
// Base infinite plane thickness
173+
var fluid_h = 0.0;
174+
162175
let num_ch = min(audio.num_channels, 12u);
176+
var total_displacement = 0.0;
177+
178+
// Normalized xz for angle alignment
179+
let p_xz_norm = p.xz / max(dist_xz, 0.0001);
163180
var glow = vec3<f32>(0.0);
164181

165182
for (var i = 0u; i < num_ch; i++) {
166183
let vu = clamp(get_vu(i), 0.0, 1.0);
184+
167185
var alignment = 1.0;
168186
var spike_pos_r = 1.5;
169-
if i == 3u { alignment = 1.0; spike_pos_r = 0.0; }
170-
else {
171-
let dir2d = normalize(get_speaker_dir(i).xz);
187+
188+
if i == 3u { // LFE channel — center blob
189+
alignment = 1.0;
190+
spike_pos_r = 0.0;
191+
} else {
192+
let dir2d = SPEAKER_DIR_2D[i];
172193
alignment = max(0.0, dot(p_xz_norm, dir2d));
194+
spike_pos_r = 1.5;
173195
}
196+
174197
let dist_to_spike = abs(dist_xz - spike_pos_r);
175198
let spatial_falloff = exp(-dist_to_spike * 3.0);
199+
200+
// Soften spike shape (pow 8) to keep SDF slopes within Lipschitz bound
176201
var lobe = pow(alignment, 8.0) * vu * 1.5 * spatial_falloff;
177-
if i != 3u { lobe *= smoothstep(0.1, 0.5, dist_xz); }
202+
203+
// Attenuate directional lobes at the center to prevent radial crease artifacts
204+
if i != 3u {
205+
lobe *= smoothstep(0.1, 0.5, dist_xz);
206+
}
207+
208+
total_displacement = smax(total_displacement, lobe, 0.3);
178209

179210
if lobe > 0.1 {
180211
var ch_color = vec3<f32>(0.2, 0.6, 1.0);
@@ -184,6 +215,18 @@ fn map(p: vec3<f32>) -> MapData {
184215
}
185216
}
186217

218+
// Subtle ripples from spectrum bass
219+
let bass = clamp(audio.spectrum[0].x + audio.spectrum[1].x, 0.0, 2.0);
220+
let ripple = sin(dist_xz * 12.0 - audio.time * 8.0) * 0.015 * bass * smoothstep(PUDDLE_RADIUS, 0.0, dist_xz);
221+
222+
// Organic surface perturbation (smooth magnetic domain noise)
223+
let noise_p = p * 4.0 + vec3<f32>(audio.time * 0.5, 0.0, audio.time * 0.3);
224+
let surface_noise = (hash3_smooth(noise_p) - 0.5) * 0.05;
225+
226+
fluid_h += total_displacement + ripple + surface_noise;
227+
228+
let d = (p.y + 0.5 - fluid_h) * STEP_SCALE;
229+
187230
return MapData(d, 1, glow);
188231
}
189232

src/shaders/vis_ferrofluidsim.wgsl

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,23 +51,32 @@ fn get_density(p: vec2<f32>) -> f32 {
5151
let fx = fract(px);
5252
let fz = fract(pz);
5353

54+
var w_x: array<f32, 5>;
55+
for (var i = 0; i < 5; i++) {
56+
let dx = f32(i - 2) - fx;
57+
w_x[i] = exp(-dx * dx * 0.12);
58+
}
59+
60+
var w_z: array<f32, 5>;
61+
for (var j = 0; j < 5; j++) {
62+
let dz = f32(j - 2) - fz;
63+
w_z[j] = exp(-dz * dz * 0.12);
64+
}
65+
5466
var sum = 0.0;
5567
var w_sum = 0.0;
5668

5769
// 5×5 Gaussian kernel at stride 1 — no aliasing artifacts
5870
// Wide sigma blends the 5×5 compute splat into smooth, continuous mounds
5971
for (var i = -2; i <= 2; i++) {
72+
let cx = ix + i;
73+
let wx = w_x[i + 2];
6074
for (var j = -2; j <= 2; j++) {
61-
let cx = ix + i;
75+
let cx_coord = cx;
6276
let cz = iz + j;
63-
let val = get_density_raw(cx, cz);
77+
let val = get_density_raw(cx_coord, cz);
6478

65-
let dx = f32(i) - fx;
66-
let dy = f32(j) - fz;
67-
let dist_sq = dx*dx + dy*dy;
68-
69-
// Wide Gaussian for smooth blending
70-
let w = exp(-dist_sq * 0.12);
79+
let w = wx * w_z[j + 2];
7180
sum += val * w;
7281
w_sum += w;
7382
}
@@ -81,11 +90,15 @@ fn get_density(p: vec2<f32>) -> f32 {
8190
}
8291

8392
fn map(p: vec3<f32>) -> f32 {
93+
let dist_from_center = length(p.xz);
94+
if (dist_from_center > PUDDLE_RADIUS + 0.5) {
95+
return (p.y + 0.5) * LIPSCHITZ;
96+
}
97+
8498
let particle_h = get_density(p.xz);
8599

86100
// Base puddle: thin continuous fluid layer that prevents holes
87101
// Simulates surface tension — ferrofluid never has gaps in its surface
88-
let dist_from_center = length(p.xz);
89102
let base_h = BASE_HEIGHT * smoothstep(PUDDLE_RADIUS, PUDDLE_RADIUS * 0.5, dist_from_center);
90103

91104
let h = max(particle_h, base_h);

src/shaders/vis_neon.wgsl

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -116,12 +116,18 @@ fn map_scene(p: vec3<f32>) -> vec2<f32> {
116116
}
117117

118118
fn calc_normal(p: vec3<f32>) -> vec3<f32> {
119-
let e = vec2<f32>(0.01, 0.0);
120-
return normalize(vec3<f32>(
121-
map_scene(p + e.xyy).x - map_scene(p - e.xyy).x,
122-
map_scene(p + e.yxy).x - map_scene(p - e.yxy).x,
123-
map_scene(p + e.yyx).x - map_scene(p - e.yyx).x
124-
));
119+
let d_floor = p.y + 1.0;
120+
let d_wall = 10.0 - p.z;
121+
let d_ceil = 5.0 - p.y;
122+
let d_left = p.x + 8.0;
123+
let d_right = 8.0 - p.x;
124+
125+
let min_d = min(d_floor, min(d_wall, min(d_ceil, min(d_left, d_right))));
126+
if (min_d == d_floor) { return vec3<f32>(0.0, 1.0, 0.0); }
127+
if (min_d == d_ceil) { return vec3<f32>(0.0, -1.0, 0.0); }
128+
if (min_d == d_left) { return vec3<f32>(1.0, 0.0, 0.0); }
129+
if (min_d == d_right) { return vec3<f32>(-1.0, 0.0, 0.0); }
130+
return vec3<f32>(0.0, 0.0, -1.0);
125131
}
126132

127133
fn get_smoke_density(p: vec3<f32>, time: f32, audio_activity: f32) -> f32 {

0 commit comments

Comments
 (0)