forked from 1b-agent/opencadstudio-upstream
Merge remote-tracking branch 'origin/main' into codex/count-feature
# Conflicts: # Cargo.lock # src/app/commands/field.rs # src/app/control/actions.rs # src/ui/window/field_dialog.rs
This commit is contained in:
commit
88d8a33572
95 files changed
+4270
-723
No files matched your search
@@ -816,6 +816,48 @@ impl Scene {
|
||||
true
|
||||
}
|
||||
|
||||
/// A boundary source split into pieces (TRIM, BREAK): every associative
|
||||
/// hatch bounded by `old` is bounded by all of `pieces` instead, so the
|
||||
/// region survives `old` being erased.
|
||||
pub(crate) fn split_hatch_source(&mut self, old: Handle, pieces: &[Handle]) {
|
||||
if pieces.is_empty() {
|
||||
return;
|
||||
}
|
||||
let dependents = self.associative_hatch_dependents(&std::iter::once(old).collect());
|
||||
if dependents.is_empty() {
|
||||
return;
|
||||
}
|
||||
for &hatch_handle in &dependents {
|
||||
if self.is_recording_undo() {
|
||||
let before = self.document.get_entity_arc(hatch_handle);
|
||||
self.record_undo_before(hatch_handle, before);
|
||||
}
|
||||
let Some(EntityType::Hatch(hatch)) = self.document.get_entity_mut(hatch_handle) else {
|
||||
continue;
|
||||
};
|
||||
for path in &mut hatch.paths {
|
||||
if !path.boundary_handles.contains(&old) {
|
||||
continue;
|
||||
}
|
||||
path.boundary_handles.retain(|source| *source != old);
|
||||
for &piece in pieces {
|
||||
if !path.boundary_handles.contains(&piece) {
|
||||
path.boundary_handles.push(piece);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.associative_hatch_source_cache.borrow_mut().take();
|
||||
let changes: Vec<_> = pieces
|
||||
.iter()
|
||||
.map(|&piece| (piece, ChangeKind::Modified))
|
||||
.collect();
|
||||
let refreshed = self.refresh_associative_hatches(&changes);
|
||||
if !refreshed.is_empty() {
|
||||
self.bump_entities(&refreshed);
|
||||
}
|
||||
}
|
||||
|
||||
fn associative_hatch_dependents(
|
||||
&self,
|
||||
changed: &rustc_hash::FxHashSet<Handle>,
|
||||
|
||||
@@ -31,23 +31,14 @@ pub fn tessellate_sat(
|
||||
if bodies.is_empty() {
|
||||
return None;
|
||||
}
|
||||
// The kernel's lift already applies each body's transform record; its
|
||||
// scale still sets the tessellation tolerance.
|
||||
let mut placed_bodies = Vec::with_capacity(bodies.len());
|
||||
for body in bodies {
|
||||
let source = body.provenance.source()?;
|
||||
let transform = body_transform(document, source.index() as usize).ok()?;
|
||||
let placement_scale = transform.map_or(1.0, |(_, _, scale)| scale.abs());
|
||||
let placed = if let Some((matrix, translation, scale)) = transform {
|
||||
let placement = brep::Placement {
|
||||
x_axis: [scale * matrix[0], scale * matrix[1], scale * matrix[2]],
|
||||
y_axis: [scale * matrix[3], scale * matrix[4], scale * matrix[5]],
|
||||
z_axis: [scale * matrix[6], scale * matrix[7], scale * matrix[8]],
|
||||
origin: translation,
|
||||
};
|
||||
brep::transform(&body, &placement)?
|
||||
} else {
|
||||
body
|
||||
};
|
||||
placed_bodies.push((placed, placement_scale));
|
||||
placed_bodies.push((body, placement_scale));
|
||||
}
|
||||
let bodies = placed_bodies;
|
||||
let resolution = if facet_res.is_finite() && facet_res > 0.0 {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use codec::entities::acis::{SabReader, SatBody, SatDocument};
|
||||
use codec::entities::{Body, Region, Solid3D, Surface};
|
||||
use kernel::brep::{self, Body as KernelBody};
|
||||
use kernel::brep::Body as KernelBody;
|
||||
|
||||
use crate::scene::model::mesh_model::{MeshLodSet, MeshModel};
|
||||
|
||||
@@ -179,21 +179,8 @@ pub(crate) fn kernel_acis_body(acis: &codec::entities::AcisData) -> Option<Kerne
|
||||
if !loss.is_empty() || bodies.len() != 1 {
|
||||
return None;
|
||||
}
|
||||
let body = bodies.pop()?;
|
||||
let source = body.provenance.source()?;
|
||||
let Some((matrix, translation, scale)) = body_transform(&sat, source.index() as usize).ok()?
|
||||
else {
|
||||
return Some(body);
|
||||
};
|
||||
brep::transform(
|
||||
&body,
|
||||
&brep::Placement {
|
||||
x_axis: [scale * matrix[0], scale * matrix[1], scale * matrix[2]],
|
||||
y_axis: [scale * matrix[3], scale * matrix[4], scale * matrix[5]],
|
||||
z_axis: [scale * matrix[6], scale * matrix[7], scale * matrix[8]],
|
||||
origin: translation,
|
||||
},
|
||||
)
|
||||
// The kernel's lift already applies the body's transform record.
|
||||
bodies.pop()
|
||||
}
|
||||
|
||||
fn remap_acis_material_bindings(
|
||||
|
||||
@@ -495,10 +495,17 @@ fn split_mixed_polyline(
|
||||
.iter()
|
||||
.copied()
|
||||
.fold(0.0f32, f32::max);
|
||||
let has_line_taper = straight_widths
|
||||
.first()
|
||||
.map_or(false, |&w0| straight_widths.iter().any(|&w| (w - w0).abs() > 1e-6));
|
||||
let taper_widths = if has_line_taper || straight_widths.iter().any(|&w| w > 1e-6) {
|
||||
// NaN breaks carry a 0 width; only real points decide whether the
|
||||
// band tapers, so a constant-width run keeps its mitered joints.
|
||||
let mut real_widths = line_pts
|
||||
.iter()
|
||||
.zip(&straight_widths)
|
||||
.filter(|(p, _)| p[0].is_finite())
|
||||
.map(|(_, &w)| w);
|
||||
let has_line_taper = real_widths
|
||||
.next()
|
||||
.map_or(false, |w0| real_widths.any(|w| (w - w0).abs() > 1e-6));
|
||||
let taper_widths = if has_line_taper {
|
||||
straight_widths
|
||||
} else {
|
||||
Vec::new()
|
||||
|
||||
@@ -143,6 +143,7 @@ impl Scene {
|
||||
/// layer 0. Auto-registering keeps it on its own layer (#252). Names are
|
||||
/// registered verbatim so the writer's (case-insensitive) lookup matches;
|
||||
/// the always-present default layer "0" and empty names are no-ops.
|
||||
/// Flags `layer_table_dirty` so the app refreshes its layer lists.
|
||||
pub fn ensure_layer(&mut self, name: &str) {
|
||||
if name.trim().is_empty() || self.document.layers.contains(name) {
|
||||
return;
|
||||
@@ -150,6 +151,8 @@ impl Scene {
|
||||
let mut layer = codec::tables::Layer::new(name);
|
||||
layer.handle = self.document.allocate_handle();
|
||||
let _ = self.document.layers.add(layer);
|
||||
self.invalidate_layer_dependencies(&[name.to_string()]);
|
||||
self.layer_table_dirty = true;
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_app_id(&mut self, name: &str) {
|
||||
|
||||
@@ -2067,6 +2067,9 @@ pub struct Scene {
|
||||
pub preview_text: Vec<crate::scene::pipeline::text_gpu::TextVertex>,
|
||||
/// Committed-segment wire drawn during multi-point commands (normal colour).
|
||||
pub interim_wire: Option<WireModel>,
|
||||
/// Set when an entity creation or update automatically registers a novel
|
||||
/// layer into the document layer table, signalling the UI to refresh.
|
||||
pub layer_table_dirty: bool,
|
||||
pub camera_generation: u64,
|
||||
/// Incremented whenever geometry-affecting state changes (entities, selection,
|
||||
/// preview wires, layer visibility, layout). The GPU pipeline uses this to
|
||||
@@ -2640,6 +2643,7 @@ impl Scene {
|
||||
preview_hatches: Arc::new(Vec::new()),
|
||||
preview_text: vec![],
|
||||
interim_wire: None,
|
||||
layer_table_dirty: false,
|
||||
camera_generation: 0,
|
||||
geometry_epoch: GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed),
|
||||
projection_bounds_epoch: std::cell::Cell::new(0),
|
||||
|
||||
@@ -278,7 +278,7 @@ pub fn reference_point(operation: &SolidHistoryOperation) -> Option<glam::DVec3>
|
||||
match operation {
|
||||
SolidHistoryOperation::Box(value) | SolidHistoryOperation::Wedge(value) => world_point(
|
||||
value.base.transform,
|
||||
[value.length * 0.5, value.width * 0.5, 0.0],
|
||||
[0.0, 0.0, -value.height * 0.5],
|
||||
),
|
||||
SolidHistoryOperation::Sphere(value) => {
|
||||
world_point(value.base.transform, [0.0, 0.0, 0.0])
|
||||
@@ -1615,9 +1615,10 @@ pub fn primitive_properties(
|
||||
) {
|
||||
return Vec::new();
|
||||
}
|
||||
let Some(operation) = primitive_property_operation(document, handle) else {
|
||||
let Some(mut operation) = primitive_property_operation(document, handle) else {
|
||||
return brep_properties(document, handle);
|
||||
};
|
||||
corner_frame(&mut operation);
|
||||
match &operation {
|
||||
SolidHistoryOperation::Box(value) => {
|
||||
rectangular_properties(document, handle, value, "Box")
|
||||
@@ -2414,6 +2415,17 @@ pub fn apply_primitive_property(
|
||||
operation: &mut SolidHistoryOperation,
|
||||
field: &str,
|
||||
value: &str,
|
||||
) -> bool {
|
||||
corner_frame(operation);
|
||||
let applied = apply_primitive_property_at_corner(operation, field, value);
|
||||
centre_frame(operation);
|
||||
applied
|
||||
}
|
||||
|
||||
fn apply_primitive_property_at_corner(
|
||||
operation: &mut SolidHistoryOperation,
|
||||
field: &str,
|
||||
value: &str,
|
||||
) -> bool {
|
||||
if let SolidHistoryOperation::Brep(brep_value) = operation {
|
||||
if let Some(applied) = apply_brep_position_property(brep_value, field, value) {
|
||||
@@ -2757,7 +2769,7 @@ fn embedded_entity(value: &EmbeddedEntity) -> Option<EntityType> {
|
||||
EmbeddedEntity::Region(value) => EntityType::Region(value.clone()),
|
||||
EmbeddedEntity::Ray(value) => EntityType::Ray(value.clone()),
|
||||
EmbeddedEntity::XLine(value) => EntityType::XLine(value.clone()),
|
||||
EmbeddedEntity::Unknown { .. } => return None,
|
||||
EmbeddedEntity::Body { .. } | EmbeddedEntity::Unknown { .. } => return None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3460,6 +3472,9 @@ pub fn primitive_grips(
|
||||
let Some(operation) = document.solid_history_operation(handle) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut operation = operation.clone();
|
||||
corner_frame(&mut operation);
|
||||
let operation = &operation;
|
||||
if let SolidHistoryOperation::Fillet(value) = operation {
|
||||
let Some(radius) = value.radii.first().copied() else {
|
||||
return Vec::new();
|
||||
@@ -3743,6 +3758,17 @@ pub fn apply_primitive_grip(
|
||||
operation: &mut SolidHistoryOperation,
|
||||
grip_id: usize,
|
||||
apply: GripApply,
|
||||
) -> bool {
|
||||
corner_frame(operation);
|
||||
let applied = apply_primitive_grip_at_corner(operation, grip_id, apply);
|
||||
centre_frame(operation);
|
||||
applied
|
||||
}
|
||||
|
||||
fn apply_primitive_grip_at_corner(
|
||||
operation: &mut SolidHistoryOperation,
|
||||
grip_id: usize,
|
||||
apply: GripApply,
|
||||
) -> bool {
|
||||
if let SolidHistoryOperation::Revolve(value) = operation {
|
||||
if let Some(index) = grip_id.checked_sub(GRIP_REVOLVE_PROFILE_FIRST) {
|
||||
@@ -4138,3 +4164,23 @@ pub fn chamfer_op(
|
||||
..SolidHistoryChamfer::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// Box and wedge histories are framed at the centre of their bounding box,
|
||||
/// as the reference stores them; the editing code here works in the frame at
|
||||
/// the base corner. These move a box or wedge between the two frames.
|
||||
fn corner_frame(operation: &mut SolidHistoryOperation) {
|
||||
shift_rectangular_frame(operation, -0.5);
|
||||
}
|
||||
|
||||
fn centre_frame(operation: &mut SolidHistoryOperation) {
|
||||
shift_rectangular_frame(operation, 0.5);
|
||||
}
|
||||
|
||||
fn shift_rectangular_frame(operation: &mut SolidHistoryOperation, factor: f64) {
|
||||
if let SolidHistoryOperation::Box(value) | SolidHistoryOperation::Wedge(value) = operation {
|
||||
if let Some(current) = matrix(value.base.transform) {
|
||||
let half = glam::DVec3::new(value.length, value.width, value.height) * factor;
|
||||
value.base.transform = (current * glam::DMat4::from_translation(half)).to_cols_array();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -744,6 +744,11 @@ impl WireModel {
|
||||
pub fn is_display_only(&self) -> bool {
|
||||
self.snap_only && self.display_visible
|
||||
}
|
||||
/// A POINT's wire: the Node is its only snap; the glyph strokes are drawn
|
||||
/// but offer no endpoints or segments to snap to.
|
||||
pub fn is_node_marker(&self) -> bool {
|
||||
self.key_vertices.is_empty() && matches!(self.snap_pts.as_slice(), [(_, SnapHint::Node)])
|
||||
}
|
||||
/// Sentinel AABB that never rejects any snap query.
|
||||
pub const UNBOUNDED_AABB: [f32; 4] = [
|
||||
f32::NEG_INFINITY,
|
||||
|
||||
@@ -699,7 +699,7 @@ fn append_wire_index_entries(wire_idx: u32, wire: &WireModel, batch: &mut WireIn
|
||||
);
|
||||
}
|
||||
|
||||
if wire.point_marker.is_none() {
|
||||
if wire.point_marker.is_none() && !wire.is_node_marker() {
|
||||
for start in 0..wire.points.len().saturating_sub(1) {
|
||||
let Some(aabb) = points_aabb3([
|
||||
wire_point(wire, start),
|
||||
|
||||
+117
-10
@@ -71,8 +71,9 @@ pub struct WireInstance {
|
||||
/// Endpoint width / the per-wire maximum width, normalized by the vertex
|
||||
/// fetch unit. `[0, 0]` means use the constant width. Ratios retain the
|
||||
/// full f32 world-width scale in `WireConst` while making every instance
|
||||
/// exactly one 64-byte cache line.
|
||||
pub taper_ratio: [u16; 2],
|
||||
/// exactly one 64-byte cache line. A constant-width band carries its
|
||||
/// joint miters (`band_miters`) here instead.
|
||||
pub taper_ratio: [i16; 2],
|
||||
}
|
||||
|
||||
impl WireInstance {
|
||||
@@ -86,7 +87,7 @@ impl WireInstance {
|
||||
wgpu::VertexAttribute { offset: std::mem::offset_of!(WireInstance, distance_a) as u64, shader_location: 4, format: wgpu::VertexFormat::Float32 },
|
||||
wgpu::VertexAttribute { offset: std::mem::offset_of!(WireInstance, distance_b) as u64, shader_location: 5, format: wgpu::VertexFormat::Float32 },
|
||||
wgpu::VertexAttribute { offset: std::mem::offset_of!(WireInstance, wire_id) as u64, shader_location: 6, format: wgpu::VertexFormat::Uint32 },
|
||||
wgpu::VertexAttribute { offset: std::mem::offset_of!(WireInstance, taper_ratio) as u64, shader_location: 7, format: wgpu::VertexFormat::Unorm16x2 },
|
||||
wgpu::VertexAttribute { offset: std::mem::offset_of!(WireInstance, taper_ratio) as u64, shader_location: 7, format: wgpu::VertexFormat::Snorm16x2 },
|
||||
];
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<WireInstance>() as u64,
|
||||
@@ -288,7 +289,7 @@ pub struct BlockWireVertex {
|
||||
pub pos_b: [f32; 3],
|
||||
pub pos_b_low: [f32; 3],
|
||||
pub distances: [f32; 2],
|
||||
pub taper_ratio: [u16; 2],
|
||||
pub taper_ratio: [i16; 2],
|
||||
}
|
||||
|
||||
impl BlockWireVertex {
|
||||
@@ -299,7 +300,7 @@ impl BlockWireVertex {
|
||||
wgpu::VertexAttribute { offset: std::mem::offset_of!(BlockWireVertex, pos_a_low) as u64, shader_location: 2, format: wgpu::VertexFormat::Float32x3 },
|
||||
wgpu::VertexAttribute { offset: std::mem::offset_of!(BlockWireVertex, pos_b_low) as u64, shader_location: 3, format: wgpu::VertexFormat::Float32x3 },
|
||||
wgpu::VertexAttribute { offset: std::mem::offset_of!(BlockWireVertex, distances) as u64, shader_location: 4, format: wgpu::VertexFormat::Float32x2 },
|
||||
wgpu::VertexAttribute { offset: std::mem::offset_of!(BlockWireVertex, taper_ratio) as u64, shader_location: 5, format: wgpu::VertexFormat::Unorm16x2 },
|
||||
wgpu::VertexAttribute { offset: std::mem::offset_of!(BlockWireVertex, taper_ratio) as u64, shader_location: 5, format: wgpu::VertexFormat::Snorm16x2 },
|
||||
];
|
||||
wgpu::VertexBufferLayout {
|
||||
array_stride: std::mem::size_of::<Self>() as u64,
|
||||
@@ -577,6 +578,96 @@ fn marker_metadata(wire: &WireModel) -> ([f32; 4], [f32; 4], [f32; 4]) {
|
||||
)
|
||||
}
|
||||
|
||||
/// Largest |tan(turn/2)| a band joint is mitered at; sharper turns stay butt
|
||||
/// ends instead of growing a long spike. Must match `MITER_LIMIT` in the wire
|
||||
/// shaders, which decode the normalized value back.
|
||||
pub(crate) const MITER_LIMIT: f64 = 8.0;
|
||||
|
||||
/// Per-segment miter factors `[k_a, k_b]` of a constant-width world band,
|
||||
/// normalized by `MITER_LIMIT`. The shader slides each quad corner by
|
||||
/// `k·side·half_width` along the segment, so neighbouring segments meet on
|
||||
/// their joint's bisector instead of leaving a notch at the outer corner.
|
||||
/// Empty for every other wire.
|
||||
fn band_miters(wire: &WireModel, normal: [f32; 4]) -> Vec<[f32; 2]> {
|
||||
let n = wire.points.len();
|
||||
if wire.world_width <= 0.0 || !wire.taper_widths.is_empty() || n < 3 {
|
||||
return Vec::new();
|
||||
}
|
||||
let at = |i: usize| -> Option<glam::DVec3> {
|
||||
let h = wire.points[i];
|
||||
let l = wire.points_low.get(i).copied().unwrap_or([0.0; 3]);
|
||||
finite3(h).then(|| {
|
||||
glam::DVec3::new(
|
||||
h[0] as f64 + l[0] as f64,
|
||||
h[1] as f64 + l[1] as f64,
|
||||
h[2] as f64 + l[2] as f64,
|
||||
)
|
||||
})
|
||||
};
|
||||
let same = |a: glam::DVec3, b: glam::DVec3| a.distance(b) <= 1e-9 * (1.0 + a.length());
|
||||
let first = (0..n).find_map(|i| at(i).map(|p| (i, p)));
|
||||
let last = (0..n).rev().find_map(|i| at(i).map(|p| (i, p)));
|
||||
let closed = matches!((first, last), (Some((i, a)), Some((j, b))) if i < j && same(a, b));
|
||||
// Direction leaving joint `from` (or arriving at it when `step` walks
|
||||
// back), skipping duplicate points and NaN breaks whose next run starts
|
||||
// at the same joint; a closed wire wraps around once.
|
||||
let neighbour = |from: usize, step: isize| -> Option<glam::DVec3> {
|
||||
let joint = at(from)?;
|
||||
let mut i = from as isize;
|
||||
let mut wrapped = false;
|
||||
let mut broken = false;
|
||||
loop {
|
||||
i += step;
|
||||
if i < 0 || i >= n as isize {
|
||||
if !closed || wrapped {
|
||||
return None;
|
||||
}
|
||||
wrapped = true;
|
||||
i = if step > 0 { first?.0 as isize } else { last?.0 as isize };
|
||||
continue;
|
||||
}
|
||||
let Some(q) = at(i as usize) else {
|
||||
broken = true;
|
||||
continue;
|
||||
};
|
||||
if same(q, joint) {
|
||||
broken = false;
|
||||
continue;
|
||||
}
|
||||
if broken {
|
||||
return None;
|
||||
}
|
||||
let d = if step > 0 { q - joint } else { joint - q };
|
||||
return Some(d.normalize());
|
||||
}
|
||||
};
|
||||
let axis = glam::DVec3::new(normal[0] as f64, normal[1] as f64, normal[2] as f64);
|
||||
let axis = if axis.length() > 1e-4 { axis.normalize() } else { glam::DVec3::Z };
|
||||
// tan of half the signed turn from `d1` to `d2` about the band normal.
|
||||
let half_turn = |d1: glam::DVec3, d2: glam::DVec3| -> f32 {
|
||||
let cos = d1.dot(d2);
|
||||
if cos <= -1.0 + 1e-9 {
|
||||
return 0.0;
|
||||
}
|
||||
let t = axis.dot(d1.cross(d2)) / (1.0 + cos);
|
||||
if t.abs() > MITER_LIMIT { 0.0 } else { (t / MITER_LIMIT) as f32 }
|
||||
};
|
||||
(0..n - 1)
|
||||
.map(|i| {
|
||||
let (Some(a), Some(b)) = (at(i), at(i + 1)) else {
|
||||
return [0.0; 2];
|
||||
};
|
||||
if same(a, b) {
|
||||
return [0.0; 2];
|
||||
}
|
||||
let d = (b - a).normalize();
|
||||
let k_a = neighbour(i, -1).map_or(0.0, |prev| half_turn(prev, d));
|
||||
let k_b = neighbour(i + 1, 1).map_or(0.0, |next| -half_turn(d, next));
|
||||
[k_a, k_b]
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Emit packed per-segment instances (each carries the wire's constants).
|
||||
pub(crate) fn emit_wire_packed(
|
||||
wire: &WireModel,
|
||||
@@ -599,6 +690,7 @@ pub(crate) fn emit_wire_packed(
|
||||
(Vec::new(), 0.0, 0.0)
|
||||
};
|
||||
let (marker_origin_high, marker_origin_low, marker_normal_scale) = marker_metadata(wire);
|
||||
let miters = band_miters(wire, marker_normal_scale);
|
||||
let low = |i: usize| -> [f32; 3] { wire.points_low.get(i).copied().unwrap_or([0.0; 3]) };
|
||||
let is_tapered = !wire.taper_widths.is_empty();
|
||||
let tw = |i: usize| -> f32 {
|
||||
@@ -638,8 +730,17 @@ pub(crate) fn emit_wire_packed(
|
||||
world_half_width: wire.world_width * 0.5,
|
||||
world_hw_a: tw(i),
|
||||
world_hw_b: tw(i + 1),
|
||||
marker_origin_high,
|
||||
marker_origin_low,
|
||||
// The origin `w` slots are free: they carry the band miters.
|
||||
marker_origin_high: {
|
||||
let mut m = marker_origin_high;
|
||||
m[3] = miters.get(i).map_or(0.0, |k| k[0]);
|
||||
m
|
||||
},
|
||||
marker_origin_low: {
|
||||
let mut m = marker_origin_low;
|
||||
m[3] = miters.get(i).map_or(0.0, |k| k[1]);
|
||||
m
|
||||
},
|
||||
marker_normal_scale,
|
||||
});
|
||||
}
|
||||
@@ -684,14 +785,17 @@ pub(crate) fn emit_wire_native(
|
||||
return (Vec::new(), cst);
|
||||
}
|
||||
let low = |i: usize| -> [f32; 3] { wire.points_low.get(i).copied().unwrap_or([0.0; 3]) };
|
||||
let taper_ratio = |i: usize| -> u16 {
|
||||
let snorm = |v: f32| -> i16 { (v.clamp(-1.0, 1.0) * i16::MAX as f32).round() as i16 };
|
||||
let taper_ratio = |i: usize| -> i16 {
|
||||
if !is_tapered || wire.world_width <= 0.0 {
|
||||
0
|
||||
} else {
|
||||
let width = wire.taper_widths.get(i).copied().unwrap_or(0.0);
|
||||
((width / wire.world_width).clamp(0.0, 1.0) * u16::MAX as f32).round() as u16
|
||||
snorm(width / wire.world_width)
|
||||
}
|
||||
};
|
||||
// A constant-width band reuses the slot for its joint miters.
|
||||
let miters = band_miters(wire, marker_normal_scale);
|
||||
let mut instances: Vec<WireInstance> = Vec::with_capacity(seg_count);
|
||||
for i in 0..seg_count {
|
||||
let a = wire.points[i];
|
||||
@@ -712,7 +816,10 @@ pub(crate) fn emit_wire_native(
|
||||
distance_a: dist_a,
|
||||
distance_b: dist_b,
|
||||
wire_id,
|
||||
taper_ratio: [taper_ratio(i), taper_ratio(i + 1)],
|
||||
taper_ratio: match miters.get(i) {
|
||||
Some(k) => [snorm(k[0]), snorm(k[1])],
|
||||
None => [taper_ratio(i), taper_ratio(i + 1)],
|
||||
},
|
||||
});
|
||||
}
|
||||
(instances, cst)
|
||||
|
||||
Reference in new issue
Block a user