forked from 1b-agent/opencadstudio-upstream
feat(scene): integrate standard DWG data
This commit is contained in:
1 parent
731df4a725
commit
eb23715605
38 files changed
+8376
-618
No files matched your search
@@ -34,6 +34,7 @@ use crate::scene::convert::solid3d_tess::{
|
||||
TRUCK_CHORD_FRAC,
|
||||
};
|
||||
use crate::scene::model::mesh_model::MeshLodSet;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
/// Slightly over 2π so revolution builders close the loop.
|
||||
const FULL: f64 = std::f64::consts::TAU + 0.2;
|
||||
@@ -91,9 +92,53 @@ pub fn tessellate_sat_truck(
|
||||
let mut verts: Vec<[f64; 3]> = Vec::new();
|
||||
let mut normals: Vec<[f32; 3]> = Vec::new();
|
||||
let mut indices: Vec<u32> = Vec::new();
|
||||
let face_materials: FxHashMap<i32, acadrust::Handle> = sat
|
||||
.records
|
||||
.iter()
|
||||
.filter(|record| record.entity_type == "material-adesk-attrib")
|
||||
.filter_map(|record| {
|
||||
let owner = record.token_pointer(2)?.0;
|
||||
let handle = record.token(3)?.as_integer()?;
|
||||
(owner >= 0 && handle > 0)
|
||||
.then(|| (owner, acadrust::Handle::new(handle as u64)))
|
||||
})
|
||||
.collect();
|
||||
let face_colors: FxHashMap<i32, [f32; 4]> = sat
|
||||
.records
|
||||
.iter()
|
||||
.filter(|record| record.entity_type == "color-adesk-attrib")
|
||||
.filter_map(|record| {
|
||||
let owner = record.token_pointer(2)?.0;
|
||||
let value = record.token(3)?.as_integer()?;
|
||||
let face_color = if (1..=255).contains(&value) {
|
||||
acadrust::Color::from_index(value as i16)
|
||||
} else if value > 257 {
|
||||
acadrust::Color::from_true_color_value(value as i32)
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
Some((
|
||||
owner,
|
||||
{
|
||||
let mut rgba =
|
||||
crate::scene::convert::tess_util::aci_to_rgba(&face_color);
|
||||
rgba[3] = color[3];
|
||||
rgba
|
||||
},
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
let mut triangle_material_handles: Vec<Option<acadrust::Handle>> = Vec::new();
|
||||
let mut triangle_colors: Vec<Option<[f32; 4]>> = Vec::new();
|
||||
let face_record_indices: Vec<i32> = sat
|
||||
.records
|
||||
.iter()
|
||||
.filter(|record| record.is_a("face"))
|
||||
.map(|record| record.index)
|
||||
.collect();
|
||||
let mut complete = true;
|
||||
|
||||
for face in sat.faces().into_iter() {
|
||||
for (face, face_record_index) in sat.faces().into_iter().zip(face_record_indices) {
|
||||
let Some(surf_rec) = sat.resolve(face.surface()) else {
|
||||
complete = false;
|
||||
continue;
|
||||
@@ -121,6 +166,15 @@ pub fn tessellate_sat_truck(
|
||||
}
|
||||
if !appended {
|
||||
complete = false;
|
||||
} else {
|
||||
let material = face_materials.get(&face_record_index).copied();
|
||||
triangle_material_handles.extend(
|
||||
std::iter::repeat(material).take((indices.len() - before) / 3),
|
||||
);
|
||||
let face_color = face_colors.get(&face_record_index).copied();
|
||||
triangle_colors.extend(
|
||||
std::iter::repeat(face_color).take((indices.len() - before) / 3),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +182,16 @@ pub fn tessellate_sat_truck(
|
||||
return None;
|
||||
}
|
||||
|
||||
let mesh = finalize_mesh(name, verts, normals, indices, color, body_transform(sat));
|
||||
let mesh = finalize_mesh(
|
||||
name,
|
||||
verts,
|
||||
normals,
|
||||
indices,
|
||||
triangle_material_handles,
|
||||
triangle_colors,
|
||||
color,
|
||||
body_transform(sat),
|
||||
);
|
||||
let mut set = MeshLodSet::from_lods(vec![mesh]);
|
||||
set.complete = complete;
|
||||
Some(set)
|
||||
@@ -166,7 +229,7 @@ fn bespoke_face(
|
||||
tess_torus_face(sat, face, &t, LodConfig::HIGH, v, n, i);
|
||||
}
|
||||
}
|
||||
"spline-surface" => {
|
||||
"spline-surface" | "meshsurf-surface" | "bs3-surface" => {
|
||||
crate::scene::convert::spline_tess::tess_spline_face(
|
||||
sat,
|
||||
face,
|
||||
|
||||
@@ -12,12 +12,13 @@
|
||||
// editing never mistakes them for closed topology.
|
||||
|
||||
use rustc_hash::FxHashSet as HashSet;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::f64::consts::TAU;
|
||||
|
||||
use acadrust::entities::acis::types::Sense;
|
||||
use acadrust::entities::acis::{
|
||||
SabReader, SatCoedge, SatConeSurface, SatDocument, SatEdge, SatEllipseCurve, SatFace,
|
||||
SatIntCurve, SatLoop, SatPlaneSurface, SatPoint, SatPointer, SatSphereSurface, SatToken,
|
||||
SatIntCurve, SatLoop, SatPlaneSurface, SatPoint, SatPointer, SatSphereSurface,
|
||||
SatTorusSurface, SatVertex,
|
||||
};
|
||||
use acadrust::entities::{Body, Region, Solid3D};
|
||||
@@ -127,9 +128,48 @@ fn tessellate_sat(
|
||||
let mut verts: Vec<[f64; 3]> = Vec::new();
|
||||
let mut normals: Vec<[f32; 3]> = Vec::new();
|
||||
let mut indices: Vec<u32> = Vec::new();
|
||||
let face_materials: FxHashMap<i32, acadrust::Handle> = sat
|
||||
.records
|
||||
.iter()
|
||||
.filter(|record| record.entity_type == "material-adesk-attrib")
|
||||
.filter_map(|record| {
|
||||
let owner = record.token_pointer(2)?.0;
|
||||
let handle = record.token(3)?.as_integer()?;
|
||||
(owner >= 0 && handle > 0)
|
||||
.then(|| (owner, acadrust::Handle::new(handle as u64)))
|
||||
})
|
||||
.collect();
|
||||
let face_colors: FxHashMap<i32, [f32; 4]> = sat
|
||||
.records
|
||||
.iter()
|
||||
.filter(|record| record.entity_type == "color-adesk-attrib")
|
||||
.filter_map(|record| {
|
||||
let owner = record.token_pointer(2)?.0;
|
||||
let value = record.token(3)?.as_integer()?;
|
||||
let color_value = if (1..=255).contains(&value) {
|
||||
Some(acadrust::Color::from_index(value as i16))
|
||||
} else if value > 257 {
|
||||
Some(acadrust::Color::from_true_color_value(value as i32))
|
||||
} else {
|
||||
None
|
||||
}?;
|
||||
let mut rgba =
|
||||
crate::scene::convert::tess_util::aci_to_rgba(&color_value);
|
||||
rgba[3] = color[3];
|
||||
Some((owner, rgba))
|
||||
})
|
||||
.collect();
|
||||
let mut triangle_material_handles: Vec<Option<acadrust::Handle>> = Vec::new();
|
||||
let mut triangle_colors: Vec<Option<[f32; 4]>> = Vec::new();
|
||||
let face_record_indices: Vec<i32> = sat
|
||||
.records
|
||||
.iter()
|
||||
.filter(|record| record.is_a("face"))
|
||||
.map(|record| record.index)
|
||||
.collect();
|
||||
let mut complete = true;
|
||||
|
||||
for face in sat.faces() {
|
||||
for (face, face_record_index) in sat.faces().into_iter().zip(face_record_indices) {
|
||||
let surf_ptr = face.surface();
|
||||
let Some(surf_rec) = sat.resolve(surf_ptr) else {
|
||||
complete = false;
|
||||
@@ -189,7 +229,7 @@ fn tessellate_sat(
|
||||
);
|
||||
}
|
||||
}
|
||||
"spline-surface" => {
|
||||
"spline-surface" | "meshsurf-surface" | "bs3-surface" => {
|
||||
crate::scene::convert::spline_tess::tess_spline_face(
|
||||
sat,
|
||||
&face,
|
||||
@@ -203,13 +243,31 @@ fn tessellate_sat(
|
||||
}
|
||||
if indices.len() == before {
|
||||
complete = false;
|
||||
} else {
|
||||
let material = face_materials.get(&face_record_index).copied();
|
||||
triangle_material_handles.extend(
|
||||
std::iter::repeat(material).take((indices.len() - before) / 3),
|
||||
);
|
||||
let face_color = face_colors.get(&face_record_index).copied();
|
||||
triangle_colors.extend(
|
||||
std::iter::repeat(face_color).take((indices.len() - before) / 3),
|
||||
);
|
||||
}
|
||||
}
|
||||
if indices.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some((
|
||||
finalize_mesh(name, verts, normals, indices, color, xform),
|
||||
finalize_mesh(
|
||||
name,
|
||||
verts,
|
||||
normals,
|
||||
indices,
|
||||
triangle_material_handles,
|
||||
triangle_colors,
|
||||
color,
|
||||
xform,
|
||||
),
|
||||
complete,
|
||||
))
|
||||
}
|
||||
@@ -937,34 +995,43 @@ fn tessellate_sat_lods(
|
||||
/// record (`<3×3> <tx ty tz> <scale> rotate reflect shear`). `None` when the
|
||||
/// document has no transform (treated as identity).
|
||||
pub(crate) fn body_transform(sat: &SatDocument) -> Option<([f64; 9], [f64; 3], f64)> {
|
||||
let t = sat.records.iter().find(|r| r.entity_type == "transform")?;
|
||||
// The transform record's numeric payload is its first 13 numeric values:
|
||||
// 3×3 matrix, translation, scale. A leading book-keeping pointer (`$-1`)
|
||||
// and the trailing rotate/reflect/shear flags aren't numeric, so
|
||||
// collecting numeric tokens skips them — reading by raw token index would
|
||||
// be thrown off by the leading pointer. SAT text tokenizes the payload as
|
||||
// 13 individual floats, but the SAB reader groups the matrix rows and the
|
||||
// translation into `Position` triplets, so those must be flattened too.
|
||||
let mut v: Vec<f64> = Vec::with_capacity(13);
|
||||
for tok in &t.tokens {
|
||||
if v.len() >= 13 {
|
||||
let transform = sat
|
||||
.records
|
||||
.iter()
|
||||
.find(|record| record.entity_type == "transform")?;
|
||||
let mut values = Vec::with_capacity(13);
|
||||
for token in &transform.tokens {
|
||||
if values.len() >= 13 {
|
||||
break;
|
||||
}
|
||||
match tok {
|
||||
SatToken::Position(x, y, z) => v.extend([*x, *y, *z]),
|
||||
_ => {
|
||||
if let Some(f) = tok.as_float() {
|
||||
v.push(f);
|
||||
if let Some((components, len)) = token.coordinate_components() {
|
||||
values.extend_from_slice(&components[..len]);
|
||||
} else if let Some(value) = token.as_float() {
|
||||
values.push(value);
|
||||
} else if let Some(text) = token.as_string() {
|
||||
// Some ASM SAB bodies pack the complete SAT transform payload in
|
||||
// one long-string token instead of individual numeric tokens.
|
||||
for word in text.split_ascii_whitespace() {
|
||||
let Ok(value) = word.parse::<f64>() else {
|
||||
break;
|
||||
};
|
||||
values.push(value);
|
||||
if values.len() >= 13 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if v.len() < 13 {
|
||||
return None;
|
||||
}
|
||||
let m = [v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8]];
|
||||
let tr = [v[9], v[10], v[11]];
|
||||
Some((m, tr, v[12]))
|
||||
(values.len() >= 13).then(|| {
|
||||
(
|
||||
[
|
||||
values[0], values[1], values[2], values[3], values[4], values[5], values[6],
|
||||
values[7], values[8],
|
||||
],
|
||||
[values[9], values[10], values[11]],
|
||||
values[12],
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Apply a body placement transform to a mesh. ACIS treats points as row
|
||||
@@ -985,6 +1052,8 @@ pub(crate) fn finalize_mesh(
|
||||
verts: Vec<[f64; 3]>,
|
||||
normals: Vec<[f32; 3]>,
|
||||
indices: Vec<u32>,
|
||||
triangle_material_handles: Vec<Option<acadrust::Handle>>,
|
||||
triangle_colors: Vec<Option<[f32; 4]>>,
|
||||
color: [f32; 4],
|
||||
xform: Option<([f64; 9], [f64; 3], f64)>,
|
||||
) -> MeshModel {
|
||||
@@ -1031,6 +1100,8 @@ pub(crate) fn finalize_mesh(
|
||||
verts_low: lo,
|
||||
normals,
|
||||
indices,
|
||||
triangle_material_handles,
|
||||
triangle_colors,
|
||||
color,
|
||||
selected: false,
|
||||
}
|
||||
@@ -1088,6 +1159,76 @@ fn parse_acis(
|
||||
None
|
||||
}
|
||||
|
||||
fn remap_acis_material_bindings(
|
||||
set: &mut MeshLodSet,
|
||||
acis: &acadrust::entities::AcisData,
|
||||
) {
|
||||
if acis.materials.is_empty() {
|
||||
return;
|
||||
}
|
||||
for lod in &mut set.lods {
|
||||
for handle in lod.triangle_material_handles.iter_mut().flatten() {
|
||||
let reference = handle.value() as i32;
|
||||
if let Some(material_handle) = acis.materials.iter().find_map(|binding| {
|
||||
(binding.absolute_reference == reference || binding.array_index == reference)
|
||||
.then_some(binding.material_handle)
|
||||
.flatten()
|
||||
}) {
|
||||
*handle = material_handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn attach_stored_silhouettes(
|
||||
set: &mut MeshLodSet,
|
||||
silhouettes: &[acadrust::entities::Silhouette],
|
||||
) {
|
||||
use crate::scene::model::mesh_model::StoredSilhouette;
|
||||
set.stored_silhouettes = silhouettes
|
||||
.iter()
|
||||
.filter_map(|silhouette| {
|
||||
let mut edge_verts = Vec::new();
|
||||
let mut edge_verts_low = Vec::new();
|
||||
for wire in &silhouette.wires {
|
||||
for segment in wire.points.windows(2) {
|
||||
for point in segment {
|
||||
let point = crate::entities::solid3d::wire_point(wire, point);
|
||||
let high = [point.x as f32, point.y as f32, point.z as f32];
|
||||
edge_verts.push(high);
|
||||
edge_verts_low.push([
|
||||
(point.x - high[0] as f64) as f32,
|
||||
(point.y - high[1] as f64) as f32,
|
||||
(point.z - high[2] as f64) as f32,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
(!edge_verts.is_empty()).then_some(StoredSilhouette {
|
||||
viewport_id: silhouette.viewport_id,
|
||||
view_direction: [
|
||||
silhouette.view_direction.x as f32,
|
||||
silhouette.view_direction.y as f32,
|
||||
silhouette.view_direction.z as f32,
|
||||
],
|
||||
up_vector: [
|
||||
silhouette.up_vector.x as f32,
|
||||
silhouette.up_vector.y as f32,
|
||||
silhouette.up_vector.z as f32,
|
||||
],
|
||||
target: [
|
||||
silhouette.target.x as f32,
|
||||
silhouette.target.y as f32,
|
||||
silhouette.target.z as f32,
|
||||
],
|
||||
is_perspective: silhouette.is_perspective,
|
||||
edge_verts,
|
||||
edge_verts_low,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
/// Tessellate a `Region` entity (2D planar ACIS body) at all three LOD levels.
|
||||
pub fn tessellate_region(
|
||||
region: &Region,
|
||||
@@ -1101,7 +1242,10 @@ pub fn tessellate_region(
|
||||
®ion.acis_data.sab_data,
|
||||
)?;
|
||||
let name = region.common.handle.value().to_string();
|
||||
tessellate_acis(&sat, name, color, facet_res, isolines)
|
||||
let mut set = tessellate_acis(&sat, name, color, facet_res, isolines)?;
|
||||
remap_acis_material_bindings(&mut set, ®ion.acis_data);
|
||||
attach_stored_silhouettes(&mut set, ®ion.silhouettes);
|
||||
Some(set)
|
||||
}
|
||||
|
||||
/// Tessellate a `Body` entity (3D ACIS body) at all three LOD levels.
|
||||
@@ -1117,7 +1261,10 @@ pub fn tessellate_body(
|
||||
&body.acis_data.sab_data,
|
||||
)?;
|
||||
let name = body.common.handle.value().to_string();
|
||||
tessellate_acis(&sat, name, color, facet_res, isolines)
|
||||
let mut set = tessellate_acis(&sat, name, color, facet_res, isolines)?;
|
||||
remap_acis_material_bindings(&mut set, &body.acis_data);
|
||||
attach_stored_silhouettes(&mut set, &body.silhouettes);
|
||||
Some(set)
|
||||
}
|
||||
|
||||
/// Tessellate a `Surface` entity (ACAD_SURFACE family) at all three LOD
|
||||
@@ -1136,7 +1283,12 @@ pub fn tessellate_surface(
|
||||
&surface.acis_data.sab_data,
|
||||
)?;
|
||||
let name = surface.common.handle.value().to_string();
|
||||
tessellate_acis(&sat, name, color, facet_res, isolines)
|
||||
let surface_isolines = usize::from(surface.u_isolines.max(surface.v_isolines).max(0) as u16);
|
||||
let mut set =
|
||||
tessellate_acis(&sat, name, color, facet_res, surface_isolines.max(isolines))?;
|
||||
remap_acis_material_bindings(&mut set, &surface.acis_data);
|
||||
attach_stored_silhouettes(&mut set, &surface.silhouettes);
|
||||
Some(set)
|
||||
}
|
||||
|
||||
/// Tessellate a `Solid3D` entity at all three LOD levels.
|
||||
@@ -1156,7 +1308,10 @@ pub fn tessellate_solid3d(
|
||||
&solid.acis_data.sab_data,
|
||||
)?;
|
||||
let name = solid.common.handle.value().to_string();
|
||||
tessellate_acis(&sat, name, color, facet_res, isolines)
|
||||
let mut set = tessellate_acis(&sat, name, color, facet_res, isolines)?;
|
||||
remap_acis_material_bindings(&mut set, &solid.acis_data);
|
||||
attach_stored_silhouettes(&mut set, &solid.silhouettes);
|
||||
Some(set)
|
||||
}
|
||||
|
||||
// ── Topology helpers ──────────────────────────────────────────────────────────
|
||||
|
||||
+46
-10
@@ -357,6 +357,8 @@ pub(crate) fn tessellate_entity(
|
||||
// by the viewport's GPU uniform so it never changes resident wire content.
|
||||
paper_space: bool,
|
||||
) -> Vec<WireModel> {
|
||||
let contextual = crate::scene::annotative::entity_for_active_context(document, e);
|
||||
let e = contextual.as_ref();
|
||||
let h = e.common().handle;
|
||||
let sel = selected.contains(&h);
|
||||
|
||||
@@ -518,9 +520,22 @@ pub(crate) fn tessellate_entity(
|
||||
// blob, the vector preview its author cached for exactly this case. AutoCAD
|
||||
// draws that when the object enabler is missing; draw it too, so the entity
|
||||
// occupies its real place instead of silently disappearing.
|
||||
if let EntityType::Unknown(_) = e {
|
||||
if let Some(blob) = e.common().graphic_data.as_ref() {
|
||||
let dec = convert::proxy_graphics::decode(blob);
|
||||
let proxy_blob: Option<std::borrow::Cow<'_, [u8]>> = match e {
|
||||
EntityType::Unknown(_) => e
|
||||
.common()
|
||||
.graphic_data
|
||||
.as_deref()
|
||||
.map(std::borrow::Cow::Borrowed),
|
||||
EntityType::Extended(entity) => match &entity.data {
|
||||
acadrust::entities::ExtendedEntityData::Proxy(proxy) => {
|
||||
Some(std::borrow::Cow::Owned(proxy.graphics.data()))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
if let Some(blob) = proxy_blob {
|
||||
let dec = convert::proxy_graphics::decode(blob.as_ref());
|
||||
if !dec.polylines.is_empty() || !dec.texts.is_empty() {
|
||||
use crate::scene::convert::proxy_graphics::ProxyColor;
|
||||
use std::collections::BTreeMap;
|
||||
@@ -618,7 +633,6 @@ pub(crate) fn tessellate_entity(
|
||||
return wires;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Section symbol (AcDbSectionSymbol): draw the "A-A" cut mark ──────────
|
||||
@@ -744,10 +758,13 @@ pub(crate) fn tessellate_entity(
|
||||
// Sub-entities inside *D### / DIMBLOCK## blocks
|
||||
// typically use ByBlock color/linetype/lineweight —
|
||||
// they should inherit from the Dimension entity.
|
||||
let sub_color_is_byblock =
|
||||
sub.common().color == acadrust::types::Color::ByBlock;
|
||||
let has_book_color =
|
||||
view::render::has_resolved_book_color(document, sub);
|
||||
let sub_color_is_byblock = !has_book_color
|
||||
&& sub.common().color == acadrust::types::Color::ByBlock;
|
||||
let sub_is_l0_bylayer =
|
||||
view::render::is_effective_layer_zero(&sub.common().layer)
|
||||
!has_book_color
|
||||
&& view::render::is_effective_layer_zero(&sub.common().layer)
|
||||
&& sub.common().color == acadrust::types::Color::ByLayer;
|
||||
let sub_wires = tessellate_entity(
|
||||
document,
|
||||
@@ -898,10 +915,13 @@ pub(crate) fn tessellate_entity(
|
||||
let Some(sub) = document.get_entity(eh) else {
|
||||
continue;
|
||||
};
|
||||
let sub_color_is_byblock =
|
||||
sub.common().color == acadrust::types::Color::ByBlock;
|
||||
let has_book_color =
|
||||
view::render::has_resolved_book_color(document, sub);
|
||||
let sub_color_is_byblock = !has_book_color
|
||||
&& sub.common().color == acadrust::types::Color::ByBlock;
|
||||
let sub_is_l0_bylayer =
|
||||
view::render::is_effective_layer_zero(&sub.common().layer)
|
||||
!has_book_color
|
||||
&& view::render::is_effective_layer_zero(&sub.common().layer)
|
||||
&& sub.common().color == acadrust::types::Color::ByLayer;
|
||||
let mut placed = sub.clone();
|
||||
{
|
||||
@@ -976,6 +996,22 @@ pub(crate) fn tessellate_entity(
|
||||
line_weight_px,
|
||||
table_anno,
|
||||
);
|
||||
for insert in
|
||||
crate::entities::table::block_cell_inserts(tab, document, table_anno)
|
||||
{
|
||||
wires.extend(tessellate_entity(
|
||||
document,
|
||||
selected,
|
||||
active_viewport,
|
||||
bg_color,
|
||||
1.0,
|
||||
&EntityType::Insert(insert),
|
||||
block_cache,
|
||||
view_aabb,
|
||||
world_per_pixel,
|
||||
paper_space,
|
||||
));
|
||||
}
|
||||
if !wires.is_empty() {
|
||||
let aabb = entity_aabb(e);
|
||||
for w in &mut wires {
|
||||
|
||||
@@ -1723,7 +1723,8 @@ fn solid_wire_fallback(entity: &EntityType) -> Vec<[f64; 3]> {
|
||||
continue;
|
||||
}
|
||||
for v in &wire.points {
|
||||
pts.push([v.x, v.y, v.z]);
|
||||
let transformed = crate::entities::solid3d::wire_point(wire, v);
|
||||
pts.push([transformed.x, transformed.y, transformed.z]);
|
||||
}
|
||||
// NaN sentinel separates distinct wire segments.
|
||||
pts.push([f64::NAN, f64::NAN, f64::NAN]);
|
||||
|
||||
Reference in new issue
Block a user