Skip to content

Commit 2782c58

Browse files
committed
v0.3.0: post-processing outlines, async asset loading, multi-threading
Post-processing pipeline with selection/hover outlines for scene editor. Two-phase async asset loading via Perry threads (stage on background thread, commit on main thread). Bump version to 0.3.0.
1 parent c1a0e3f commit 2782c58

9 files changed

Lines changed: 649 additions & 1 deletion

File tree

native/macos/src/lib.rs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1553,6 +1553,58 @@ pub extern "C" fn bloom_disable_shadows() {
15531553
engine().renderer.shadow_map.disable();
15541554
}
15551555

1556+
// ============================================================
1557+
// Post-processing
1558+
// ============================================================
1559+
1560+
#[no_mangle]
1561+
pub extern "C" fn bloom_enable_postfx() {
1562+
let eng = engine();
1563+
let w = eng.renderer.width();
1564+
let h = eng.renderer.height();
1565+
let fmt = eng.renderer.surface_format();
1566+
eng.postfx = Some(bloom_shared::postfx::PostFxPipeline::new(
1567+
&eng.renderer.device, w, h, fmt,
1568+
));
1569+
}
1570+
1571+
#[no_mangle]
1572+
pub extern "C" fn bloom_disable_postfx() {
1573+
engine().postfx = None;
1574+
}
1575+
1576+
#[no_mangle]
1577+
pub extern "C" fn bloom_postfx_set_selected(handle: f64) {
1578+
if let Some(pfx) = &mut engine().postfx {
1579+
if handle == 0.0 {
1580+
pfx.set_selected(Vec::new());
1581+
} else {
1582+
pfx.set_selected(vec![handle]);
1583+
}
1584+
}
1585+
}
1586+
1587+
#[no_mangle]
1588+
pub extern "C" fn bloom_postfx_set_hovered(handle: f64) {
1589+
if let Some(pfx) = &mut engine().postfx {
1590+
pfx.set_hovered(handle);
1591+
}
1592+
}
1593+
1594+
#[no_mangle]
1595+
pub extern "C" fn bloom_postfx_set_outline_color(r: f64, g: f64, b: f64, a: f64) {
1596+
if let Some(pfx) = &mut engine().postfx {
1597+
pfx.outline_params.color_selected = [r as f32, g as f32, b as f32, a as f32];
1598+
}
1599+
}
1600+
1601+
#[no_mangle]
1602+
pub extern "C" fn bloom_postfx_set_outline_thickness(thickness: f64) {
1603+
if let Some(pfx) = &mut engine().postfx {
1604+
pfx.outline_params.thickness[0] = thickness as f32;
1605+
}
1606+
}
1607+
15561608
/// Attach a loaded GLTF model's mesh geometry to a scene node.
15571609
/// Copies the vertex/index data from the model into the scene node.
15581610
#[no_mangle]
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//! Custom WGSL shaders for Pascal Editor effects.
2+
//!
3+
//! These are hand-ported equivalents of the Three.js TSL (Texture Shading Language)
4+
//! node materials used in the Pascal Editor:
5+
//! - Wall cutaway dot pattern
6+
//! - Zone gradient
7+
//! - Item preview shimmer
8+
//!
9+
//! In the browser, these are defined as JavaScript function calls (TSL nodes) that
10+
//! compile to WGSL at runtime. Since we compile natively, we write the WGSL directly.
11+
12+
/// Wall cutaway dot pattern — shows dots that fade with height when a wall
13+
/// is in "cutaway" mode. Replaces the TSL `Fn(() => { ... fract ... step ... })`.
14+
pub const WALL_CUTAWAY_SHADER: &str = "
15+
struct CutawayParams {
16+
dot_scale: vec4<f32>, // [scale, dot_size, fade_height, opacity]
17+
base_color: vec4<f32>, // rgba
18+
};
19+
20+
@group(0) @binding(0) var<uniform> params: CutawayParams;
21+
22+
struct VertexOutput {
23+
@builtin(position) clip_position: vec4<f32>,
24+
@location(0) local_pos: vec3<f32>,
25+
@location(1) color: vec4<f32>,
26+
};
27+
28+
@fragment
29+
fn fs_wall_cutaway(in: VertexOutput) -> @location(0) vec4<f32> {
30+
let scale = params.dot_scale.x;
31+
let dot_size = params.dot_scale.y;
32+
let fade_height = params.dot_scale.z;
33+
34+
// Create repeating dot grid
35+
let uv = vec2<f32>(in.local_pos.x, in.local_pos.y) / scale;
36+
let grid_uv = fract(uv);
37+
let dist = length(grid_uv - vec2<f32>(0.5));
38+
let dots = step(dist, dot_size * 0.5);
39+
40+
// Vertical fade (transparent near bottom, opaque at top)
41+
let y_fade = 1.0 - smoothstep(0.0, fade_height, in.local_pos.y);
42+
43+
let alpha = dots * y_fade * params.dot_scale.w;
44+
if (alpha < 0.01) { discard; }
45+
46+
return vec4<f32>(params.base_color.rgb * in.color.rgb, alpha);
47+
}
48+
";
49+
50+
/// Zone gradient — vertical gradient for zone visualization walls.
51+
/// Replaces the TSL `uv().y → mul(opacity)` with uniform.
52+
pub const ZONE_GRADIENT_SHADER: &str = "
53+
struct ZoneParams {
54+
base_color: vec4<f32>, // rgba
55+
opacity: vec4<f32>, // [max_opacity, 0, 0, 0]
56+
};
57+
58+
@group(0) @binding(0) var<uniform> zone_params: ZoneParams;
59+
60+
struct VertexOutput {
61+
@builtin(position) clip_position: vec4<f32>,
62+
@location(0) uv: vec2<f32>,
63+
@location(1) color: vec4<f32>,
64+
};
65+
66+
@fragment
67+
fn fs_zone_gradient(in: VertexOutput) -> @location(0) vec4<f32> {
68+
// Gradient: fully opaque at bottom, transparent at top
69+
let gradient_t = in.uv.y;
70+
let alpha = zone_params.opacity.x * (1.0 - gradient_t) * 0.6;
71+
return vec4<f32>(zone_params.base_color.rgb, alpha);
72+
}
73+
";
74+
75+
/// Item preview shimmer — animated shimmer effect for item placement preview.
76+
/// Replaces the TSL `positionLocal.y + time → fract → smoothstep`.
77+
pub const PREVIEW_SHIMMER_SHADER: &str = "
78+
struct ShimmerParams {
79+
time: vec4<f32>, // [time, speed, 0, 0]
80+
base_color: vec4<f32>, // rgba
81+
};
82+
83+
@group(0) @binding(0) var<uniform> shimmer_params: ShimmerParams;
84+
85+
struct VertexOutput {
86+
@builtin(position) clip_position: vec4<f32>,
87+
@location(0) local_pos: vec3<f32>,
88+
@location(1) color: vec4<f32>,
89+
};
90+
91+
@fragment
92+
fn fs_preview_shimmer(in: VertexOutput) -> @location(0) vec4<f32> {
93+
let time = shimmer_params.time.x;
94+
let speed = shimmer_params.time.y;
95+
96+
// Scrolling shimmer band
97+
let shimmer_y = fract((in.local_pos.y + time * speed) * 10.0);
98+
let shimmer = smoothstep(0.0, 0.1, shimmer_y) * smoothstep(0.3, 0.2, shimmer_y);
99+
100+
let base = shimmer_params.base_color;
101+
let brightness = 1.0 + shimmer * 0.3;
102+
return vec4<f32>(base.rgb * brightness, base.a * 0.7);
103+
}
104+
";

