feat(render): add scene environment pipeline
This commit is contained in:
parent
b667eeeb05
commit
c45daeffb6
17 changed files with 2236 additions and 213 deletions
22
src/scene/cache/block_cache.rs
vendored
22
src/scene/cache/block_cache.rs
vendored
|
|
@ -183,6 +183,7 @@ impl BlockCache {
|
|||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
bg_color: [f32; 4],
|
||||
viewport: Option<Handle>,
|
||||
// Scene draw-depth map ([depth, half] per handle) — source of each
|
||||
// block child's in-block rank, shared by cached wires and graph leaves.
|
||||
depth_map: &HashMap<u64, [f32; 2]>,
|
||||
|
|
@ -220,6 +221,7 @@ impl BlockCache {
|
|||
annotation_scale_handle,
|
||||
all_visible,
|
||||
bg_color,
|
||||
viewport,
|
||||
depth_map,
|
||||
)),
|
||||
)
|
||||
|
|
@ -238,6 +240,7 @@ impl BlockCache {
|
|||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
bg_color: [f32; 4],
|
||||
viewport: Option<Handle>,
|
||||
depth_map: &HashMap<u64, [f32; 2]>,
|
||||
) -> Self {
|
||||
use crate::par::prelude::*;
|
||||
|
|
@ -255,6 +258,7 @@ impl BlockCache {
|
|||
annotation_scale_handle,
|
||||
all_visible,
|
||||
bg_color,
|
||||
viewport,
|
||||
depth_map,
|
||||
)),
|
||||
)
|
||||
|
|
@ -396,6 +400,7 @@ fn build_defn(
|
|||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
bg_color: [f32; 4],
|
||||
viewport: Option<Handle>,
|
||||
depth_map: &HashMap<u64, [f32; 2]>,
|
||||
) -> BlockDefn {
|
||||
let br = match doc.block_records.get(block_name) {
|
||||
|
|
@ -461,7 +466,7 @@ fn build_defn(
|
|||
}
|
||||
EntityType::Insert(nested_ins) => {
|
||||
subs.push(LocalSub::Nested(build_nested_ref(
|
||||
nested_ins, doc, bg_color, depth_map,
|
||||
nested_ins, doc, bg_color, viewport, depth_map,
|
||||
)));
|
||||
}
|
||||
EntityType::Dimension(_) => {
|
||||
|
|
@ -471,6 +476,7 @@ fn build_defn(
|
|||
anno_scale,
|
||||
annotation_scale_handle,
|
||||
bg_color,
|
||||
viewport,
|
||||
depth_map,
|
||||
) {
|
||||
subs.push(LocalSub::Wire(wire));
|
||||
|
|
@ -504,7 +510,8 @@ fn build_defn(
|
|||
annotation_scale_handle,
|
||||
all_visible,
|
||||
depth_map,
|
||||
);
|
||||
)
|
||||
.with_viewport(viewport);
|
||||
graph.walk_insert(
|
||||
&insert,
|
||||
table.common.handle,
|
||||
|
|
@ -518,6 +525,7 @@ fn build_defn(
|
|||
anno_scale,
|
||||
annotation_scale_handle,
|
||||
bg_color,
|
||||
viewport,
|
||||
depth_map,
|
||||
) {
|
||||
subs.push(LocalSub::Wire(wire));
|
||||
|
|
@ -531,6 +539,7 @@ fn build_defn(
|
|||
anno_scale,
|
||||
annotation_scale_handle,
|
||||
bg_color,
|
||||
viewport,
|
||||
depth_map,
|
||||
) {
|
||||
subs.push(LocalSub::Wire(wire));
|
||||
|
|
@ -544,6 +553,7 @@ fn build_defn(
|
|||
&insert,
|
||||
doc,
|
||||
bg_color,
|
||||
viewport,
|
||||
depth_map,
|
||||
)));
|
||||
}
|
||||
|
|
@ -560,6 +570,7 @@ fn build_defn(
|
|||
anno_scale,
|
||||
annotation_scale_handle,
|
||||
bg_color,
|
||||
viewport,
|
||||
depth_map,
|
||||
) {
|
||||
subs.push(LocalSub::Wire(lw));
|
||||
|
|
@ -579,6 +590,7 @@ fn build_nested_ref(
|
|||
nested_ins: &acadrust::entities::Insert,
|
||||
doc: &CadDocument,
|
||||
bg_color: [f32; 4],
|
||||
viewport: Option<Handle>,
|
||||
depth_map: &HashMap<u64, [f32; 2]>,
|
||||
) -> NestedRef {
|
||||
let _ = bg_color;
|
||||
|
|
@ -595,7 +607,7 @@ fn build_nested_ref(
|
|||
NestedRef {
|
||||
block_name: nested_ins.block_name.clone(),
|
||||
xform,
|
||||
style: crate::scene::render_graph::InsertStyleSpec::new(doc, nested_ins),
|
||||
style: crate::scene::render_graph::InsertStyleSpec::new(doc, nested_ins, viewport),
|
||||
instance_offsets: crate::scene::render_graph::array_offsets(nested_ins),
|
||||
clip_poly,
|
||||
local_rank: depth_map
|
||||
|
|
@ -610,6 +622,7 @@ fn tessellate_sub_local(
|
|||
anno_scale: f32,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
bg_color: [f32; 4],
|
||||
viewport: Option<Handle>,
|
||||
depth_map: &HashMap<u64, [f32; 2]>,
|
||||
) -> Vec<LocalWire> {
|
||||
let h = sub.common().handle;
|
||||
|
|
@ -626,7 +639,8 @@ fn tessellate_sub_local(
|
|||
// with the per-render bg, so the cache no longer has to rebuild on
|
||||
// BACKGROUND / layout-switch — the dynamic adaptation tracks the
|
||||
// live bg at render time.
|
||||
let (sub_color, pat_len, pat, lw_px, aci) = crate::scene::view::render::render_style_for(doc, sub);
|
||||
let (sub_color, pat_len, pat, lw_px, aci) =
|
||||
crate::scene::view::render::render_style_for_viewport(doc, sub, viewport);
|
||||
let _ = bg_color;
|
||||
|
||||
let has_book_color =
|
||||
|
|
|
|||
|
|
@ -405,7 +405,7 @@ impl Scene {
|
|||
model_space: bool,
|
||||
) -> bool {
|
||||
let _ = model_space;
|
||||
let Some(cam) = self.camera_from_view(
|
||||
let Some(cam) = self.camera_from_view_mode(
|
||||
view.direction,
|
||||
view.target,
|
||||
acadrust::types::Vector2 {
|
||||
|
|
@ -414,6 +414,8 @@ impl Scene {
|
|||
},
|
||||
view.height,
|
||||
view.twist_angle,
|
||||
view.perspective,
|
||||
view.lens_length,
|
||||
) else {
|
||||
return false;
|
||||
};
|
||||
|
|
@ -463,16 +465,15 @@ impl Scene {
|
|||
/// their already-effective `view_target` / `view_center` / `view_height`.
|
||||
///
|
||||
/// Returns `None` for a zero `view_height` (an uninitialised entry).
|
||||
pub(super) fn camera_from_view(
|
||||
pub(super) fn camera_from_view_mode(
|
||||
&self,
|
||||
view_direction: acadrust::types::Vector3,
|
||||
view_target: acadrust::types::Vector3,
|
||||
view_center: acadrust::types::Vector2,
|
||||
view_height: f64,
|
||||
twist: f64,
|
||||
// Subtracted from `view_target` to reach wire-space. Model views pass
|
||||
// `[0.0_f64; 3]`; paper-space views (whose entities carry no
|
||||
// offset) pass `[0; 3]`.
|
||||
perspective: bool,
|
||||
lens_length: f64,
|
||||
) -> Option<Camera> {
|
||||
if view_height.abs() < 1e-9 {
|
||||
return None;
|
||||
|
|
@ -516,14 +517,26 @@ impl Scene {
|
|||
let target = base
|
||||
+ view_right.as_dvec3() * view_center.x
|
||||
+ view_up.as_dvec3() * view_center.y;
|
||||
let fov_y = 45.0_f32.to_radians();
|
||||
let lens_length = lens_length.max(1.0) as f32;
|
||||
// The saved-view convention uses a 24 mm vertical aperture. This is
|
||||
// the inverse of the projection path's documented
|
||||
// `distance = view_height * lens / 24` relation.
|
||||
let fov_y = if perspective {
|
||||
2.0 * (12.0 / lens_length).atan()
|
||||
} else {
|
||||
45.0_f32.to_radians()
|
||||
};
|
||||
let distance = ((view_height as f32 / 2.0) / (fov_y * 0.5).tan()).max(0.001);
|
||||
Some(Camera {
|
||||
target,
|
||||
rotation,
|
||||
distance,
|
||||
fov_y,
|
||||
projection: view::camera::Projection::Orthographic,
|
||||
projection: if perspective {
|
||||
view::camera::Projection::Perspective
|
||||
} else {
|
||||
view::camera::Projection::Orthographic
|
||||
},
|
||||
yaw,
|
||||
pitch,
|
||||
// Sized from the saved view height (not a zoom-scaled range) so a
|
||||
|
|
@ -544,12 +557,14 @@ impl Scene {
|
|||
|
||||
/// Decode a VPort table entry (model-space tiled view) into a `Camera`.
|
||||
fn camera_from_vport(&self, vp: &acadrust::tables::VPort) -> Option<Camera> {
|
||||
self.camera_from_view(
|
||||
self.camera_from_view_mode(
|
||||
vp.view_direction,
|
||||
vp.view_target,
|
||||
vp.view_center,
|
||||
vp.view_height,
|
||||
vp.view_twist,
|
||||
vp.perspective,
|
||||
vp.lens_length,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -580,6 +595,8 @@ impl Scene {
|
|||
z: view_dir.z as f64,
|
||||
};
|
||||
entry.view_height = view_height as f64;
|
||||
entry.perspective = cam.projection == view::camera::Projection::Perspective;
|
||||
entry.lens_length = (12.0 / (cam.fov_y * 0.5).tan().max(1e-6)) as f64;
|
||||
entry.view_center = acadrust::types::Vector2::ZERO;
|
||||
// Stored twist = -roll, matching the decoder (roll = -twist).
|
||||
entry.view_twist = -cam.roll() as f64;
|
||||
|
|
@ -796,7 +813,7 @@ impl Scene {
|
|||
|
||||
// Paper-space entities carry no world_offset → decode with a zero
|
||||
// offset, through the same shared decoder (twist included).
|
||||
let Some(cam) = self.camera_from_view(
|
||||
let Some(cam) = self.camera_from_view_mode(
|
||||
vp.view_direction,
|
||||
vp.view_target,
|
||||
acadrust::types::Vector2 {
|
||||
|
|
@ -805,6 +822,8 @@ impl Scene {
|
|||
},
|
||||
vp.view_height,
|
||||
vp.twist_angle,
|
||||
vp.status.perspective,
|
||||
vp.lens_length,
|
||||
) else {
|
||||
return false;
|
||||
};
|
||||
|
|
@ -847,6 +866,8 @@ impl Scene {
|
|||
vp.view_direction = vd3;
|
||||
vp.view_height = view_height as f64;
|
||||
vp.view_twist = twist;
|
||||
vp.perspective = cam.projection == view::camera::Projection::Perspective;
|
||||
vp.lens_length = (12.0 / (cam.fov_y * 0.5).tan().max(1e-6)) as f64;
|
||||
}
|
||||
|
||||
// Persist the tiled layout as duplicate `*Active` VPort entries.
|
||||
|
|
@ -878,6 +899,9 @@ impl Scene {
|
|||
vp.view_direction = vd3;
|
||||
vp.view_height = view_height as f64;
|
||||
vp.twist_angle = twist;
|
||||
vp.status.perspective =
|
||||
cam.projection == view::camera::Projection::Perspective;
|
||||
vp.lens_length = (12.0 / (cam.fov_y * 0.5).tan().max(1e-6)) as f64;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
|
@ -985,6 +1009,7 @@ impl Scene {
|
|||
None,
|
||||
scale,
|
||||
self.annotation_all_visible(),
|
||||
None,
|
||||
) {
|
||||
let is_infinite = Self::handle_from_wire_name(&wire.name)
|
||||
.and_then(|handle| self.document.get_entity(handle))
|
||||
|
|
@ -1081,6 +1106,7 @@ impl Scene {
|
|||
None,
|
||||
scale,
|
||||
self.annotation_all_visible(),
|
||||
None,
|
||||
);
|
||||
// Ray / XLine tessellate as ±DISPLAY_EXTENT display segments
|
||||
// (entities/ray.rs) — their endpoints are rendering artifacts, not
|
||||
|
|
|
|||
|
|
@ -442,7 +442,7 @@ pub(crate) fn tessellate_entity(
|
|||
// footprint doesn't drift when the camera
|
||||
// crosses the LOD threshold. See #19.
|
||||
let (entity_color, _, _, _, aci_idx) =
|
||||
view::render::render_style_for(document, e);
|
||||
view::render::render_style_for_viewport(document, e, active_viewport);
|
||||
let entity_color = view::render::adapt_to_bg(entity_color, bg_color);
|
||||
let entity_color = fade_if_locked(document, e, entity_color, bg_color);
|
||||
if is_3d_entity {
|
||||
|
|
@ -529,11 +529,11 @@ pub(crate) fn tessellate_entity(
|
|||
}
|
||||
|
||||
let (entity_color, pattern_length, pattern, line_weight_px, aci) =
|
||||
view::render::render_style_for(document, e);
|
||||
view::render::render_style_for_viewport(document, e, active_viewport);
|
||||
let entity_color = view::render::adapt_to_bg(entity_color, bg_color);
|
||||
let entity_color = fade_if_locked(document, e, entity_color, bg_color);
|
||||
let lt_scale = document.header.linetype_scale as f32 * e.common().linetype_scale as f32;
|
||||
let lt_name = view::render::linetype_name_for(document, e);
|
||||
let lt_name = view::render::linetype_name_for_viewport(document, e, active_viewport);
|
||||
// Paper-space linetype scaling belongs to the viewport uniform. Keeping
|
||||
// resident geometry at its model-space scale prevents every MSPACE wheel
|
||||
// tick from rebuilding and uploading the viewport's complete wire set.
|
||||
|
|
@ -931,12 +931,16 @@ pub(crate) fn tessellate_entity(
|
|||
if let EntityType::Insert(ins) = e {
|
||||
// Resolve the INSERT's own style so ByBlock sub-entities can inherit it.
|
||||
let (ins_color, ins_pat_len, ins_pat, ins_lw_px, _) =
|
||||
view::render::render_style_for(document, e);
|
||||
view::render::render_style_for_viewport(document, e, active_viewport);
|
||||
let ins_color = view::render::adapt_to_bg(ins_color, bg_color);
|
||||
// Resolve the INSERT's *layer* style — the layer-0 inheritance target
|
||||
// for sub-entities on layer "0" with ByLayer properties (#221).
|
||||
let ins_layer = {
|
||||
let mut s = view::render::layer_render_style(document, &ins.common.layer);
|
||||
let mut s = view::render::layer_render_style_viewport(
|
||||
document,
|
||||
&ins.common.layer,
|
||||
active_viewport,
|
||||
);
|
||||
s.color = view::render::adapt_to_bg(s.color, bg_color);
|
||||
s
|
||||
};
|
||||
|
|
@ -986,6 +990,7 @@ pub(crate) fn tessellate_entity(
|
|||
annotation_scale_handle,
|
||||
true,
|
||||
bg_color,
|
||||
active_viewport,
|
||||
&fallback_depths,
|
||||
);
|
||||
&fallback_cache
|
||||
|
|
|
|||
|
|
@ -799,6 +799,7 @@ impl Scene {
|
|||
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
viewport: Option<Handle>,
|
||||
) -> Vec<HatchModel> {
|
||||
let layer_hidden = |layer: &str| {
|
||||
self.document
|
||||
|
|
@ -818,6 +819,11 @@ impl Scene {
|
|||
// Every content viewport supplies the block it renders. Do not depend
|
||||
// on camera/frustum culling to separate paper and model coordinates:
|
||||
// overlapping coordinates otherwise make foreign fills visible.
|
||||
let hatch_bg = if self.current_layout != "Model" {
|
||||
self.paper_bg_color
|
||||
} else {
|
||||
self.bg_color
|
||||
};
|
||||
let depth_map = self.draw_depth_map();
|
||||
let mut models: Vec<HatchModel> = self
|
||||
.hatches
|
||||
|
|
@ -887,7 +893,12 @@ impl Scene {
|
|||
// emitted first so LessEqual layering keeps it underneath.
|
||||
let mut backdrop: Option<HatchModel> = None;
|
||||
if let Some(e) = entity {
|
||||
let style = self.render_style(e);
|
||||
let mut style = crate::scene::view::render::render_style_for_viewport(
|
||||
&self.document,
|
||||
e,
|
||||
viewport,
|
||||
);
|
||||
style.0 = crate::scene::view::render::adapt_to_bg(style.0, hatch_bg);
|
||||
m.aci = style.4;
|
||||
m.line_weight_px = style.3;
|
||||
// A gradient's colour is its first stop (already baked into
|
||||
|
|
@ -913,9 +924,10 @@ impl Scene {
|
|||
})
|
||||
.unwrap_or(0);
|
||||
(
|
||||
crate::scene::view::render::layer_render_style(
|
||||
crate::scene::view::render::layer_render_style_viewport(
|
||||
&self.document,
|
||||
&dxf.common.layer,
|
||||
viewport,
|
||||
)
|
||||
.color,
|
||||
aci,
|
||||
|
|
@ -970,11 +982,6 @@ impl Scene {
|
|||
.collect();
|
||||
|
||||
// Background for adapting block-child hatch colours at the leaf (#221).
|
||||
let hatch_bg: [f32; 4] = if self.current_layout != "Model" {
|
||||
self.paper_bg_color
|
||||
} else {
|
||||
self.bg_color
|
||||
};
|
||||
// Instanced/owned hatch leaves are produced by the shared scene graph.
|
||||
models.extend(self.instanced_hatch_models(
|
||||
target_block,
|
||||
|
|
@ -983,6 +990,7 @@ impl Scene {
|
|||
frozen,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
viewport,
|
||||
));
|
||||
|
||||
// Wide polyline bands remain on the wire path, including inside block
|
||||
|
|
@ -1009,6 +1017,7 @@ impl Scene {
|
|||
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
viewport: Option<Handle>,
|
||||
) -> Vec<HatchModel> {
|
||||
self.instanced_hatch_models_filtered(
|
||||
layout_block,
|
||||
|
|
@ -1017,6 +1026,7 @@ impl Scene {
|
|||
frozen,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
viewport,
|
||||
None,
|
||||
false,
|
||||
)
|
||||
|
|
@ -1055,6 +1065,7 @@ impl Scene {
|
|||
(!frozen.is_empty()).then_some(&frozen),
|
||||
self.displayed_annotation_scale_handle(),
|
||||
self.annotation_all_visible(),
|
||||
self.active_viewport,
|
||||
Some(&targets),
|
||||
true,
|
||||
)
|
||||
|
|
@ -1068,6 +1079,7 @@ impl Scene {
|
|||
frozen: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
viewport: Option<Handle>,
|
||||
targets: Option<&rustc_hash::FxHashSet<Handle>>,
|
||||
include_preview_hidden: bool,
|
||||
) -> Vec<HatchModel> {
|
||||
|
|
@ -1078,7 +1090,8 @@ impl Scene {
|
|||
annotation_scale_handle,
|
||||
all_visible,
|
||||
depth_map.as_ref(),
|
||||
);
|
||||
)
|
||||
.with_viewport(viewport);
|
||||
let mut hatch_block_memo = std::collections::HashMap::new();
|
||||
let mut models = Vec::new();
|
||||
graph.walk_root(
|
||||
|
|
|
|||
|
|
@ -1471,6 +1471,12 @@ struct SceneLight {
|
|||
attenuation_type: f32,
|
||||
attenuation_start: f32,
|
||||
attenuation_end: f32,
|
||||
cast_shadows: bool,
|
||||
shadow_softness: f32,
|
||||
shadow_map_size: u32,
|
||||
web_profile: [f32; 8],
|
||||
web_rotation: [f32; 3],
|
||||
web_enabled: bool,
|
||||
}
|
||||
|
||||
pub struct Scene {
|
||||
|
|
@ -2078,6 +2084,7 @@ impl Scene {
|
|||
&self,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
viewport: Option<Handle>,
|
||||
) -> Arc<cache::block_cache::BlockCache> {
|
||||
let bg = if self.current_layout == "Model" {
|
||||
self.bg_color
|
||||
|
|
@ -2089,6 +2096,7 @@ impl Scene {
|
|||
key = key.rotate_left(13) ^ component.to_bits() as u64;
|
||||
}
|
||||
key = key.rotate_left(13) ^ all_visible as u64;
|
||||
key = key.rotate_left(13) ^ viewport.map(|handle| handle.value()).unwrap_or(0);
|
||||
{
|
||||
let cache = self.block_defn_cache.borrow();
|
||||
if let Some((epoch, arc)) = cache.get(&key) {
|
||||
|
|
@ -2107,6 +2115,7 @@ impl Scene {
|
|||
annotation_scale_handle,
|
||||
all_visible,
|
||||
bg,
|
||||
viewport,
|
||||
&self.draw_depth_map(),
|
||||
);
|
||||
let arc = Arc::new(built);
|
||||
|
|
@ -2134,6 +2143,7 @@ impl Scene {
|
|||
scale,
|
||||
self.annotation_all_visible(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let gen = WIRE_CONTENT_GEN.fetch_add(1, Ordering::Relaxed);
|
||||
self.last_model_wire_gen.set(gen);
|
||||
|
|
@ -4634,7 +4644,7 @@ impl Scene {
|
|||
&self.document,
|
||||
&self.document.header.current_annotation_scale,
|
||||
);
|
||||
self.resident_wires_for(block, None, scale, None)
|
||||
self.resident_wires_for(block, None, scale, None, None)
|
||||
}
|
||||
|
||||
/// Unified static-hold wire builder — the ONE tessellation path every
|
||||
|
|
@ -4682,6 +4692,7 @@ impl Scene {
|
|||
anno_scale_override: Option<f32>,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
frozen_layers: Option<&HashSet<Handle>>,
|
||||
style_viewport: Option<Handle>,
|
||||
) -> Arc<Vec<WireModel>> {
|
||||
// Normalize an inert anno override away so distinct viewport scales
|
||||
// share one resident set when annotation can't change the wires.
|
||||
|
|
@ -4705,6 +4716,7 @@ impl Scene {
|
|||
annotation_scale_handle,
|
||||
all_visible,
|
||||
frozen_layers,
|
||||
style_viewport,
|
||||
);
|
||||
{
|
||||
let sets = self.resident_wire_sets.borrow();
|
||||
|
|
@ -4728,6 +4740,7 @@ impl Scene {
|
|||
annotation_scale_handle,
|
||||
all_visible,
|
||||
frozen_layers,
|
||||
style_viewport,
|
||||
)
|
||||
{
|
||||
return arc;
|
||||
|
|
@ -4744,6 +4757,7 @@ impl Scene {
|
|||
anno_scale_override,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
style_viewport,
|
||||
);
|
||||
// Synthesized nonprint markers (geo-location daisy) live in model space
|
||||
// only and are derived from document objects, not entities — append them
|
||||
|
|
@ -4791,6 +4805,7 @@ impl Scene {
|
|||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
frozen_layers: Option<&HashSet<Handle>>,
|
||||
style_viewport: Option<Handle>,
|
||||
) -> u64 {
|
||||
let mut key: u64 = 0xcbf2_9ce4_8422_2325;
|
||||
let mut mix =
|
||||
|
|
@ -4804,6 +4819,7 @@ impl Scene {
|
|||
.unwrap_or(u64::MAX));
|
||||
mix(annotation_scale_handle.map(|handle| handle.value()).unwrap_or(0));
|
||||
mix(all_visible as u64);
|
||||
mix(style_viewport.map(|handle| handle.value()).unwrap_or(0));
|
||||
match frozen_layers {
|
||||
Some(frozen) => {
|
||||
let mut signature = 0u64;
|
||||
|
|
@ -4903,7 +4919,11 @@ impl Scene {
|
|||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
frozen_layers: Option<&HashSet<Handle>>,
|
||||
style_viewport: Option<Handle>,
|
||||
) -> Option<Arc<Vec<WireModel>>> {
|
||||
if style_viewport.is_some() {
|
||||
return None;
|
||||
}
|
||||
let perf = crate::perf::enabled();
|
||||
let t_patch = iced::time::Instant::now();
|
||||
// The entry must exist, be stale, and be uniquely held so we can move
|
||||
|
|
@ -4951,7 +4971,7 @@ impl Scene {
|
|||
} else {
|
||||
1.0
|
||||
};
|
||||
let blk = self.block_cache_arc_for(annotation_scale_handle, all_visible);
|
||||
let blk = self.block_cache_arc_for(annotation_scale_handle, all_visible, style_viewport);
|
||||
let empty_sel: HashSet<Handle> = HashSet::default();
|
||||
let mut new_runs: HashMap<Handle, Vec<WireModel>> = HashMap::default();
|
||||
let mut memo_updates: Vec<(Handle, Arc<Vec<WireModel>>)> = Vec::new();
|
||||
|
|
@ -4976,7 +4996,7 @@ impl Scene {
|
|||
let raw = tessellate_entity(
|
||||
&self.document,
|
||||
&empty_sel,
|
||||
self.active_viewport,
|
||||
style_viewport.or(self.active_viewport),
|
||||
bg,
|
||||
anno,
|
||||
annotation_scale_handle,
|
||||
|
|
@ -5265,7 +5285,7 @@ impl Scene {
|
|||
}
|
||||
let layout_block = self.current_layout_block_handle();
|
||||
let scale = self.paper_annotation_scale_handle();
|
||||
let base = self.resident_wires_for(layout_block, None, scale, None);
|
||||
let base = self.resident_wires_for(layout_block, None, scale, None, None);
|
||||
let mut wires = (*base).clone();
|
||||
// The overall "sheet" viewport now IS the paper view itself, so its own
|
||||
// border rectangle must not be drawn as an entity on the sheet.
|
||||
|
|
@ -5650,6 +5670,7 @@ impl Scene {
|
|||
None,
|
||||
scale,
|
||||
self.annotation_all_visible(),
|
||||
None,
|
||||
));
|
||||
self.hatch_cache.borrow_mut().insert(
|
||||
key,
|
||||
|
|
@ -5796,7 +5817,8 @@ impl Scene {
|
|||
annotation_scale_handle,
|
||||
all_visible,
|
||||
depth_map.as_ref(),
|
||||
);
|
||||
)
|
||||
.with_viewport(viewport);
|
||||
let mut models = Vec::new();
|
||||
let root = if self.block_edit_block.is_none() {
|
||||
viewport.map(|viewport| render_graph::SceneRoot::Viewport {
|
||||
|
|
@ -5923,6 +5945,7 @@ impl Scene {
|
|||
None,
|
||||
scale,
|
||||
self.annotation_all_visible(),
|
||||
None,
|
||||
));
|
||||
self.mesh_cache
|
||||
.borrow_mut()
|
||||
|
|
@ -6034,6 +6057,7 @@ impl Scene {
|
|||
None,
|
||||
self.displayed_annotation_scale_handle(),
|
||||
self.annotation_all_visible(),
|
||||
self.active_viewport,
|
||||
));
|
||||
*self.interaction_mesh_cache.borrow_mut() =
|
||||
Some((self.geometry_epoch, key, Arc::clone(&meshes)));
|
||||
|
|
@ -6083,6 +6107,7 @@ impl Scene {
|
|||
frozen: Option<&HashSet<Handle>>,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
viewport: Option<Handle>,
|
||||
) -> Vec<MeshLodSet> {
|
||||
// Top-level solids: drop those whose layer is off/frozen or that are
|
||||
// flagged invisible / isolated-hidden, mirroring the 2D wire path, plus
|
||||
|
|
@ -6105,7 +6130,33 @@ impl Scene {
|
|||
})
|
||||
.unwrap_or(false)
|
||||
})
|
||||
.map(|(_, set)| set.clone())
|
||||
.map(|(&handle, set)| {
|
||||
let mut set = set.clone();
|
||||
if let Some(entity) = self.document.get_entity(handle) {
|
||||
let style = crate::scene::view::render::render_style_for_viewport(
|
||||
&self.document,
|
||||
entity,
|
||||
viewport,
|
||||
);
|
||||
let attached_material = set
|
||||
.material
|
||||
.as_ref()
|
||||
.is_some_and(|material| material.handle.is_some());
|
||||
for mesh in &mut set.lods {
|
||||
if !attached_material {
|
||||
mesh.color[0..3].copy_from_slice(&style.0[0..3]);
|
||||
}
|
||||
mesh.color[3] = style.0[3];
|
||||
}
|
||||
if let Some(material) = set.material.as_mut() {
|
||||
if !attached_material {
|
||||
material.diffuse[0..3].copy_from_slice(&style.0[0..3]);
|
||||
}
|
||||
material.diffuse[3] = style.0[3];
|
||||
}
|
||||
}
|
||||
set
|
||||
})
|
||||
.collect();
|
||||
// Block-definition solids are instanced per INSERT of the ACTIVE space's
|
||||
// block so a block placed at an INSERT scale renders at the right size
|
||||
|
|
@ -6116,6 +6167,7 @@ impl Scene {
|
|||
frozen,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
viewport,
|
||||
));
|
||||
all
|
||||
}
|
||||
|
|
@ -6171,7 +6223,8 @@ impl Scene {
|
|||
let scale = self.viewport_scale_handle(viewport);
|
||||
let all_visible = self.annotation_all_visible();
|
||||
let context_sig = scale.map_or(0, |handle| handle.value())
|
||||
^ u64::from(all_visible).rotate_left(61);
|
||||
^ u64::from(all_visible).rotate_left(61)
|
||||
^ viewport.value().rotate_left(23);
|
||||
let sig = Self::frozen_layers_sig(frozen) ^ context_sig;
|
||||
let key = (target_block, self.current_layout.clone(), sig);
|
||||
let sel = self.selected_hatch_sig();
|
||||
|
|
@ -6185,6 +6238,7 @@ impl Scene {
|
|||
Some(frozen),
|
||||
scale,
|
||||
all_visible,
|
||||
Some(viewport),
|
||||
));
|
||||
self.frozen_hatch_cache
|
||||
.borrow_mut()
|
||||
|
|
@ -6205,7 +6259,8 @@ impl Scene {
|
|||
let scale = self.viewport_scale_handle(viewport);
|
||||
let all_visible = self.annotation_all_visible();
|
||||
let context_sig = scale.map_or(0, |handle| handle.value())
|
||||
^ u64::from(all_visible).rotate_left(61);
|
||||
^ u64::from(all_visible).rotate_left(61)
|
||||
^ viewport.value().rotate_left(23);
|
||||
let sig = Self::frozen_layers_sig(frozen) ^ context_sig;
|
||||
let key = (target_block, self.current_layout.clone(), sig);
|
||||
if let Some((e, arc)) = self.frozen_wipeout_cache.borrow().get(&key) {
|
||||
|
|
@ -6238,7 +6293,8 @@ impl Scene {
|
|||
let scale = self.viewport_scale_handle(viewport);
|
||||
let all_visible = self.annotation_all_visible();
|
||||
let context_sig = scale.map_or(0, |handle| handle.value())
|
||||
^ u64::from(all_visible).rotate_left(61);
|
||||
^ u64::from(all_visible).rotate_left(61)
|
||||
^ viewport.value().rotate_left(23);
|
||||
let sig = Self::frozen_layers_sig(frozen) ^ context_sig;
|
||||
let key = (target_block, sig);
|
||||
if let Some((e, arc)) = self.frozen_image_cache.borrow().get(&key) {
|
||||
|
|
@ -6272,7 +6328,8 @@ impl Scene {
|
|||
let scale = self.viewport_scale_handle(viewport);
|
||||
let all_visible = self.annotation_all_visible();
|
||||
let context_sig = scale.map_or(0, |handle| handle.value())
|
||||
^ u64::from(all_visible).rotate_left(61);
|
||||
^ u64::from(all_visible).rotate_left(61)
|
||||
^ viewport.value().rotate_left(23);
|
||||
let sig = Self::frozen_layers_sig(frozen) ^ context_sig;
|
||||
let key = (target_block, self.current_layout.clone(), sig);
|
||||
if let Some((e, arc)) = self.frozen_mesh_cache.borrow().get(&key) {
|
||||
|
|
@ -6285,6 +6342,7 @@ impl Scene {
|
|||
Some(frozen),
|
||||
scale,
|
||||
all_visible,
|
||||
Some(viewport),
|
||||
));
|
||||
self.frozen_mesh_cache
|
||||
.borrow_mut()
|
||||
|
|
@ -6364,6 +6422,7 @@ impl Scene {
|
|||
frozen: Option<&HashSet<Handle>>,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
viewport: Option<Handle>,
|
||||
) -> Vec<MeshLodSet> {
|
||||
if self.block_meshes.is_empty() {
|
||||
return Vec::new();
|
||||
|
|
@ -6375,7 +6434,8 @@ impl Scene {
|
|||
annotation_scale_handle,
|
||||
all_visible,
|
||||
depth_map.as_ref(),
|
||||
);
|
||||
)
|
||||
.with_viewport(viewport);
|
||||
let mut out = Vec::new();
|
||||
graph.walk_root(
|
||||
self.render_scene_root(layout_block),
|
||||
|
|
@ -7960,6 +8020,7 @@ impl Scene {
|
|||
anno_scale_override: Option<f32>,
|
||||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
style_viewport: Option<Handle>,
|
||||
) -> Vec<WireModel> {
|
||||
use acadrust::objects::ObjectType;
|
||||
|
||||
|
|
@ -8065,7 +8126,7 @@ impl Scene {
|
|||
// `geometry_epoch` (a full model re-tessellation).
|
||||
let empty_sel: HashSet<Handle> = HashSet::default();
|
||||
let sel: &HashSet<Handle> = &empty_sel;
|
||||
let avp = self.active_viewport;
|
||||
let avp = style_viewport.or(self.active_viewport);
|
||||
// A paper-space content viewport renders MODEL block entities while
|
||||
// the user is sitting in a paper layout — that path expects
|
||||
// `world_offset` subtraction even though `current_layout != "Model"`.
|
||||
|
|
@ -8086,7 +8147,7 @@ impl Scene {
|
|||
// Content shown inside a paper-space viewport carries a scale override;
|
||||
// only there does PSLTSCALE resize linetypes by the viewport scale.
|
||||
let paper = anno_scale_override.is_some();
|
||||
let blk_cache = self.block_cache_arc_for(annotation_scale_handle, all_visible);
|
||||
let blk_cache = self.block_cache_arc_for(annotation_scale_handle, all_visible, style_viewport);
|
||||
let blk_ref: &cache::block_cache::BlockCache = &blk_cache;
|
||||
// Zoom-adaptive curve sampling for top-level Edge tessellation. Target
|
||||
// ~0.5 px chord height — far-out arcs that used to emit hundreds of
|
||||
|
|
@ -8863,6 +8924,7 @@ impl Scene {
|
|||
let blk_cache = self.block_cache_arc_for(
|
||||
annotation_scale_handle,
|
||||
self.annotation_all_visible(),
|
||||
self.active_viewport,
|
||||
);
|
||||
// tessellate_one is used for one-off lookups (hit test, properties).
|
||||
// Skip culling here so the caller always gets the full geometry.
|
||||
|
|
@ -8945,6 +9007,7 @@ impl Scene {
|
|||
None,
|
||||
scale,
|
||||
self.annotation_all_visible(),
|
||||
None,
|
||||
);
|
||||
let mut min = glam::DVec2::splat(f64::INFINITY);
|
||||
let mut max = glam::DVec2::splat(f64::NEG_INFINITY);
|
||||
|
|
|
|||
|
|
@ -349,7 +349,7 @@ impl ImageModel {
|
|||
|
||||
/// Decoded RGBA image shared between the raster pipeline and the
|
||||
/// unresolved-reference probe. Cheap to clone — the pixels are `Arc`-shared.
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DecodedImage {
|
||||
pub pixels: Arc<Vec<u8>>,
|
||||
pub width: u32,
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ fn property_color(style: &VisualStyle, index: usize) -> Option<[f32; 4]> {
|
|||
}
|
||||
|
||||
impl MeshVisualStyle {
|
||||
fn from_dwg(handle: Handle, style: &VisualStyle) -> Self {
|
||||
pub(crate) fn from_dwg(handle: Handle, style: &VisualStyle) -> Self {
|
||||
Self {
|
||||
full_handle: Some(handle),
|
||||
face_handle: None,
|
||||
|
|
@ -232,6 +232,14 @@ impl MeshVisualStyle {
|
|||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_visual_style_handle(
|
||||
document: &CadDocument,
|
||||
handle: Handle,
|
||||
) -> Option<MeshVisualStyle> {
|
||||
visual_style(document, Some(handle))
|
||||
.map(|(handle, style)| MeshVisualStyle::from_dwg(handle, style))
|
||||
}
|
||||
|
||||
fn visual_style(
|
||||
document: &CadDocument,
|
||||
handle: Option<Handle>,
|
||||
|
|
|
|||
|
|
@ -360,6 +360,10 @@ impl Scene {
|
|||
vp.view_direction.y = dir.y as f64;
|
||||
vp.view_direction.z = dir.z as f64;
|
||||
vp.twist_angle = twist;
|
||||
vp.status.perspective =
|
||||
tmp.projection == view::camera::Projection::Perspective;
|
||||
vp.lens_length =
|
||||
(12.0 / (tmp.fov_y * 0.5).tan().max(1e-6)) as f64;
|
||||
return true;
|
||||
}
|
||||
false
|
||||
|
|
|
|||
|
|
@ -477,6 +477,7 @@ impl Scene {
|
|||
None,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
None,
|
||||
);
|
||||
models.extend(instanced);
|
||||
Arc::new(models)
|
||||
|
|
@ -560,6 +561,7 @@ impl Scene {
|
|||
frozen,
|
||||
annotation_scale_handle,
|
||||
all_visible,
|
||||
None,
|
||||
));
|
||||
models
|
||||
}
|
||||
|
|
@ -679,7 +681,7 @@ impl Scene {
|
|||
let center_set =
|
||||
vp.view_center.x.abs() > 1e-6 || vp.view_center.y.abs() > 1e-6;
|
||||
|
||||
if let Some(cam) = self.camera_from_view(
|
||||
if let Some(cam) = self.camera_from_view_mode(
|
||||
vp.view_direction,
|
||||
vp.view_target,
|
||||
acadrust::types::Vector2 {
|
||||
|
|
@ -688,6 +690,8 @@ impl Scene {
|
|||
},
|
||||
saved_h,
|
||||
vp.twist_angle,
|
||||
vp.status.perspective,
|
||||
vp.lens_length,
|
||||
) {
|
||||
if target_set || !center_set {
|
||||
return Some(cam);
|
||||
|
|
@ -718,12 +722,14 @@ impl Scene {
|
|||
y: cy,
|
||||
z: vp.view_target.z,
|
||||
};
|
||||
self.camera_from_view(
|
||||
self.camera_from_view_mode(
|
||||
vp.view_direction,
|
||||
tgt,
|
||||
acadrust::types::Vector2::ZERO,
|
||||
fit_h,
|
||||
vp.twist_angle,
|
||||
vp.status.perspective,
|
||||
vp.lens_length,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -758,6 +764,7 @@ impl Scene {
|
|||
Some(self.viewport_annotation_multiplier(vp_handle)),
|
||||
scale_handle,
|
||||
Some(&frozen),
|
||||
Some(vp_handle),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -612,7 +612,7 @@ pub fn create_material_bind_group(
|
|||
[
|
||||
material.advanced_data_present as u32,
|
||||
material.is_anonymous as u32,
|
||||
0,
|
||||
material.handle.is_some() as u32,
|
||||
0,
|
||||
]
|
||||
}),
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ use device_capabilities::DeviceCapabilities;
|
|||
|
||||
/// MSAA sample count for the main drawing pipelines.
|
||||
const MSAA_SAMPLES: u32 = 4;
|
||||
const SHADOW_MAP_SIZE: u32 = 2048;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum MeshHighlightKind {
|
||||
|
|
@ -74,6 +75,8 @@ struct MeshCullUniform {
|
|||
}
|
||||
|
||||
pub struct Pipeline {
|
||||
background_pipeline: wgpu::RenderPipeline,
|
||||
shadow_pipeline: wgpu::RenderPipeline,
|
||||
wire_pipeline: wgpu::RenderPipeline,
|
||||
/// Stamps clip-boundary polygons into the stencil buffer (viewports + XCLIP).
|
||||
clip_mask_pipeline: wgpu::RenderPipeline,
|
||||
|
|
@ -130,7 +133,18 @@ pub struct Pipeline {
|
|||
/// writes depth). Paired with `mesh_depth_pipeline` for HiddenLine.
|
||||
face3d_depth_pipeline: wgpu::RenderPipeline,
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
frame_bgl: wgpu::BindGroupLayout,
|
||||
uniform_bind_group: wgpu::BindGroup,
|
||||
shadow_uniform_bind_group: wgpu::BindGroup,
|
||||
background_texture: wgpu::Texture,
|
||||
environment_texture: wgpu::Texture,
|
||||
background_sampler: wgpu::Sampler,
|
||||
background_source_id: usize,
|
||||
environment_source_id: usize,
|
||||
_shadow_texture: wgpu::Texture,
|
||||
shadow_view: wgpu::TextureView,
|
||||
shadow_sampler: wgpu::Sampler,
|
||||
shadow_enabled: bool,
|
||||
wipeout_bgl1: wgpu::BindGroupLayout,
|
||||
image_bgl1: wgpu::BindGroupLayout,
|
||||
/// Group-1 layout for the text pipeline (atlas texture + sampler).
|
||||
|
|
@ -347,26 +361,198 @@ impl Pipeline {
|
|||
// Bind group layout 0 — shared by wire and hatch pipelines.
|
||||
let frame_bgl = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("viewer.frame_bgl"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
entries: &[
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 1,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 2,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 3,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Float { filterable: true },
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 4,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 5,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Texture {
|
||||
sample_type: wgpu::TextureSampleType::Depth,
|
||||
view_dimension: wgpu::TextureViewDimension::D2,
|
||||
multisampled: false,
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
wgpu::BindGroupLayoutEntry {
|
||||
binding: 6,
|
||||
visibility: wgpu::ShaderStages::FRAGMENT,
|
||||
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
|
||||
count: None,
|
||||
},
|
||||
],
|
||||
});
|
||||
let shadow_frame_bgl = device.create_bind_group_layout(
|
||||
&wgpu::BindGroupLayoutDescriptor {
|
||||
label: Some("shadow.frame_bgl"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
},
|
||||
);
|
||||
|
||||
let placeholder_texture = |label: &'static str, rgba: [u8; 4]| {
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some(label),
|
||||
size: wgpu::Extent3d {
|
||||
width: 1,
|
||||
height: 1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
});
|
||||
queue.write_texture(
|
||||
texture.as_image_copy(),
|
||||
&rgba,
|
||||
wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(4),
|
||||
rows_per_image: Some(1),
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: 1,
|
||||
height: 1,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
texture
|
||||
};
|
||||
let background_texture = placeholder_texture("background.placeholder", [0, 0, 0, 255]);
|
||||
let environment_texture =
|
||||
placeholder_texture("environment.placeholder", [128, 128, 128, 255]);
|
||||
let background_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("background.sampler"),
|
||||
address_mode_u: wgpu::AddressMode::Repeat,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
mipmap_filter: wgpu::MipmapFilterMode::Linear,
|
||||
..Default::default()
|
||||
});
|
||||
let shadow_texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("shadow.texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width: SHADOW_MAP_SIZE,
|
||||
height: SHADOW_MAP_SIZE,
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING,
|
||||
view_formats: &[],
|
||||
});
|
||||
let shadow_view = shadow_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let shadow_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: Some("shadow.sampler"),
|
||||
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||
compare: Some(wgpu::CompareFunction::LessEqual),
|
||||
mag_filter: wgpu::FilterMode::Linear,
|
||||
min_filter: wgpu::FilterMode::Linear,
|
||||
..Default::default()
|
||||
});
|
||||
let background_view = background_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let environment_view = environment_texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("viewer.bind_group"),
|
||||
layout: &frame_bgl,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: uniform_buffer.as_entire_binding(),
|
||||
}],
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: uniform_buffer.as_entire_binding(),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::TextureView(&background_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::Sampler(&background_sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 3,
|
||||
resource: wgpu::BindingResource::TextureView(&environment_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 4,
|
||||
resource: wgpu::BindingResource::Sampler(&background_sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 5,
|
||||
resource: wgpu::BindingResource::TextureView(&shadow_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 6,
|
||||
resource: wgpu::BindingResource::Sampler(&shadow_sampler),
|
||||
},
|
||||
],
|
||||
});
|
||||
let shadow_uniform_bind_group = device.create_bind_group(
|
||||
&wgpu::BindGroupDescriptor {
|
||||
label: Some("shadow.frame_bind_group"),
|
||||
layout: &shadow_frame_bgl,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: uniform_buffer.as_entire_binding(),
|
||||
}],
|
||||
},
|
||||
);
|
||||
|
||||
// ── Wire pipeline ──────────────────────────────────────────────────
|
||||
// Select once from actual device limits. Any device whose compositor
|
||||
|
|
@ -424,6 +610,57 @@ impl Pipeline {
|
|||
write_mask: 0x00,
|
||||
};
|
||||
|
||||
let background_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("background.shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!(
|
||||
"../../shaders/background.wgsl"
|
||||
))),
|
||||
});
|
||||
let background_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("background.pipeline_layout"),
|
||||
bind_group_layouts: &[Some(&frame_bgl)],
|
||||
immediate_size: 0,
|
||||
});
|
||||
let background_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("background.pipeline"),
|
||||
layout: Some(&background_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &background_shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
cull_mode: None,
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: wgpu::TextureFormat::Depth24PlusStencil8,
|
||||
depth_write_enabled: Some(false),
|
||||
depth_compare: Some(wgpu::CompareFunction::Always),
|
||||
stencil: content_stencil.clone(),
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: MSAA_SAMPLES,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &background_shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(wgpu::BlendState::REPLACE),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
let wire_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("wire.pipeline"),
|
||||
layout: Some(&wire_layout),
|
||||
|
|
@ -1033,6 +1270,55 @@ impl Pipeline {
|
|||
immediate_size: 0,
|
||||
});
|
||||
|
||||
let shadow_source = include_str!("../../shaders/shadow.wgsl");
|
||||
let shadow_source = if mesh_storage_instancing {
|
||||
std::borrow::Cow::Borrowed(shadow_source)
|
||||
} else {
|
||||
std::borrow::Cow::Owned(shadow_source.replace(
|
||||
"var<storage, read> mesh_instances: array<MeshInstance>;",
|
||||
"var<uniform> mesh_instances: array<MeshInstance, 1>;",
|
||||
))
|
||||
};
|
||||
let shadow_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("shadow.shader"),
|
||||
source: wgpu::ShaderSource::Wgsl(shadow_source),
|
||||
});
|
||||
let shadow_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: Some("shadow.pipeline_layout"),
|
||||
bind_group_layouts: &[&shadow_frame_bgl, &mesh_material_bgl].map(Some),
|
||||
immediate_size: 0,
|
||||
});
|
||||
let shadow_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("shadow.pipeline"),
|
||||
layout: Some(&shadow_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shadow_shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[mesh_gpu::MeshVertex::layout()],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::TriangleList,
|
||||
cull_mode: None,
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
depth_write_enabled: Some(true),
|
||||
depth_compare: Some(wgpu::CompareFunction::LessEqual),
|
||||
stencil: wgpu::StencilState::default(),
|
||||
bias: wgpu::DepthBiasState {
|
||||
constant: 2,
|
||||
slope_scale: 2.0,
|
||||
clamp: 0.0,
|
||||
},
|
||||
}),
|
||||
multisample: wgpu::MultisampleState::default(),
|
||||
fragment: None,
|
||||
multiview_mask: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
let mesh_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("mesh.pipeline"),
|
||||
layout: Some(&mesh_layout),
|
||||
|
|
@ -1612,6 +1898,8 @@ impl Pipeline {
|
|||
});
|
||||
|
||||
Self {
|
||||
background_pipeline,
|
||||
shadow_pipeline,
|
||||
wire_pipeline,
|
||||
clip_mask_pipeline,
|
||||
wire_black_pipeline,
|
||||
|
|
@ -1654,7 +1942,18 @@ impl Pipeline {
|
|||
face3d_pipeline,
|
||||
face3d_depth_pipeline,
|
||||
uniform_buffer,
|
||||
frame_bgl,
|
||||
uniform_bind_group,
|
||||
shadow_uniform_bind_group,
|
||||
background_texture,
|
||||
environment_texture,
|
||||
background_sampler,
|
||||
background_source_id: 0,
|
||||
environment_source_id: 0,
|
||||
_shadow_texture: shadow_texture,
|
||||
shadow_view,
|
||||
shadow_sampler,
|
||||
shadow_enabled: false,
|
||||
wipeout_bgl1,
|
||||
image_bgl1,
|
||||
depth_texture_size: Size::new(1, 1),
|
||||
|
|
@ -2819,10 +3118,118 @@ impl Pipeline {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn upload_uniforms(&self, queue: &wgpu::Queue, uniforms: &Uniforms) {
|
||||
pub fn upload_uniforms(&mut self, queue: &wgpu::Queue, uniforms: &Uniforms) {
|
||||
self.shadow_enabled = uniforms.shadow_params[0] > 0.5;
|
||||
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::bytes_of(uniforms));
|
||||
}
|
||||
|
||||
pub fn upload_background_images(
|
||||
&mut self,
|
||||
device: &wgpu::Device,
|
||||
queue: &wgpu::Queue,
|
||||
background: Option<&crate::scene::model::image_model::DecodedImage>,
|
||||
environment: Option<&crate::scene::model::image_model::DecodedImage>,
|
||||
) {
|
||||
let source_id = |image: Option<&crate::scene::model::image_model::DecodedImage>| {
|
||||
image
|
||||
.map(|image| std::sync::Arc::as_ptr(&image.pixels) as usize)
|
||||
.unwrap_or(0)
|
||||
};
|
||||
let background_id = source_id(background);
|
||||
let environment_id = source_id(environment);
|
||||
if background_id == self.background_source_id
|
||||
&& environment_id == self.environment_source_id
|
||||
{
|
||||
return;
|
||||
}
|
||||
let upload = |
|
||||
label: &'static str,
|
||||
image: Option<&crate::scene::model::image_model::DecodedImage>,
|
||||
fallback: [u8; 4],
|
||||
| {
|
||||
let (width, height, pixels) = image
|
||||
.map(|image| (image.width, image.height, image.pixels.as_slice()))
|
||||
.unwrap_or((1, 1, fallback.as_slice()));
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some(label),
|
||||
size: wgpu::Extent3d {
|
||||
width: width.max(1),
|
||||
height: height.max(1),
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
|
||||
view_formats: &[],
|
||||
});
|
||||
queue.write_texture(
|
||||
texture.as_image_copy(),
|
||||
pixels,
|
||||
wgpu::TexelCopyBufferLayout {
|
||||
offset: 0,
|
||||
bytes_per_row: Some(4 * width.max(1)),
|
||||
rows_per_image: Some(height.max(1)),
|
||||
},
|
||||
wgpu::Extent3d {
|
||||
width: width.max(1),
|
||||
height: height.max(1),
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
);
|
||||
texture
|
||||
};
|
||||
self.background_texture = upload("background.texture", background, [0, 0, 0, 255]);
|
||||
self.environment_texture = upload(
|
||||
"environment.texture",
|
||||
environment,
|
||||
[128, 128, 128, 255],
|
||||
);
|
||||
let background_view = self
|
||||
.background_texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let environment_view = self
|
||||
.environment_texture
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
self.uniform_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some("viewer.bind_group"),
|
||||
layout: &self.frame_bgl,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
resource: self.uniform_buffer.as_entire_binding(),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 1,
|
||||
resource: wgpu::BindingResource::TextureView(&background_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 2,
|
||||
resource: wgpu::BindingResource::Sampler(&self.background_sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 3,
|
||||
resource: wgpu::BindingResource::TextureView(&environment_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 4,
|
||||
resource: wgpu::BindingResource::Sampler(&self.background_sampler),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 5,
|
||||
resource: wgpu::BindingResource::TextureView(&self.shadow_view),
|
||||
},
|
||||
wgpu::BindGroupEntry {
|
||||
binding: 6,
|
||||
resource: wgpu::BindingResource::Sampler(&self.shadow_sampler),
|
||||
},
|
||||
],
|
||||
});
|
||||
self.background_source_id = background_id;
|
||||
self.environment_source_id = environment_id;
|
||||
}
|
||||
|
||||
/// Write the blit shader's UV crop uniform. Call in `prepare` (the only
|
||||
/// place with a `&Queue`) — `render` then just submits the draw call.
|
||||
pub fn upload_blit_uv(&self, queue: &wgpu::Queue, uv_offset: [f32; 2], uv_scale: [f32; 2]) {
|
||||
|
|
@ -2869,11 +3276,15 @@ impl Pipeline {
|
|||
};
|
||||
let msaa = &self.msaa_view;
|
||||
let [r, g, b, a] = bg_color;
|
||||
let clear_color = wgpu::Color {
|
||||
r: r as f64,
|
||||
g: g as f64,
|
||||
b: b as f64,
|
||||
a: a as f64,
|
||||
let clear_color = if self.clip_boundary.is_some() {
|
||||
wgpu::Color::TRANSPARENT
|
||||
} else {
|
||||
wgpu::Color {
|
||||
r: r as f64,
|
||||
g: g as f64,
|
||||
b: b as f64,
|
||||
a: a as f64,
|
||||
}
|
||||
};
|
||||
// Non-rectangular viewport clip: the boundary is stamped into the
|
||||
// just-cleared (0x00) stencil with `Invert`, so an odd (interior)
|
||||
|
|
@ -2883,6 +3294,51 @@ impl Pipeline {
|
|||
// render rectangle does the clipping).
|
||||
let stencil_ref: u32 = if self.clip_boundary.is_some() { 0xFF } else { 0 };
|
||||
|
||||
if self.shadow_enabled && !self.skip_geometry && !mesh_wireframe {
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("shadow.render_pass"),
|
||||
color_attachments: &[],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.shadow_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(1.0),
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
}),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
multiview_mask: None,
|
||||
});
|
||||
pass.set_pipeline(&self.shadow_pipeline);
|
||||
pass.set_bind_group(0, &self.shadow_uniform_bind_group, &[]);
|
||||
for (_, chunk) in self.active_mesh_chunks_indexed() {
|
||||
if !chunk.visible {
|
||||
continue;
|
||||
}
|
||||
pass.set_bind_group(
|
||||
1,
|
||||
chunk
|
||||
.material_bind_group
|
||||
.as_ref()
|
||||
.unwrap_or(&self.mesh_default_material_bind_group),
|
||||
&[],
|
||||
);
|
||||
pass.set_vertex_buffer(0, chunk.vertex_buffer.slice(..));
|
||||
if chunk.index_count != 0 {
|
||||
pass.set_index_buffer(chunk.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
|
||||
pass.draw_indexed(0..chunk.index_count, 0, 0..chunk.instance_count);
|
||||
}
|
||||
if chunk.transp_index_count != 0 {
|
||||
pass.set_index_buffer(
|
||||
chunk.transp_index_buffer.slice(..),
|
||||
wgpu::IndexFormat::Uint32,
|
||||
);
|
||||
pass.draw_indexed(0..chunk.transp_index_count, 0, 0..chunk.instance_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Scene-render cache: when `prepare` found this frame pixel-identical
|
||||
// to the last (unchanged view / geometry / selection / preview), the
|
||||
// resolve texture already holds the image. Skip every geometry pass +
|
||||
|
|
@ -2931,6 +3387,10 @@ impl Pipeline {
|
|||
pass.set_vertex_buffer(0, vbuf.slice(..));
|
||||
pass.draw(0..*vcount, 0..1);
|
||||
}
|
||||
pass.set_pipeline(&self.background_pipeline);
|
||||
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||
pass.set_stencil_reference(stencil_ref);
|
||||
pass.draw(0..3, 0..1);
|
||||
// The capability-selected façade dispatches storage or texture
|
||||
// draws before wires so outlines remain on top in either backend.
|
||||
// Skipped while navigating because per-pixel hatch work dominates
|
||||
|
|
|
|||
|
|
@ -53,8 +53,51 @@ pub struct Uniforms {
|
|||
pub light_color_hotspot: [[f32; 4]; 4],
|
||||
/// x = attenuation mode, y/z = start/end limits, w = cosine of falloff.
|
||||
pub light_attenuation: [[f32; 4]; 4],
|
||||
/// x = active light count, y = fallback ambient strength.
|
||||
/// x = active light count, yzw = per-viewport ambient-light RGB.
|
||||
pub lighting: [f32; 4],
|
||||
/// x = viewport face-colour mode, y = viewport face opacity,
|
||||
/// z = highlight intensity for faces without an attached material,
|
||||
/// w = visual-style brightness.
|
||||
pub visual_style: [f32; 4],
|
||||
/// Viewport visual-style mono/tint colour.
|
||||
pub visual_style_color: [f32; 4],
|
||||
/// RGB background selected by the viewport; alpha is one when a drawing
|
||||
/// background object, rather than the application canvas, supplies it.
|
||||
pub viewport_background: [f32; 4],
|
||||
/// x/y = normalized linear brightness/contrast in the documented
|
||||
/// -10..10 viewport range; z/w are reserved for exposure controls.
|
||||
pub view_tone: [f32; 4],
|
||||
pub background_top: [f32; 4],
|
||||
pub background_middle: [f32; 4],
|
||||
pub background_bottom: [f32; 4],
|
||||
pub background_aux0: [f32; 4],
|
||||
pub background_aux1: [f32; 4],
|
||||
/// x = mode, y = horizon, z = height, w = rotation in radians.
|
||||
pub background_params: [f32; 4],
|
||||
/// x = fit, y = keep aspect, z = tile, w = decoded image aspect.
|
||||
pub background_image_params: [f32; 4],
|
||||
/// xy = image offset, zw = image scale.
|
||||
pub background_image_transform: [f32; 4],
|
||||
/// x = environment enabled, y = rotation, z = diffuse strength,
|
||||
/// w = reflection strength.
|
||||
pub environment_params: [f32; 4],
|
||||
/// View-to-world rotation used to derive environment lookup directions.
|
||||
pub environment_view: glam::Mat4,
|
||||
/// Eye-relative light projection for the first shadow-casting source.
|
||||
pub shadow_view_proj: glam::Mat4,
|
||||
/// x = enabled, y = depth bias, z = softness, w = light index.
|
||||
pub shadow_params: [f32; 4],
|
||||
/// RGB = document render-environment fog colour.
|
||||
pub fog_color: [f32; 4],
|
||||
/// x = enabled, y = affect background, z/w = near/far density.
|
||||
pub fog_params: [f32; 4],
|
||||
/// x/y = near/far eye distance used to interpolate the density.
|
||||
pub fog_distances: [f32; 4],
|
||||
/// Eight normalized vertical-candela samples for each photometric light.
|
||||
pub light_web_profile_a: [[f32; 4]; 4],
|
||||
pub light_web_profile_b: [[f32; 4]; 4],
|
||||
/// xyz = web rotation, w = profile enabled.
|
||||
pub light_web_rotation: [[f32; 4]; 4],
|
||||
}
|
||||
|
||||
impl Uniforms {
|
||||
|
|
@ -83,7 +126,29 @@ impl Uniforms {
|
|||
light_direction_intensity: [[0.0; 4]; 4],
|
||||
light_color_hotspot: [[0.0; 4]; 4],
|
||||
light_attenuation: [[0.0; 4]; 4],
|
||||
lighting: [0.0, 0.18, 0.0, 0.0],
|
||||
lighting: [0.0, 0.18, 0.18, 0.18],
|
||||
visual_style: [1.0, 1.0, -1.0, 0.0],
|
||||
visual_style_color: [1.0, 1.0, 1.0, 1.0],
|
||||
viewport_background: [0.0, 0.0, 0.0, 0.0],
|
||||
view_tone: [0.0; 4],
|
||||
background_top: [0.0; 4],
|
||||
background_middle: [0.0; 4],
|
||||
background_bottom: [0.0; 4],
|
||||
background_aux0: [0.0; 4],
|
||||
background_aux1: [0.0; 4],
|
||||
background_params: [0.0; 4],
|
||||
background_image_params: [0.0; 4],
|
||||
background_image_transform: [0.0, 0.0, 1.0, 1.0],
|
||||
environment_params: [0.0, 0.0, 0.25, 0.35],
|
||||
environment_view: glam::Mat4::IDENTITY,
|
||||
shadow_view_proj: glam::Mat4::IDENTITY,
|
||||
shadow_params: [0.0, 0.0015, 1.0, 0.0],
|
||||
fog_color: [0.0, 0.0, 0.0, 1.0],
|
||||
fog_params: [0.0; 4],
|
||||
fog_distances: [0.0, 1.0, 0.0, 0.0],
|
||||
light_web_profile_a: [[1.0; 4]; 4],
|
||||
light_web_profile_b: [[1.0; 4]; 4],
|
||||
light_web_rotation: [[0.0; 4]; 4],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ use acadrust::{CadDocument, EntityType, Handle};
|
|||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
|
||||
use crate::scene::view::render::{
|
||||
has_resolved_book_color, is_effective_layer_zero, layer_render_style,
|
||||
render_style_for, render_style_for_block_sub, InheritStyle,
|
||||
has_resolved_book_color, is_effective_layer_zero, layer_render_style_viewport,
|
||||
render_style_for_block_sub_viewport, render_style_for_viewport, InheritStyle,
|
||||
};
|
||||
|
||||
pub type ResolvedStyle = ([f32; 4], f32, [f32; 8], f32, u8);
|
||||
|
|
@ -75,12 +75,12 @@ pub struct InsertStyleSpec {
|
|||
}
|
||||
|
||||
impl InsertStyleSpec {
|
||||
pub fn new(document: &CadDocument, insert: &Insert) -> Self {
|
||||
pub fn new(document: &CadDocument, insert: &Insert, viewport: Option<Handle>) -> Self {
|
||||
let entity = EntityType::Insert(insert.clone());
|
||||
let has_book_color = has_resolved_book_color(document, &entity);
|
||||
let linetype = &insert.common.linetype;
|
||||
Self {
|
||||
own: BlockStyle::for_entity(document, &entity),
|
||||
own: BlockStyle::for_entity(document, &entity, viewport),
|
||||
color_byblock: !has_book_color && insert.common.color == Color::ByBlock,
|
||||
color_bylayer: !has_book_color && insert.common.color == Color::ByLayer,
|
||||
linetype_byblock: linetype.eq_ignore_ascii_case("byblock"),
|
||||
|
|
@ -136,10 +136,14 @@ impl InsertStyleSpec {
|
|||
}
|
||||
|
||||
impl BlockStyle {
|
||||
pub fn for_entity(document: &CadDocument, entity: &EntityType) -> Self {
|
||||
pub fn for_entity(
|
||||
document: &CadDocument,
|
||||
entity: &EntityType,
|
||||
viewport: Option<Handle>,
|
||||
) -> Self {
|
||||
Self {
|
||||
insert: render_style_for(document, entity),
|
||||
layer0: layer_render_style(document, &entity.common().layer),
|
||||
insert: render_style_for_viewport(document, entity, viewport),
|
||||
layer0: layer_render_style_viewport(document, &entity.common().layer, viewport),
|
||||
layer0_aci: layer_aci(document, &entity.common().layer),
|
||||
}
|
||||
}
|
||||
|
|
@ -148,27 +152,29 @@ impl BlockStyle {
|
|||
document: &CadDocument,
|
||||
insert: &Insert,
|
||||
parent: Self,
|
||||
viewport: Option<Handle>,
|
||||
) -> Self {
|
||||
InsertStyleSpec::new(document, insert).resolve(parent)
|
||||
InsertStyleSpec::new(document, insert, viewport).resolve(parent)
|
||||
}
|
||||
|
||||
pub fn for_owned(
|
||||
document: &CadDocument,
|
||||
entity: &EntityType,
|
||||
parent: Option<Self>,
|
||||
viewport: Option<Handle>,
|
||||
) -> Self {
|
||||
let on_layer0 = is_effective_layer_zero(&entity.common().layer);
|
||||
let insert = parent
|
||||
.map(|style| style.resolve(document, entity))
|
||||
.unwrap_or_else(|| render_style_for(document, entity));
|
||||
.map(|style| style.resolve(document, entity, viewport))
|
||||
.unwrap_or_else(|| render_style_for_viewport(document, entity, viewport));
|
||||
Self {
|
||||
insert,
|
||||
layer0: if on_layer0 {
|
||||
parent
|
||||
.map(|style| style.layer0)
|
||||
.unwrap_or_else(|| layer_render_style(document, &entity.common().layer))
|
||||
.unwrap_or_else(|| layer_render_style_viewport(document, &entity.common().layer, viewport))
|
||||
} else {
|
||||
layer_render_style(document, &entity.common().layer)
|
||||
layer_render_style_viewport(document, &entity.common().layer, viewport)
|
||||
},
|
||||
layer0_aci: if on_layer0 {
|
||||
parent
|
||||
|
|
@ -180,8 +186,13 @@ impl BlockStyle {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn resolve(self, document: &CadDocument, entity: &EntityType) -> ResolvedStyle {
|
||||
let mut resolved = render_style_for_block_sub(
|
||||
pub fn resolve(
|
||||
self,
|
||||
document: &CadDocument,
|
||||
entity: &EntityType,
|
||||
viewport: Option<Handle>,
|
||||
) -> ResolvedStyle {
|
||||
let mut resolved = render_style_for_block_sub_viewport(
|
||||
document,
|
||||
entity,
|
||||
self.insert.0,
|
||||
|
|
@ -189,6 +200,7 @@ impl BlockStyle {
|
|||
self.insert.2,
|
||||
self.insert.3,
|
||||
self.layer0,
|
||||
viewport,
|
||||
);
|
||||
let common = entity.common();
|
||||
let has_book_color = has_resolved_book_color(document, entity);
|
||||
|
|
@ -228,10 +240,11 @@ pub struct RenderContext {
|
|||
pub depth_base: f32,
|
||||
pub depth_scale: f32,
|
||||
pub nesting_depth: usize,
|
||||
pub viewport: Option<Handle>,
|
||||
}
|
||||
|
||||
impl RenderContext {
|
||||
fn direct(depth_base: f32) -> Self {
|
||||
fn direct(depth_base: f32, viewport: Option<Handle>) -> Self {
|
||||
Self {
|
||||
transform: Transform::identity(),
|
||||
root_handle: Handle::NULL,
|
||||
|
|
@ -242,6 +255,7 @@ impl RenderContext {
|
|||
depth_base,
|
||||
depth_scale: 1.0,
|
||||
nesting_depth: 0,
|
||||
viewport,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -251,8 +265,8 @@ impl RenderContext {
|
|||
|
||||
pub fn style_for(&self, document: &CadDocument, entity: &EntityType) -> ResolvedStyle {
|
||||
self.block_style
|
||||
.map(|style| style.resolve(document, entity))
|
||||
.unwrap_or_else(|| render_style_for(document, entity))
|
||||
.map(|style| style.resolve(document, entity, self.viewport))
|
||||
.unwrap_or_else(|| render_style_for_viewport(document, entity, self.viewport))
|
||||
}
|
||||
|
||||
pub fn draw_depth(&self, handle: Handle, depths: &FxHashMap<u64, [f32; 2]>) -> f32 {
|
||||
|
|
@ -276,6 +290,7 @@ pub struct RenderSceneGraph<'a> {
|
|||
annotation_scale_handle: Option<Handle>,
|
||||
all_visible: bool,
|
||||
depths: &'a FxHashMap<u64, [f32; 2]>,
|
||||
viewport: Option<Handle>,
|
||||
}
|
||||
|
||||
impl<'a> RenderSceneGraph<'a> {
|
||||
|
|
@ -292,9 +307,15 @@ impl<'a> RenderSceneGraph<'a> {
|
|||
annotation_scale_handle,
|
||||
all_visible,
|
||||
depths,
|
||||
viewport: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_viewport(mut self, viewport: Option<Handle>) -> Self {
|
||||
self.viewport = viewport.filter(|handle| handle.is_valid());
|
||||
self
|
||||
}
|
||||
|
||||
/// Walk direct root entities and every referenced block subtree. `visible`
|
||||
/// can add session-only rules such as isolate/preview hiding; returning
|
||||
/// false for an Insert removes its whole subtree.
|
||||
|
|
@ -304,6 +325,10 @@ impl<'a> RenderSceneGraph<'a> {
|
|||
F: FnMut(&EntityType, &RenderContext),
|
||||
{
|
||||
let block = root.content_block();
|
||||
let viewport = match root {
|
||||
SceneRoot::Viewport { viewport, .. } => Some(viewport),
|
||||
SceneRoot::Block(_) => self.viewport,
|
||||
};
|
||||
let Some(record) = self
|
||||
.document
|
||||
.block_records
|
||||
|
|
@ -326,7 +351,7 @@ impl<'a> RenderSceneGraph<'a> {
|
|||
.depths
|
||||
.get(&handle.value())
|
||||
.map_or(0.0, |depth| depth[0]);
|
||||
let context = RenderContext::direct(direct_depth);
|
||||
let context = RenderContext::direct(direct_depth, viewport);
|
||||
if !self.document_visible(entity) || !visible(entity, &context) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -358,7 +383,7 @@ impl<'a> RenderSceneGraph<'a> {
|
|||
.depths
|
||||
.get(&root_handle.value())
|
||||
.map_or(0.0, |depth| depth[0]);
|
||||
let context = RenderContext::direct(depth_base);
|
||||
let context = RenderContext::direct(depth_base, self.viewport);
|
||||
if self.document_visible(&entity) && visible(&entity, &context) {
|
||||
self.walk_insert_instances(&root_insert, &context, &mut visible, &mut leaf);
|
||||
}
|
||||
|
|
@ -378,8 +403,8 @@ impl<'a> RenderSceneGraph<'a> {
|
|||
let insert_entity = EntityType::Insert(insert.clone());
|
||||
let block_style = parent
|
||||
.block_style
|
||||
.map(|style| BlockStyle::for_nested(self.document, insert, style))
|
||||
.unwrap_or_else(|| BlockStyle::for_entity(self.document, &insert_entity));
|
||||
.map(|style| BlockStyle::for_nested(self.document, insert, style, self.viewport))
|
||||
.unwrap_or_else(|| BlockStyle::for_entity(self.document, &insert_entity, self.viewport));
|
||||
let root_handle = if parent.root_handle.is_null() {
|
||||
insert.common.handle
|
||||
} else {
|
||||
|
|
@ -525,6 +550,7 @@ impl<'a> RenderSceneGraph<'a> {
|
|||
self.document,
|
||||
entity,
|
||||
context.block_style,
|
||||
self.viewport,
|
||||
));
|
||||
owned.nesting_depth += 1;
|
||||
stack.push(block_name.to_string());
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
139
src/shaders/background.wgsl
Normal file
139
src/shaders/background.wgsl
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
struct Uniforms {
|
||||
viewport_size: vec2<f32>,
|
||||
world_per_pixel: f32,
|
||||
lwdisplay_enable: f32,
|
||||
flat_shade: f32,
|
||||
transparency_enable: f32,
|
||||
linetype_scale: f32,
|
||||
_pad: f32,
|
||||
view_rot: mat4x4<f32>,
|
||||
eye_high: vec3<f32>,
|
||||
_pad_eh: f32,
|
||||
eye_low: vec3<f32>,
|
||||
_pad_el: f32,
|
||||
light_position_type: array<vec4<f32>, 4>,
|
||||
light_direction_intensity: array<vec4<f32>, 4>,
|
||||
light_color_hotspot: array<vec4<f32>, 4>,
|
||||
light_attenuation: array<vec4<f32>, 4>,
|
||||
lighting: vec4<f32>,
|
||||
visual_style: vec4<f32>,
|
||||
visual_style_color: vec4<f32>,
|
||||
viewport_background: vec4<f32>,
|
||||
view_tone: vec4<f32>,
|
||||
background_top: vec4<f32>,
|
||||
background_middle: vec4<f32>,
|
||||
background_bottom: vec4<f32>,
|
||||
background_aux0: vec4<f32>,
|
||||
background_aux1: vec4<f32>,
|
||||
background_params: vec4<f32>,
|
||||
background_image_params: vec4<f32>,
|
||||
background_image_transform: vec4<f32>,
|
||||
environment_params: vec4<f32>,
|
||||
environment_view: mat4x4<f32>,
|
||||
shadow_view_proj: mat4x4<f32>,
|
||||
shadow_params: vec4<f32>,
|
||||
fog_color: vec4<f32>,
|
||||
fog_params: vec4<f32>,
|
||||
fog_distances: vec4<f32>,
|
||||
light_web_profile_a: array<vec4<f32>, 4>,
|
||||
light_web_profile_b: array<vec4<f32>, 4>,
|
||||
light_web_rotation: array<vec4<f32>, 4>,
|
||||
};
|
||||
|
||||
@group(0) @binding(0) var<uniform> u: Uniforms;
|
||||
@group(0) @binding(1) var background_texture: texture_2d<f32>;
|
||||
@group(0) @binding(2) var background_sampler: sampler;
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@location(0) uv: vec2<f32>,
|
||||
@location(1) ndc: vec2<f32>,
|
||||
};
|
||||
|
||||
var<private> POSITIONS: array<vec2<f32>, 3> = array<vec2<f32>, 3>(
|
||||
vec2<f32>(-1.0, -1.0),
|
||||
vec2<f32>( 3.0, -1.0),
|
||||
vec2<f32>(-1.0, 3.0),
|
||||
);
|
||||
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) index: u32) -> VertexOutput {
|
||||
let position = POSITIONS[index];
|
||||
var out: VertexOutput;
|
||||
out.position = vec4<f32>(position, 0.999, 1.0);
|
||||
out.uv = vec2<f32>(position.x * 0.5 + 0.5, 0.5 - position.y * 0.5);
|
||||
out.ndc = position;
|
||||
return out;
|
||||
}
|
||||
|
||||
fn equirect_uv(ndc: vec2<f32>, rotation: f32) -> vec2<f32> {
|
||||
let direction = normalize((u.environment_view * vec4<f32>(ndc, -1.0, 0.0)).xyz);
|
||||
let longitude = atan2(direction.x, -direction.z) + rotation;
|
||||
let latitude = asin(clamp(direction.y, -1.0, 1.0));
|
||||
return vec2<f32>(fract(longitude / (2.0 * 3.14159265359) + 0.5),
|
||||
0.5 - latitude / 3.14159265359);
|
||||
}
|
||||
|
||||
fn image_uv(source_uv: vec2<f32>) -> vec2<f32> {
|
||||
var uv = (source_uv - u.background_image_transform.xy)
|
||||
/ max(abs(u.background_image_transform.zw), vec2<f32>(1e-6));
|
||||
if u.background_image_params.y > 0.5 {
|
||||
let image_aspect = max(u.background_image_params.w, 1e-6);
|
||||
let viewport_aspect = max(u.viewport_size.x / max(u.viewport_size.y, 1.0), 1e-6);
|
||||
if image_aspect > viewport_aspect {
|
||||
let scale = viewport_aspect / image_aspect;
|
||||
uv.y = (uv.y - 0.5) / scale + 0.5;
|
||||
} else {
|
||||
let scale = image_aspect / viewport_aspect;
|
||||
uv.x = (uv.x - 0.5) / scale + 0.5;
|
||||
}
|
||||
}
|
||||
if u.background_image_params.z > 0.5 {
|
||||
return fract(uv);
|
||||
}
|
||||
return clamp(uv, vec2<f32>(0.0), vec2<f32>(1.0));
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
let mode = u32(max(u.background_params.x, 0.0) + 0.5);
|
||||
var color: vec3<f32>;
|
||||
if mode == 1u {
|
||||
color = u.viewport_background.rgb;
|
||||
} else if mode == 2u {
|
||||
let angle = u.background_params.w;
|
||||
let axis = vec2<f32>(-sin(angle), cos(angle));
|
||||
let coordinate = dot(in.uv - vec2<f32>(0.5), axis) + 0.5;
|
||||
let horizon = clamp(u.background_params.y, 0.0, 1.0);
|
||||
let half_height = max(abs(u.background_params.z) * 0.5, 1e-3);
|
||||
let lower = smoothstep(horizon - half_height, horizon, coordinate);
|
||||
let upper = smoothstep(horizon, horizon + half_height, coordinate);
|
||||
let low_color = mix(u.background_bottom.rgb, u.background_middle.rgb, lower);
|
||||
color = mix(low_color, u.background_top.rgb, upper);
|
||||
} else if mode == 3u {
|
||||
let horizon = 0.5;
|
||||
if in.uv.y <= horizon {
|
||||
let sky = smoothstep(0.0, horizon, in.uv.y);
|
||||
color = mix(u.background_top.rgb, u.background_middle.rgb, sky);
|
||||
} else {
|
||||
let ground = smoothstep(horizon, 1.0, in.uv.y);
|
||||
let underground = mix(u.background_bottom.rgb, u.background_aux0.rgb, ground);
|
||||
let distance_tint = mix(u.background_aux1.rgb, u.viewport_background.rgb, ground);
|
||||
color = mix(underground, distance_tint, 0.35);
|
||||
}
|
||||
} else if mode == 4u {
|
||||
color = textureSample(background_texture, background_sampler, image_uv(in.uv)).rgb;
|
||||
} else if mode == 5u {
|
||||
color = textureSample(background_texture, background_sampler,
|
||||
equirect_uv(in.ndc, u.background_params.w)).rgb;
|
||||
} else if mode == 6u {
|
||||
let t = smoothstep(0.0, 1.0, in.uv.y);
|
||||
color = mix(u.background_bottom.rgb, u.background_top.rgb, t);
|
||||
} else {
|
||||
color = u.viewport_background.rgb;
|
||||
}
|
||||
if u.fog_params.x > 0.5 && u.fog_params.y > 0.5 {
|
||||
color = mix(color, u.fog_color.rgb, clamp(u.fog_params.w, 0.0, 1.0));
|
||||
}
|
||||
return vec4<f32>(color, 1.0);
|
||||
}
|
||||
|
|
@ -2,8 +2,9 @@
|
|||
//
|
||||
// Vertex layout: position [f32;3], normal [f32;3], color [f32;4] (40 bytes)
|
||||
//
|
||||
// Lighting: simple half-Lambert with a fixed directional light. Two
|
||||
// shading paths share this shader, picked per-frame via `u.flat_shade`:
|
||||
// Lighting: viewport default lights or drawing light entities supplied by the
|
||||
// frame uniform. Two shading paths share this shader, picked per-frame via
|
||||
// `u.flat_shade`:
|
||||
// - 0.0 → per-vertex normals interpolated to the fragment (Gouraud).
|
||||
// - 1.0 → per-triangle face normal from screen-space derivatives
|
||||
// `cross(dpdx(pos), dpdy(pos))`, so each triangle reads as a single
|
||||
|
|
@ -27,10 +28,36 @@ struct Uniforms {
|
|||
light_color_hotspot: array<vec4<f32>, 4>,
|
||||
light_attenuation: array<vec4<f32>, 4>,
|
||||
lighting: vec4<f32>,
|
||||
visual_style: vec4<f32>,
|
||||
visual_style_color: vec4<f32>,
|
||||
viewport_background: vec4<f32>,
|
||||
view_tone: vec4<f32>,
|
||||
background_top: vec4<f32>,
|
||||
background_middle: vec4<f32>,
|
||||
background_bottom: vec4<f32>,
|
||||
background_aux0: vec4<f32>,
|
||||
background_aux1: vec4<f32>,
|
||||
background_params: vec4<f32>,
|
||||
background_image_params: vec4<f32>,
|
||||
background_image_transform: vec4<f32>,
|
||||
environment_params: vec4<f32>,
|
||||
environment_view: mat4x4<f32>,
|
||||
shadow_view_proj: mat4x4<f32>,
|
||||
shadow_params: vec4<f32>,
|
||||
fog_color: vec4<f32>,
|
||||
fog_params: vec4<f32>,
|
||||
fog_distances: vec4<f32>,
|
||||
light_web_profile_a: array<vec4<f32>, 4>,
|
||||
light_web_profile_b: array<vec4<f32>, 4>,
|
||||
light_web_rotation: array<vec4<f32>, 4>,
|
||||
};
|
||||
|
||||
@group(0) @binding(0)
|
||||
var<uniform> u: Uniforms;
|
||||
@group(0) @binding(3) var environment_texture: texture_2d<f32>;
|
||||
@group(0) @binding(4) var environment_sampler: sampler;
|
||||
@group(0) @binding(5) var shadow_texture: texture_depth_2d;
|
||||
@group(0) @binding(6) var shadow_sampler: sampler_comparison;
|
||||
|
||||
struct MaterialMaps {
|
||||
blends0: vec4<f32>,
|
||||
|
|
@ -202,6 +229,63 @@ fn map_weight(uv: vec2<f32>, tiling: u32) -> f32 {
|
|||
return 1.0;
|
||||
}
|
||||
|
||||
fn environment_uv(direction: vec3<f32>) -> vec2<f32> {
|
||||
let d = normalize(direction);
|
||||
let longitude = atan2(d.x, -d.z) + u.environment_params.y;
|
||||
let latitude = asin(clamp(d.y, -1.0, 1.0));
|
||||
return vec2<f32>(
|
||||
fract(longitude / (2.0 * 3.14159265359) + 0.5),
|
||||
0.5 - latitude / 3.14159265359,
|
||||
);
|
||||
}
|
||||
|
||||
fn shadow_visibility(position: vec3<f32>) -> f32 {
|
||||
if u.shadow_params.x < 0.5 {
|
||||
return 1.0;
|
||||
}
|
||||
let clip = u.shadow_view_proj * vec4<f32>(position, 1.0);
|
||||
let ndc = clip.xyz / max(abs(clip.w), 1e-6);
|
||||
if ndc.z <= 0.0 || ndc.z >= 1.0 || any(abs(ndc.xy) > vec2<f32>(1.0)) {
|
||||
return 1.0;
|
||||
}
|
||||
let uv = vec2<f32>(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
||||
let dimensions = vec2<f32>(textureDimensions(shadow_texture));
|
||||
let texel = 1.0 / max(dimensions, vec2<f32>(1.0));
|
||||
let radius = i32(clamp(ceil(u.shadow_params.z * 2.0), 0.0, 2.0));
|
||||
var visibility = 0.0;
|
||||
var samples = 0.0;
|
||||
for (var y = -2; y <= 2; y = y + 1) {
|
||||
for (var x = -2; x <= 2; x = x + 1) {
|
||||
if abs(x) <= radius && abs(y) <= radius {
|
||||
visibility += textureSampleCompare(
|
||||
shadow_texture,
|
||||
shadow_sampler,
|
||||
uv + vec2<f32>(f32(x), f32(y)) * texel,
|
||||
ndc.z - u.shadow_params.y,
|
||||
);
|
||||
samples += 1.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return visibility / max(samples, 1.0);
|
||||
}
|
||||
|
||||
fn photometric_web(index: u32, cosine: f32) -> f32 {
|
||||
if u.light_web_rotation[index].w < 0.5 {
|
||||
return 1.0;
|
||||
}
|
||||
let profile_a = u.light_web_profile_a[index];
|
||||
let profile_b = u.light_web_profile_b[index];
|
||||
let angle_index = clamp(acos(clamp(cosine, -1.0, 1.0)) / 3.14159265359 * 7.0, 0.0, 7.0);
|
||||
let lower = u32(floor(angle_index));
|
||||
let upper = min(lower + 1u, 7u);
|
||||
let value_at = array<f32, 8>(
|
||||
profile_a.x, profile_a.y, profile_a.z, profile_a.w,
|
||||
profile_b.x, profile_b.y, profile_b.z, profile_b.w,
|
||||
);
|
||||
return mix(value_at[lower], value_at[upper], fract(angle_index));
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
|
||||
let two_sided = maps.render_modes.x != 0u;
|
||||
|
|
@ -252,79 +336,6 @@ fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
|
|||
);
|
||||
}
|
||||
|
||||
// Native AcDbLight / Sun lighting. When a drawing has no active lights,
|
||||
// retain the neutral three-point editor rig so ordinary models remain
|
||||
// readable.
|
||||
let l0 = normalize(vec3<f32>( 0.5, 0.8, 0.6)); // key (upper front)
|
||||
let l1 = normalize(vec3<f32>(-0.7, 0.3, 0.4)); // fill (left)
|
||||
let l2 = normalize(vec3<f32>( 0.2, -0.6, -0.8)); // back/under
|
||||
var direct_light = vec3<f32>(
|
||||
0.45 * select(max(dot(n, l0), 0.0), abs(dot(n, l0)), two_sided)
|
||||
+ 0.30 * select(max(dot(n, l1), 0.0), abs(dot(n, l1)), two_sided)
|
||||
+ 0.25 * select(max(dot(n, l2), 0.0), abs(dot(n, l2)), two_sided)
|
||||
);
|
||||
var key_light = l0;
|
||||
if (u.lighting.x > 0.5) {
|
||||
direct_light = vec3<f32>(0.0);
|
||||
let count = min(u32(u.lighting.x), 4u);
|
||||
for (var index = 0u; index < count; index = index + 1u) {
|
||||
let position_type = u.light_position_type[index];
|
||||
let direction_intensity = u.light_direction_intensity[index];
|
||||
let color_hotspot = u.light_color_hotspot[index];
|
||||
let attenuation_data = u.light_attenuation[index];
|
||||
var light_vector = -normalize(direction_intensity.xyz);
|
||||
var attenuation = 1.0;
|
||||
if (position_type.w > 1.5) {
|
||||
let delta = position_type.xyz - in.world_pos;
|
||||
let distance = max(length(delta), 1e-5);
|
||||
light_vector = delta / distance;
|
||||
if (attenuation_data.x > 1.5) {
|
||||
attenuation /= max(distance * distance, 1.0);
|
||||
} else if (attenuation_data.x > 0.5) {
|
||||
attenuation /= max(distance, 1.0);
|
||||
}
|
||||
if (attenuation_data.z > attenuation_data.y) {
|
||||
attenuation *= 1.0 - smoothstep(
|
||||
attenuation_data.y,
|
||||
attenuation_data.z,
|
||||
distance,
|
||||
);
|
||||
}
|
||||
if (position_type.w > 2.5) {
|
||||
let source_to_fragment = -light_vector;
|
||||
let cone = dot(
|
||||
source_to_fragment,
|
||||
normalize(direction_intensity.xyz),
|
||||
);
|
||||
attenuation *= smoothstep(
|
||||
attenuation_data.w,
|
||||
color_hotspot.w,
|
||||
cone,
|
||||
);
|
||||
}
|
||||
}
|
||||
let strength = max(direction_intensity.w, 0.0) * attenuation;
|
||||
let response = select(
|
||||
max(dot(n, light_vector), 0.0),
|
||||
abs(dot(n, light_vector)),
|
||||
two_sided,
|
||||
);
|
||||
direct_light += color_hotspot.rgb * response * strength;
|
||||
if (index == 0u) {
|
||||
key_light = light_vector;
|
||||
}
|
||||
}
|
||||
}
|
||||
let view = normalize(in.eye_vec);
|
||||
let half_vec = normalize(key_light + view);
|
||||
let gloss_exp = mix(2.0, 128.0, clamp(in.material.x, 0.0, 1.0));
|
||||
let fresnel0 = pow(
|
||||
(max(in.specular.w, 1.0) - 1.0) / (max(in.specular.w, 1.0) + 1.0),
|
||||
2.0,
|
||||
);
|
||||
let fresnel = fresnel0
|
||||
+ (1.0 - fresnel0) * pow(1.0 - abs(dot(n, view)), 5.0);
|
||||
let specular_strength = clamp(1.0 + in.material.y + fresnel, 0.0, 1.5);
|
||||
var albedo = in.color.rgb;
|
||||
if (maps.present0.x != 0u) {
|
||||
let texel = textureSample(diffuse_map, diffuse_sampler, in.uv_diffuse).rgb;
|
||||
|
|
@ -335,6 +346,23 @@ fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
|
|||
);
|
||||
albedo = mix(albedo, texel, blend);
|
||||
}
|
||||
|
||||
// Viewport face-colour modes apply to unmaterialed faces. An attached
|
||||
// material remains authoritative, including its own diffuse texture.
|
||||
if (maps.source_state.z == 0u) {
|
||||
let mode = u32(max(u.visual_style.x, 0.0) + 0.5);
|
||||
if (mode == 2u) {
|
||||
albedo = u.viewport_background.rgb;
|
||||
} else if (mode == 3u || mode == 4u) {
|
||||
albedo = u.visual_style_color.rgb;
|
||||
} else if (mode == 5u) {
|
||||
albedo = mix(albedo, u.visual_style_color.rgb, 0.5);
|
||||
} else if (mode == 6u) {
|
||||
let luminance = dot(albedo, vec3<f32>(0.2126, 0.7152, 0.0722));
|
||||
albedo = mix(albedo, vec3<f32>(luminance), 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
var specular_color = select(in.specular.rgb, albedo, in.flags.x == 1u);
|
||||
if (maps.present0.y != 0u) {
|
||||
let texel = textureSample(specular_map, specular_sampler, in.uv_specular).rgb;
|
||||
|
|
@ -345,17 +373,124 @@ fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
|
|||
);
|
||||
specular_color = mix(specular_color, texel, blend);
|
||||
}
|
||||
let half_response = select(
|
||||
max(dot(n, half_vec), 0.0),
|
||||
abs(dot(n, half_vec)),
|
||||
two_sided,
|
||||
let view = normalize(in.eye_vec);
|
||||
let gloss_exp = mix(2.0, 128.0, clamp(in.material.x, 0.0, 1.0));
|
||||
let fresnel0 = pow(
|
||||
(max(in.specular.w, 1.0) - 1.0) / (max(in.specular.w, 1.0) + 1.0),
|
||||
2.0,
|
||||
);
|
||||
let specular = specular_color
|
||||
* pow(half_response, gloss_exp)
|
||||
* specular_strength
|
||||
* max(in.advanced.z, 0.0);
|
||||
let ambient_light = albedo * clamp(in.ambient.rgb, vec3<f32>(0.0), vec3<f32>(1.0));
|
||||
var lit = ambient_light + albedo * clamp(direct_light, vec3<f32>(0.0), vec3<f32>(2.0)) + specular;
|
||||
let fresnel = fresnel0
|
||||
+ (1.0 - fresnel0) * pow(1.0 - abs(dot(n, view)), 5.0);
|
||||
let specular_strength = clamp(1.0 + in.material.y + fresnel, 0.0, 1.5);
|
||||
let viewport_highlight = select(
|
||||
1.0,
|
||||
max(u.visual_style.z, 0.0),
|
||||
maps.source_state.z == 0u && u.visual_style.z >= 0.0,
|
||||
);
|
||||
|
||||
// The CPU resolves the viewport's default-light policy before upload, so
|
||||
// this loop handles both generated distant lights and drawing light data.
|
||||
// Diffuse and specular are accumulated from every visible light using the
|
||||
// same colour, intensity, cone and attenuation terms.
|
||||
var direct_light = vec3<f32>(0.0);
|
||||
var direct_specular = vec3<f32>(0.0);
|
||||
let shadow = shadow_visibility(in.world_pos);
|
||||
let shadow_light_index = u32(max(u.shadow_params.w, 0.0) + 0.5);
|
||||
let count = min(u32(u.lighting.x), 4u);
|
||||
for (var index = 0u; index < count; index = index + 1u) {
|
||||
let position_type = u.light_position_type[index];
|
||||
let direction_intensity = u.light_direction_intensity[index];
|
||||
let color_hotspot = u.light_color_hotspot[index];
|
||||
let attenuation_data = u.light_attenuation[index];
|
||||
var light_vector = -normalize(direction_intensity.xyz);
|
||||
var attenuation = 1.0;
|
||||
if (position_type.w > 1.5) {
|
||||
let delta = position_type.xyz - in.world_pos;
|
||||
let distance = max(length(delta), 1e-5);
|
||||
light_vector = delta / distance;
|
||||
if (attenuation_data.x > 1.5) {
|
||||
attenuation /= max(distance * distance, 1.0);
|
||||
} else if (attenuation_data.x > 0.5) {
|
||||
attenuation /= max(distance, 1.0);
|
||||
}
|
||||
if (attenuation_data.z > attenuation_data.y) {
|
||||
attenuation *= 1.0 - smoothstep(
|
||||
attenuation_data.y,
|
||||
attenuation_data.z,
|
||||
distance,
|
||||
);
|
||||
}
|
||||
if (position_type.w > 2.5) {
|
||||
let source_to_fragment = -light_vector;
|
||||
let cone = dot(
|
||||
source_to_fragment,
|
||||
normalize(direction_intensity.xyz),
|
||||
);
|
||||
attenuation *= smoothstep(
|
||||
attenuation_data.w,
|
||||
color_hotspot.w,
|
||||
cone,
|
||||
);
|
||||
}
|
||||
attenuation *= photometric_web(
|
||||
index,
|
||||
dot(-light_vector, normalize(direction_intensity.xyz)),
|
||||
);
|
||||
}
|
||||
let visibility = select(1.0, shadow, index == shadow_light_index);
|
||||
let strength = max(direction_intensity.w, 0.0) * attenuation * visibility;
|
||||
let response = select(
|
||||
max(dot(n, light_vector), 0.0),
|
||||
abs(dot(n, light_vector)),
|
||||
two_sided,
|
||||
);
|
||||
direct_light += color_hotspot.rgb * response * strength;
|
||||
let half_vec = normalize(light_vector + view);
|
||||
let half_response = select(
|
||||
max(dot(n, half_vec), 0.0),
|
||||
abs(dot(n, half_vec)),
|
||||
two_sided,
|
||||
);
|
||||
direct_specular += color_hotspot.rgb
|
||||
* specular_color
|
||||
* pow(half_response, gloss_exp)
|
||||
* specular_strength
|
||||
* viewport_highlight
|
||||
* max(in.advanced.z, 0.0)
|
||||
* strength;
|
||||
}
|
||||
let ambient_light = clamp(in.ambient.rgb, vec3<f32>(0.0), vec3<f32>(1.0))
|
||||
* clamp(u.lighting.yzw, vec3<f32>(0.0), vec3<f32>(1.0));
|
||||
var lit = ambient_light
|
||||
+ albedo * clamp(direct_light, vec3<f32>(0.0), vec3<f32>(2.0))
|
||||
+ direct_specular;
|
||||
if (u.environment_params.x > 0.5) {
|
||||
let diffuse_environment = textureSample(
|
||||
environment_texture,
|
||||
environment_sampler,
|
||||
environment_uv(n),
|
||||
).rgb;
|
||||
let reflection_direction = reflect(-view, n);
|
||||
let reflected_environment = textureSample(
|
||||
environment_texture,
|
||||
environment_sampler,
|
||||
environment_uv(reflection_direction),
|
||||
).rgb;
|
||||
let indirect_enabled = maps.source_state.z == 0u
|
||||
|| maps.render_modes.z >= 2u
|
||||
|| maps.render_modes.w != 0u;
|
||||
let color_bleed = select(0.0, max(maps.indirect.x, 0.0), indirect_enabled);
|
||||
lit += albedo
|
||||
* diffuse_environment
|
||||
* max(u.environment_params.z, 0.0)
|
||||
* color_bleed;
|
||||
lit += reflected_environment
|
||||
* specular_color
|
||||
* specular_strength
|
||||
* viewport_highlight
|
||||
* max(in.advanced.z, 0.0)
|
||||
* max(u.environment_params.w, 0.0);
|
||||
}
|
||||
if (maps.present0.z != 0u) {
|
||||
let reflected = textureSample(
|
||||
reflection_map,
|
||||
|
|
@ -398,6 +533,21 @@ fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
|
|||
if (in.flags.w == 1u) {
|
||||
rgb = lit + albedo * max(in.material.w, 0.0);
|
||||
}
|
||||
let contrast = 1.0 + clamp(u.view_tone.y, -1.0, 1.0);
|
||||
rgb = (rgb - vec3<f32>(0.5)) * contrast + vec3<f32>(0.5);
|
||||
rgb += vec3<f32>(clamp(u.view_tone.x, -1.0, 1.0) * 0.5);
|
||||
rgb *= max(1.0 + u.visual_style.w, 0.0);
|
||||
if (u.fog_params.x > 0.5) {
|
||||
let fog_range = max(u.fog_distances.y - u.fog_distances.x, 1e-6);
|
||||
let fog_distance = length(in.world_pos);
|
||||
let fog_position = clamp(
|
||||
(fog_distance - u.fog_distances.x) / fog_range,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
let fog_density = mix(u.fog_params.z, u.fog_params.w, fog_position);
|
||||
rgb = mix(rgb, u.fog_color.rgb, clamp(fog_density, 0.0, 1.0));
|
||||
}
|
||||
var material_alpha = in.color.a;
|
||||
if (maps.present0.w != 0u) {
|
||||
let opacity = textureSample(opacity_map, opacity_sampler, in.uv_opacity).r;
|
||||
|
|
@ -409,6 +559,7 @@ fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
|
|||
material_alpha *= mix(1.0, opacity, blend);
|
||||
}
|
||||
material_alpha *= 1.0 - clamp(in.ambient.a * max(in.advanced.w, 0.0), 0.0, 0.95);
|
||||
material_alpha *= clamp(u.visual_style.y, 0.0, 1.0);
|
||||
let alpha = select(1.0, material_alpha, u.transparency_enable > 0.5);
|
||||
return vec4<f32>(rgb, alpha);
|
||||
}
|
||||
|
|
|
|||
79
src/shaders/shadow.wgsl
Normal file
79
src/shaders/shadow.wgsl
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
struct Uniforms {
|
||||
viewport_size: vec2<f32>,
|
||||
world_per_pixel: f32,
|
||||
lwdisplay_enable: f32,
|
||||
flat_shade: f32,
|
||||
transparency_enable: f32,
|
||||
linetype_scale: f32,
|
||||
_pad: f32,
|
||||
view_rot: mat4x4<f32>,
|
||||
eye_high: vec3<f32>,
|
||||
_pad_eh: f32,
|
||||
eye_low: vec3<f32>,
|
||||
_pad_el: f32,
|
||||
light_position_type: array<vec4<f32>, 4>,
|
||||
light_direction_intensity: array<vec4<f32>, 4>,
|
||||
light_color_hotspot: array<vec4<f32>, 4>,
|
||||
light_attenuation: array<vec4<f32>, 4>,
|
||||
lighting: vec4<f32>,
|
||||
visual_style: vec4<f32>,
|
||||
visual_style_color: vec4<f32>,
|
||||
viewport_background: vec4<f32>,
|
||||
view_tone: vec4<f32>,
|
||||
background_top: vec4<f32>,
|
||||
background_middle: vec4<f32>,
|
||||
background_bottom: vec4<f32>,
|
||||
background_aux0: vec4<f32>,
|
||||
background_aux1: vec4<f32>,
|
||||
background_params: vec4<f32>,
|
||||
background_image_params: vec4<f32>,
|
||||
background_image_transform: vec4<f32>,
|
||||
environment_params: vec4<f32>,
|
||||
environment_view: mat4x4<f32>,
|
||||
shadow_view_proj: mat4x4<f32>,
|
||||
shadow_params: vec4<f32>,
|
||||
fog_color: vec4<f32>,
|
||||
fog_params: vec4<f32>,
|
||||
fog_distances: vec4<f32>,
|
||||
light_web_profile_a: array<vec4<f32>, 4>,
|
||||
light_web_profile_b: array<vec4<f32>, 4>,
|
||||
light_web_rotation: array<vec4<f32>, 4>,
|
||||
};
|
||||
|
||||
struct MeshInstance {
|
||||
model_row_0: vec4<f32>,
|
||||
model_row_1: vec4<f32>,
|
||||
model_row_2: vec4<f32>,
|
||||
translation_low: vec4<f32>,
|
||||
normal_row_0: vec4<f32>,
|
||||
normal_row_1: vec4<f32>,
|
||||
normal_row_2: vec4<f32>,
|
||||
};
|
||||
|
||||
@group(0) @binding(0) var<uniform> u: Uniforms;
|
||||
@group(1) @binding(15) var<storage, read> mesh_instances: array<MeshInstance>;
|
||||
|
||||
struct VertexIn {
|
||||
@location(0) position: vec3<f32>,
|
||||
@location(3) position_low: vec3<f32>,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(
|
||||
vertex: VertexIn,
|
||||
@builtin(instance_index) instance_index: u32,
|
||||
) -> @builtin(position) vec4<f32> {
|
||||
let instance = mesh_instances[instance_index];
|
||||
let world_high = vec3<f32>(
|
||||
dot(instance.model_row_0.xyz, vertex.position) + instance.model_row_0.w,
|
||||
dot(instance.model_row_1.xyz, vertex.position) + instance.model_row_1.w,
|
||||
dot(instance.model_row_2.xyz, vertex.position) + instance.model_row_2.w,
|
||||
);
|
||||
let world_low = vec3<f32>(
|
||||
dot(instance.model_row_0.xyz, vertex.position_low),
|
||||
dot(instance.model_row_1.xyz, vertex.position_low),
|
||||
dot(instance.model_row_2.xyz, vertex.position_low),
|
||||
) + instance.translation_low.xyz;
|
||||
let relative = (world_high - u.eye_high) + (world_low - u.eye_low);
|
||||
return u.shadow_view_proj * vec4<f32>(relative, 1.0);
|
||||
}
|
||||
Loading…
Reference in a new issue