Point clouds: lighting, 2D visual style, real-time density

Points carry their world normal: POINTCLOUDLIGHTING lights them in the
shaded visual styles (1 lit from both sides, 2 with back faces dark),
from the view (POINTCLOUDLIGHTSOURCE 0) or the default lights (1).
The 2D wireframe style draws no cloud points: a found cloud shows its
extents and "Point clouds are not displayed in 2D visual style.",
or nothing with POINTCLOUD2DVSDISPLAY 1. While the view moves only
POINTCLOUDRTDENSITY per cent of the points are drawn; the points are
stored in an order where every leading part samples the whole cloud.
The colour map dialog lists its schemes by name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ramox81andClaude Opus 5.5 committed 2026-09-30 14:54:03 +03:00
1 parent ee0d4aedb5
commit b69ae291bb
10 files changed
+202 -22

No files matched your search

+39 -4
View File
@@ -165,8 +165,11 @@ pub struct ViewportData {
/// Wipeout fills — rendered in a separate pass AFTER wires.
pub(in crate::scene) wipeout_hatches: Arc<Vec<HatchModel>>,
pub(in crate::scene) images: Arc<Vec<ImageModel>>,
/// Point cloud sprites; empty on the paper sheet.
/// Point cloud sprites; empty on the paper sheet and in 2D wireframe.
pub(in crate::scene) point_clouds: Arc<crate::scene::model::point_cloud::PointCloudSet>,
/// Point cloud lighting and density for this view: `[mode, source,
/// drawn fraction, _, light direction x, y, z, _]`.
pub(in crate::scene) point_cloud_light: [f32; 8],
pub(in crate::scene) meshes: Arc<Vec<MeshLodSet>>,
pub(in crate::scene) background_image:
Option<crate::scene::model::image_model::DecodedImage>,
@@ -564,6 +567,7 @@ impl shader::Primitive for Primitive {
inner.upload_point_clouds(device, queue, &vp.point_clouds);
inner.cached_point_cloud_source = Some(Arc::clone(&vp.point_clouds));
}
inner.set_point_cloud_view(queue, vp.point_cloud_light);
if inner
.cached_text_source
.as_ref()
@@ -1427,6 +1431,7 @@ fn render_signature(vp: &ViewportData, placement: &PhysicalViewport) -> u64 {
std::sync::Arc::as_ptr(&vp.point_clouds)
}
.hash(&mut h);
vp.point_cloud_light.map(f32::to_bits).hash(&mut h);
vp.geometry_epoch.hash(&mut h);
vp.selection_generation.hash(&mut h);
vp.selected_sig.hash(&mut h);
@@ -4526,21 +4531,31 @@ impl Scene {
} else {
Arc::new(Vec::new())
};
// 2D wireframe draws no cloud points; the clouds show their extents
// and a message there instead (drawn with the live overlays).
let wireframe_2d = inst.render_mode == codec::entities::ViewportRenderMode::Wireframe2D;
let cloud_2d_wires = if wireframe_2d && show_live_overlay && !inst.paper_sheet {
self.point_cloud_2d_wires()
} else {
Vec::new()
};
let preview_wires = if !show_live_overlay
|| (self.interim_wire.is_none()
&& self.preview_wires.is_empty()
&& self.constraint_hover_wires.is_empty())
&& self.constraint_hover_wires.is_empty()
&& cloud_2d_wires.is_empty())
{
Arc::new(Vec::new())
} else {
let mut v: Vec<WireModel> = Vec::with_capacity(
self.preview_wires.len() + self.constraint_hover_wires.len() + 1,
self.preview_wires.len() + self.constraint_hover_wires.len() + cloud_2d_wires.len() + 1,
);
if let Some(iw) = &self.interim_wire {
v.push(iw.clone());
}
v.extend(self.preview_wires.iter().cloned());
v.extend(self.constraint_hover_wires.iter().cloned());
v.extend(cloud_2d_wires);
Arc::new(v)
};
let preview_hatches = if show_live_overlay {
@@ -4736,7 +4751,7 @@ impl Scene {
} else {
self.meshes_for_viewport(inst.handle, &vp_frozen)
};
let point_clouds = self.point_clouds_for_viewport(&vp_frozen, inst.paper_sheet);
let point_clouds = self.point_clouds_for_viewport(&vp_frozen, inst.paper_sheet || wireframe_2d);
// SDF text quads (behind OCS_TEXT_SDF). The glyph quads ride on each
// entity's own wire (produced by the tessellator, transformed for
@@ -4820,6 +4835,25 @@ impl Scene {
Arc::new(vec![])
};
let navigating = !inst.paper_sheet && self.navigating_lod();
let point_cloud_light = {
use crate::scene::model::point_cloud::{setting, setting_value};
let value = |name| setting(name).map_or(0, |s| setting_value(&self.document, s)) as f32;
// Lit only in the shaded styles, as the reference.
let lighting = if flags.mesh_fill && !flags.hidden_line { value("POINTCLOUDLIGHTING") } else { 0.0 };
let source = value("POINTCLOUDLIGHTSOURCE");
let forward = (inst.camera.rotation * glam::Vec3::NEG_Z).normalize_or(glam::Vec3::NEG_Z);
// ponytail: the default lights as one light from the upper left
// of the view; the reference's exact light set is not known.
let light = if source > 0.5 {
let (up, right) = (inst.camera.rotation * glam::Vec3::Y, inst.camera.rotation * glam::Vec3::X);
(-forward * 0.6 + up * 0.6 - right * 0.5).normalize_or(-forward)
} else {
-forward
};
// While the view moves, POINTCLOUDRTDENSITY per cent of the points.
let fraction = if navigating { value("POINTCLOUDRTDENSITY") / 100.0 } else { 1.0 };
[lighting, source, fraction, 0.0, light.x, light.y, light.z, 0.0]
};
Some(ViewportData {
instance_id,
force_rasterize,
@@ -4837,6 +4871,7 @@ impl Scene {
wipeout_hatches,
images,
point_clouds,
point_cloud_light,
meshes,
background_image: display.background.image.clone(),
environment_image: display.background.environment.clone(),