native/shared/src/engine.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::textures::TextureManager;
66
use crate::models::ModelManager;
77
use crate::scene::SceneGraph;
88
use crate::frame_callbacks::FrameCallbackSystem;
9+
use crate::postfx::PostFxPipeline;
910

1011
pub struct EngineState {
1112
pub renderer: Renderer,
@@ -16,6 +17,7 @@ pub struct EngineState {
1617
pub models: ModelManager,
1718
pub scene: SceneGraph,
1819
pub frame_callbacks: FrameCallbackSystem,
20+
pub postfx: Option<PostFxPipeline>,
1921

2022
// Timing
2123
pub target_fps: f64,
@@ -42,6 +44,7 @@ impl EngineState {
4244
models: ModelManager::new(),
4345
scene: SceneGraph::new(),
4446
frame_callbacks: FrameCallbackSystem::new(),
47+
postfx: None,
4548
target_fps: 60.0,
4649
delta_time: 0.0,
4750
last_frame_time: now,

native/shared/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ pub mod frame_callbacks;
1111
pub mod geometry;
1212
pub mod picking;
1313
pub mod shadows;
14+
pub mod postfx;
15+
pub mod custom_shaders;
1416
pub mod staging;
1517
pub mod engine;
1618

0 commit comments

Comments
 (0)