fix: correct polyline arc direction, text precision, Face3D edge color, buffer chunking
- fix: polyline arc center sign was inverted (- → +) causing arcs to render on the wrong side in LwPolyline and Polyline2D - fix: store glyph strokes in local space with f64 origin so world_offset subtraction uses f64 precision; prevents blocky text at large UTM coordinates - fix: Face3D fill darkened to 45% so edge wires are visually distinct from fill - fix: wire batch vertex buffer chunked to 256 MB GPU limit (was panic on large files) - fix: gpu_face3d_edges changed from Option<WireGpu> to Vec<WireGpu> to support chunks Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
02203dba93
commit
1a19a841e6
11 changed files with 136 additions and 113 deletions
|
|
@ -5,7 +5,7 @@ use crate::command::EntityTransform;
|
|||
use crate::entities::common::{edit_prop as edit, ro_prop as ro, square_grip};
|
||||
use crate::entities::text_support::resolve_text_style;
|
||||
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
|
||||
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
|
||||
use crate::scene::acad_to_truck::{TextStroke, TruckEntity, TruckObject};
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection};
|
||||
use crate::scene::wire_model::SnapHint;
|
||||
use crate::scene::{cxf, transform};
|
||||
|
|
@ -26,8 +26,9 @@ impl TruckConvertible for AttributeDefinition {
|
|||
self.default_value.clone()
|
||||
};
|
||||
let wf = (self.width_factor as f32).max(0.01);
|
||||
let origin = [self.insertion_point.x, self.insertion_point.y];
|
||||
let strokes = cxf::tessellate_text_ex(
|
||||
[self.insertion_point.x as f32, self.insertion_point.y as f32],
|
||||
[0.0, 0.0],
|
||||
self.height as f32,
|
||||
self.rotation as f32,
|
||||
wf * resolved.width_factor.max(0.01),
|
||||
|
|
@ -36,7 +37,7 @@ impl TruckConvertible for AttributeDefinition {
|
|||
&display,
|
||||
);
|
||||
Some(TruckEntity {
|
||||
object: TruckObject::Text(strokes),
|
||||
object: TruckObject::Text(vec![TextStroke { strokes, origin }]),
|
||||
snap_pts: vec![(snap_pt, SnapHint::Insertion)],
|
||||
tangent_geoms: vec![],
|
||||
key_vertices: vec![],
|
||||
|
|
@ -128,8 +129,9 @@ impl TruckConvertible for AttributeEntity {
|
|||
);
|
||||
let resolved = resolve_text_style(&self.text_style, document);
|
||||
let wf = (self.width_factor as f32).max(0.01);
|
||||
let origin = [self.insertion_point.x, self.insertion_point.y];
|
||||
let strokes = cxf::tessellate_text_ex(
|
||||
[self.insertion_point.x as f32, self.insertion_point.y as f32],
|
||||
[0.0, 0.0],
|
||||
self.height as f32,
|
||||
self.rotation as f32,
|
||||
wf * resolved.width_factor.max(0.01),
|
||||
|
|
@ -138,7 +140,7 @@ impl TruckConvertible for AttributeEntity {
|
|||
&self.value,
|
||||
);
|
||||
Some(TruckEntity {
|
||||
object: TruckObject::Text(strokes),
|
||||
object: TruckObject::Text(vec![TextStroke { strokes, origin }]),
|
||||
snap_pts: vec![(snap_pt, SnapHint::Insertion)],
|
||||
tangent_geoms: vec![],
|
||||
key_vertices: vec![],
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ fn arc_midpoint(p0: [f64; 2], p1: [f64; 2], bulge: f64) -> [f64; 2] {
|
|||
let py = dx / d;
|
||||
let sign = if bulge > 0.0 { 1.0_f64 } else { -1.0_f64 };
|
||||
let h = r - (r * r - d * d / 4.0).max(0.0).sqrt();
|
||||
let cx = mx - sign * px * (r - h);
|
||||
let cy = my - sign * py * (r - h);
|
||||
let cx = mx + sign * px * (r - h);
|
||||
let cy = my + sign * py * (r - h);
|
||||
let a0 = (p0[1] - cy).atan2(p0[0] - cx);
|
||||
let a1 = (p1[1] - cy).atan2(p1[0] - cx);
|
||||
let (sa, mut ea) = if bulge > 0.0 { (a0, a1) } else { (a1, a0) };
|
||||
|
|
@ -112,8 +112,8 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
|
|||
let py = dx / len;
|
||||
let sagitta_sign = if bulge > 0.0 { 1.0_f64 } else { -1.0_f64 };
|
||||
let h = r - (r * r - d * d / 4.0).max(0.0).sqrt();
|
||||
let cx = mx - sagitta_sign * px * (r - h);
|
||||
let cy = my - sagitta_sign * py * (r - h);
|
||||
let cx = mx + sagitta_sign * px * (r - h);
|
||||
let cy = my + sagitta_sign * py * (r - h);
|
||||
let mid_a = {
|
||||
let a0 = (p0.y - cy).atan2(p0.x - cx);
|
||||
let a1 = (p1.y - cy).atan2(p1.x - cx);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ use crate::entities::text_support::{
|
|||
measure_mtext_chars, resolve_text_style, split_mtext_lines, strip_mtext_codes, word_wrap,
|
||||
};
|
||||
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
|
||||
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
|
||||
use crate::scene::acad_to_truck::{TextStroke, TruckEntity, TruckObject};
|
||||
use crate::scene::cxf;
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
||||
use crate::scene::wire_model::SnapHint;
|
||||
|
|
@ -122,26 +122,22 @@ fn to_truck(t: &MText, document: &acadrust::CadDocument) -> TruckEntity {
|
|||
let vertical_text = matches!(t.drawing_direction, DrawingDirection::TopToBottom);
|
||||
let rot = t.rotation as f32;
|
||||
let (cos_r, sin_r) = (rot.cos(), rot.sin());
|
||||
let insertion = Vec3::new(
|
||||
t.insertion_point.x as f32,
|
||||
t.insertion_point.y as f32,
|
||||
t.insertion_point.z as f32,
|
||||
);
|
||||
let mut all_strokes = Vec::new();
|
||||
let ins_x = t.insertion_point.x;
|
||||
let ins_y = t.insertion_point.y;
|
||||
let insertion = Vec3::new(ins_x as f32, ins_y as f32, t.insertion_point.z as f32);
|
||||
let mut all_strokes: Vec<TextStroke> = Vec::new();
|
||||
for (i, line) in lines.iter().enumerate() {
|
||||
let li = i as f32;
|
||||
let (ox, oy) = if vertical_text {
|
||||
// Compute line offset as f32 (small relative values), apply to f64 insertion point.
|
||||
let (small_x, small_y) = if vertical_text {
|
||||
let col_offset = li * t.height as f32 * 1.2;
|
||||
(
|
||||
t.insertion_point.x as f32 + col_offset * cos_r + v_offset * (-sin_r),
|
||||
t.insertion_point.y as f32 + col_offset * sin_r + v_offset * cos_r,
|
||||
col_offset * cos_r + v_offset * (-sin_r),
|
||||
col_offset * sin_r + v_offset * cos_r,
|
||||
)
|
||||
} else {
|
||||
let line_y = -(li * line_h) + v_offset;
|
||||
(
|
||||
t.insertion_point.x as f32 + line_y * (-sin_r),
|
||||
t.insertion_point.y as f32 + line_y * cos_r,
|
||||
)
|
||||
(line_y * (-sin_r), line_y * cos_r)
|
||||
};
|
||||
let line_w = if h_anchor > 0.0 {
|
||||
let scale = t.height as f32 / 9.0 * style_width_factor;
|
||||
|
|
@ -150,10 +146,12 @@ fn to_truck(t: &MText, document: &acadrust::CadDocument) -> TruckEntity {
|
|||
0.0
|
||||
};
|
||||
let h_shift = -line_w * h_anchor;
|
||||
let origin_x = ox + h_shift * cos_r;
|
||||
let origin_y = oy + h_shift * sin_r;
|
||||
let origin: [f64; 2] = [
|
||||
ins_x + (small_x + h_shift * cos_r) as f64,
|
||||
ins_y + (small_y + h_shift * sin_r) as f64,
|
||||
];
|
||||
let strokes = cxf::tessellate_text_ex(
|
||||
[origin_x, origin_y],
|
||||
[0.0, 0.0],
|
||||
t.height as f32,
|
||||
rot,
|
||||
style_width_factor,
|
||||
|
|
@ -161,7 +159,7 @@ fn to_truck(t: &MText, document: &acadrust::CadDocument) -> TruckEntity {
|
|||
&font_name,
|
||||
line,
|
||||
);
|
||||
all_strokes.extend(strokes);
|
||||
all_strokes.push(TextStroke { strokes, origin });
|
||||
}
|
||||
TruckEntity {
|
||||
object: TruckObject::Text(all_strokes),
|
||||
|
|
|
|||
|
|
@ -169,8 +169,8 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> TruckEntity {
|
|||
let py = dx / len;
|
||||
let sagitta_sign = if bulge > 0.0 { 1.0_f64 } else { -1.0_f64 };
|
||||
let h = r - (r * r - d * d / 4.0).max(0.0).sqrt();
|
||||
let cx = mx - sagitta_sign * px * (r - h);
|
||||
let cy = my - sagitta_sign * py * (r - h);
|
||||
let cx = mx + sagitta_sign * px * (r - h);
|
||||
let cy = my + sagitta_sign * py * (r - h);
|
||||
let mid_a = {
|
||||
let a0 = (p0.y - cy).atan2(p0.x - cx);
|
||||
let a1 = (p1.y - cy).atan2(p1.x - cx);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use crate::command::EntityTransform;
|
|||
use crate::entities::common::{edit_prop as edit, parse_f64, square_grip};
|
||||
use crate::entities::text_support::{resolve_dxf_special_chars, resolve_text_style, text_local_bounds};
|
||||
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
|
||||
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
|
||||
use crate::scene::acad_to_truck::{TextStroke, TruckEntity, TruckObject};
|
||||
use crate::scene::cxf;
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
||||
use crate::scene::wire_model::SnapHint;
|
||||
|
|
@ -101,14 +101,17 @@ fn to_truck(t: &Text, document: &acadrust::CadDocument) -> TruckEntity {
|
|||
} else {
|
||||
(0.0, 0.0)
|
||||
};
|
||||
let (cos_r, sin_r) = (rotation.cos(), rotation.sin());
|
||||
let origin = [
|
||||
anchor[0] - (anchor_local_x * cos_r - anchor_local_y * sin_r),
|
||||
anchor[1] - (anchor_local_x * sin_r + anchor_local_y * cos_r),
|
||||
let (cos_r, sin_r) = (rotation.cos() as f64, rotation.sin() as f64);
|
||||
// Keep origin as f64 — large coordinates (UTM etc.) must not be cast to
|
||||
// f32 here; world_offset subtraction happens later in tessellate.rs.
|
||||
let anchor_f64 = [anchor[0] as f64, anchor[1] as f64];
|
||||
let origin: [f64; 2] = [
|
||||
anchor_f64[0] - (anchor_local_x as f64 * cos_r - anchor_local_y as f64 * sin_r),
|
||||
anchor_f64[1] - (anchor_local_x as f64 * sin_r + anchor_local_y as f64 * cos_r),
|
||||
];
|
||||
// Pass raw value — tessellate_text_ex resolves %%x codes and emits decoration strokes.
|
||||
let strokes_2d = cxf::tessellate_text_ex(
|
||||
origin,
|
||||
// Strokes are in glyph-local space (origin = [0,0]).
|
||||
let strokes = cxf::tessellate_text_ex(
|
||||
[0.0, 0.0],
|
||||
t.height as f32,
|
||||
rotation,
|
||||
width_factor,
|
||||
|
|
@ -117,7 +120,7 @@ fn to_truck(t: &Text, document: &acadrust::CadDocument) -> TruckEntity {
|
|||
&t.value,
|
||||
);
|
||||
TruckEntity {
|
||||
object: TruckObject::Text(strokes_2d),
|
||||
object: TruckObject::Text(vec![TextStroke { strokes, origin }]),
|
||||
snap_pts: vec![(snap_pt, SnapHint::Insertion)],
|
||||
tangent_geoms: vec![],
|
||||
key_vertices: vec![],
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use glam::Vec3;
|
|||
use crate::command::EntityTransform;
|
||||
use crate::entities::common::{edit_prop as edit, ro_prop as ro, square_grip};
|
||||
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, TruckConvertible};
|
||||
use crate::scene::acad_to_truck::{TruckEntity, TruckObject};
|
||||
use crate::scene::acad_to_truck::{TextStroke, TruckEntity, TruckObject};
|
||||
use crate::scene::object::{GripApply, GripDef, PropSection};
|
||||
use crate::scene::wire_model::SnapHint;
|
||||
use crate::scene::{cxf, transform};
|
||||
|
|
@ -138,14 +138,13 @@ fn tessellate_tolerance(tol: &Tolerance) -> Vec<Vec<[f32; 2]>> {
|
|||
let total_w: f32 = col_widths.iter().sum();
|
||||
let total_h = cell_h * rows.len() as f32;
|
||||
|
||||
// ── Transform helpers (world 2-D, ignoring Z for Text path) ──────────
|
||||
let ox = tol.insertion_point.x as f32;
|
||||
let oy = tol.insertion_point.y as f32;
|
||||
// ── Transform helpers (local space — translation applied in tessellate.rs) ──
|
||||
let angle = (tol.direction.y as f32).atan2(tol.direction.x as f32);
|
||||
let (sa, ca) = angle.sin_cos();
|
||||
|
||||
// Rotate only; origin is kept as f64 and applied later with full precision.
|
||||
let rot = |x: f32, y: f32| -> [f32; 2] {
|
||||
[ox + x * ca - y * sa, oy + x * sa + y * ca]
|
||||
[x * ca - y * sa, x * sa + y * ca]
|
||||
};
|
||||
|
||||
let mut out: Vec<Vec<[f32; 2]>> = Vec::new();
|
||||
|
|
@ -228,12 +227,12 @@ impl TruckConvertible for Tolerance {
|
|||
self.insertion_point.z as f32,
|
||||
);
|
||||
|
||||
// Build the feature-control frame: box outline + cell text, all
|
||||
// represented as 2-D CXF polylines (TruckObject::Text).
|
||||
let polylines = tessellate_tolerance(self);
|
||||
// Build the feature-control frame in local space; origin stored as f64.
|
||||
let strokes = tessellate_tolerance(self);
|
||||
let origin = [self.insertion_point.x, self.insertion_point.y];
|
||||
|
||||
Some(TruckEntity {
|
||||
object: TruckObject::Text(polylines),
|
||||
object: TruckObject::Text(vec![TextStroke { strokes, origin }]),
|
||||
snap_pts: vec![(snap_pt, SnapHint::Insertion)],
|
||||
tangent_geoms: vec![],
|
||||
key_vertices: vec![],
|
||||
|
|
|
|||
|
|
@ -7,12 +7,20 @@ use truck_modeling::{Edge, Solid, Vertex, Wire};
|
|||
use crate::entities::traits::EntityTypeOps;
|
||||
use crate::scene::wire_model::{SnapHint, TangentGeom};
|
||||
|
||||
/// One group of glyph strokes with its world-space origin stored in f64.
|
||||
/// Strokes are in glyph-local space (origin = [0,0]) so that the large
|
||||
/// world offset can be subtracted with f64 precision in tessellate.rs.
|
||||
pub struct TextStroke {
|
||||
pub strokes: Vec<Vec<[f32; 2]>>,
|
||||
pub origin: [f64; 2],
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum TruckObject {
|
||||
Point(Vertex),
|
||||
Curve(Edge),
|
||||
Contour(Wire),
|
||||
Text(Vec<Vec<[f32; 2]>>),
|
||||
Text(Vec<TextStroke>),
|
||||
/// Pre-computed NaN-separated 3-D point list (leader lines, arrowheads, etc.).
|
||||
Lines(Vec<[f32; 3]>),
|
||||
Volume(Solid),
|
||||
|
|
|
|||
|
|
@ -65,9 +65,10 @@ impl Face3DGpu {
|
|||
if wire.key_vertices.len() < 4 {
|
||||
continue;
|
||||
}
|
||||
let color = wire.color;
|
||||
let [r, g, b, a] = wire.color;
|
||||
let fill_color = [r * 0.45, g * 0.45, b * 0.45, a];
|
||||
let p = &wire.key_vertices;
|
||||
let v = |i: usize| Face3DVertex { position: p[i], color };
|
||||
let v = |i: usize| Face3DVertex { position: p[i], color: fill_color };
|
||||
|
||||
// Triangle 1: p0, p1, p2
|
||||
vertices.push(v(0));
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ pub struct Pipeline {
|
|||
gpu_meshes: Vec<MeshGpu>,
|
||||
/// Batched 3DFACE fill (all faces in one buffer) and edges (merged wire).
|
||||
gpu_face3d_fill: Option<Face3DGpu>,
|
||||
gpu_face3d_edges: Option<WireGpu>,
|
||||
gpu_face3d_edges: Vec<WireGpu>,
|
||||
pub viewcube: ViewCubePipeline,
|
||||
/// Last geometry epoch for which GPU buffers were uploaded.
|
||||
/// Initialized to u64::MAX so the first frame always uploads.
|
||||
|
|
@ -590,7 +590,7 @@ impl Pipeline {
|
|||
gpu_images: vec![],
|
||||
gpu_meshes: vec![],
|
||||
gpu_face3d_fill: None,
|
||||
gpu_face3d_edges: None,
|
||||
gpu_face3d_edges: vec![],
|
||||
viewcube,
|
||||
cached_epoch: u64::MAX,
|
||||
}
|
||||
|
|
@ -613,7 +613,7 @@ impl Pipeline {
|
|||
pub fn upload_face3d(&mut self, device: &wgpu::Device, face3d_wires: &[WireModel]) {
|
||||
if face3d_wires.is_empty() {
|
||||
self.gpu_face3d_fill = None;
|
||||
self.gpu_face3d_edges = None;
|
||||
self.gpu_face3d_edges = vec![];
|
||||
return;
|
||||
}
|
||||
self.gpu_face3d_fill = Some(Face3DGpu::from_wires(device, face3d_wires));
|
||||
|
|
@ -813,36 +813,38 @@ impl Pipeline {
|
|||
}
|
||||
}
|
||||
|
||||
// ── Pass 5b: 3DFACE edges (batched) ──────────────────────────────
|
||||
if let Some(ref edges) = self.gpu_face3d_edges {
|
||||
if edges.vertex_count >= 6 {
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("face3d_edges.render_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: msaa,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
stencil_ops: None,
|
||||
// ── Pass 5b: 3DFACE edges (batched, possibly multiple chunks) ────
|
||||
if !self.gpu_face3d_edges.is_empty() {
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("face3d_edges.render_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: msaa,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
|
||||
view: &self.depth_view,
|
||||
depth_ops: Some(wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Store,
|
||||
}),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
pass.set_viewport(0.0, 0.0, vp.width as f32, vp.height as f32, 0.0, 1.0);
|
||||
pass.set_pipeline(&self.wire_pipeline);
|
||||
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||
pass.set_vertex_buffer(0, edges.vertex_buffer.slice(..));
|
||||
pass.draw(0..edges.vertex_count, 0..1);
|
||||
stencil_ops: None,
|
||||
}),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
pass.set_viewport(0.0, 0.0, vp.width as f32, vp.height as f32, 0.0, 1.0);
|
||||
pass.set_pipeline(&self.wire_pipeline);
|
||||
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||
for edges in &self.gpu_face3d_edges {
|
||||
if edges.vertex_count >= 6 {
|
||||
pass.set_vertex_buffer(0, edges.vertex_buffer.slice(..));
|
||||
pass.draw(0..edges.vertex_count, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -125,13 +125,13 @@ impl WireGpu {
|
|||
Self::build(device, wire, [r, g, b, a * alpha])
|
||||
}
|
||||
|
||||
/// Merge multiple WireModels into a single GPU buffer (1 draw call for all).
|
||||
/// Merge multiple WireModels into GPU buffers chunked to fit the 256 MB GPU limit.
|
||||
/// Each wire keeps its own color and pattern — they're stored per-vertex.
|
||||
/// Returns None if the combined vertex list is empty.
|
||||
pub fn from_batch(device: &wgpu::Device, wires: &[WireModel]) -> Option<Self> {
|
||||
/// Returns an empty Vec if the combined vertex list is empty.
|
||||
pub fn from_batch(device: &wgpu::Device, wires: &[WireModel]) -> Vec<Self> {
|
||||
let total_segs: usize = wires.iter().map(|w| w.points.len().saturating_sub(1)).sum();
|
||||
if total_segs == 0 {
|
||||
return None;
|
||||
return vec![];
|
||||
}
|
||||
let mut vertices: Vec<WireVertex> = Vec::with_capacity(total_segs * 6);
|
||||
|
||||
|
|
@ -186,19 +186,27 @@ impl WireGpu {
|
|||
}
|
||||
|
||||
if vertices.is_empty() {
|
||||
return None;
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("wire.batch.vbuf"),
|
||||
contents: bytemuck::cast_slice(&vertices),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
// GPU max buffer size is 256 MB; chunk to stay within the limit.
|
||||
const MAX_VERTS: usize = 268_435_456 / std::mem::size_of::<WireVertex>();
|
||||
|
||||
Some(Self {
|
||||
vertex_buffer,
|
||||
vertex_count: vertices.len() as u32,
|
||||
})
|
||||
vertices
|
||||
.chunks(MAX_VERTS)
|
||||
.enumerate()
|
||||
.map(|(i, chunk)| {
|
||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some(&format!("wire.batch.vbuf.{i}")),
|
||||
contents: bytemuck::cast_slice(chunk),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
Self {
|
||||
vertex_buffer,
|
||||
vertex_count: chunk.len() as u32,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn build(device: &wgpu::Device, wire: &WireModel, color: [f32; 4]) -> Self {
|
||||
|
|
|
|||
|
|
@ -64,27 +64,29 @@ pub fn tessellate(
|
|||
if let Some(te) = convert(entity, document) {
|
||||
match te.object {
|
||||
// ── Text / MText: pre-tessellated glyph strokes ───────────────
|
||||
TruckObject::Text(strokes_2d) => {
|
||||
// Glyph strokes come from acad_to_truck in world-space f32.
|
||||
// Subtract world_offset (f32 subtraction — acceptable precision
|
||||
// for text rendering; the anchor was already cast to f32 there).
|
||||
TruckObject::Text(stroke_groups) => {
|
||||
// Each TextStroke keeps its strokes in glyph-local space and
|
||||
// its world origin as f64. Subtract world_offset in f64 before
|
||||
// casting to f32 so large UTM coordinates don't crush precision.
|
||||
let [ox, oy, oz] = world_offset;
|
||||
let elev = entity_z(entity) - oz as f32;
|
||||
|
||||
// Pack all strokes into one flat point list, separated by
|
||||
// NaN sentinels so wire_gpu.rs skips disconnected segments.
|
||||
let mut points: Vec<[f32; 3]> = Vec::new();
|
||||
for (i, stroke) in strokes_2d.iter().enumerate() {
|
||||
if stroke.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
if i > 0 && !points.is_empty() {
|
||||
// NaN sentinel — wire_gpu skips any segment where
|
||||
// either endpoint contains NaN.
|
||||
points.push([f32::NAN, f32::NAN, f32::NAN]);
|
||||
}
|
||||
for &[x, y] in stroke {
|
||||
points.push([x - ox as f32, y - oy as f32, elev]);
|
||||
let mut first = true;
|
||||
for group in &stroke_groups {
|
||||
let lx = (group.origin[0] - ox) as f32;
|
||||
let ly = (group.origin[1] - oy) as f32;
|
||||
for stroke in &group.strokes {
|
||||
if stroke.len() < 2 {
|
||||
continue;
|
||||
}
|
||||
if !first && !points.is_empty() {
|
||||
points.push([f32::NAN, f32::NAN, f32::NAN]);
|
||||
}
|
||||
first = false;
|
||||
for &[x, y] in stroke {
|
||||
points.push([x + lx, y + ly, elev]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue