forked from 1b-agent/opencadstudio-upstream
Extend GPU analytical circle and arc rendering to block inserts
- Emit transformed analytical curves (circles, arcs, ellipses) in block expansion as standalone wires rather than merging them into batched line strips. - Keep render_instance unset on analytical curves in block prototypes so they route directly to CircleGpu / EllipseGpu instead of BlockWireGpu. - Support TangentGeom::Circle in extract_circle_instance and wire partitioning. - Drop analytical tangents on wires clipped by xclip so clipped geometry falls back correctly to line segments. - Add unit test verifying block circles and arcs extract as analytical GPU instances.
This commit is contained in:
1 parent
690971c44e
commit
73cd759ca4
5 files changed
+202
-11
No files matched your search
Vendored
+146
-9
@@ -1108,7 +1108,7 @@ pub fn expand_insert(
|
|||||||
);
|
);
|
||||||
let mut first = first_batches.finalize(&name, selected, bg_color);
|
let mut first = first_batches.finalize(&name, selected, bg_color);
|
||||||
for wire in &mut first {
|
for wire in &mut first {
|
||||||
if wire.render_instance.is_none() {
|
if !is_standalone_analytical_curve(wire) && wire.render_instance.is_none() {
|
||||||
wire.render_instance = Some(
|
wire.render_instance = Some(
|
||||||
crate::scene::model::instance_model::RenderInstance {
|
crate::scene::model::instance_model::RenderInstance {
|
||||||
source_id: crate::scene::model::instance_model::next_source_id(),
|
source_id: crate::scene::model::instance_model::next_source_id(),
|
||||||
@@ -1149,7 +1149,7 @@ pub fn expand_insert(
|
|||||||
if let Some(guard) = prototype_guard.as_mut() {
|
if let Some(guard) = prototype_guard.as_mut() {
|
||||||
let translation = transform_translation(&xform);
|
let translation = transform_translation(&xform);
|
||||||
for wire in &mut wires {
|
for wire in &mut wires {
|
||||||
if wire.render_instance.is_none() {
|
if !is_standalone_analytical_curve(wire) && wire.render_instance.is_none() {
|
||||||
wire.render_instance = Some(crate::scene::model::instance_model::RenderInstance {
|
wire.render_instance = Some(crate::scene::model::instance_model::RenderInstance {
|
||||||
source_id: crate::scene::model::instance_model::next_source_id(),
|
source_id: crate::scene::model::instance_model::next_source_id(),
|
||||||
translation,
|
translation,
|
||||||
@@ -1314,6 +1314,20 @@ fn aabb_pixel_size(local_aabb: [f32; 4], world_per_pixel: f32) -> f32 {
|
|||||||
w.max(h) / world_per_pixel
|
w.max(h) / world_per_pixel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_standalone_analytical_curve(wire: &WireModel) -> bool {
|
||||||
|
wire.tangent_geoms.len() == 1
|
||||||
|
&& wire.fill_tris.is_empty()
|
||||||
|
&& wire.pick_tris.is_empty()
|
||||||
|
&& wire.text_verts.is_empty()
|
||||||
|
&& matches!(
|
||||||
|
wire.tangent_geoms[0],
|
||||||
|
TangentGeom::Circle { .. }
|
||||||
|
| TangentGeom::PlanarCircle { .. }
|
||||||
|
| TangentGeom::Arc { .. }
|
||||||
|
| TangentGeom::PlanarEllipse { .. }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
struct ExpandCtx<'a> {
|
struct ExpandCtx<'a> {
|
||||||
cache: &'a BlockCache,
|
cache: &'a BlockCache,
|
||||||
ins_color: [f32; 4],
|
ins_color: [f32; 4],
|
||||||
@@ -1931,12 +1945,14 @@ fn expand_defn(
|
|||||||
.collect();
|
.collect();
|
||||||
crate::scene::pick::xclip::clip_wires(&mut wires, &world_poly);
|
crate::scene::pick::xclip::clip_wires(&mut wires, &world_poly);
|
||||||
for wire in &mut wires {
|
for wire in &mut wires {
|
||||||
wire.render_instance = Some(
|
if !is_standalone_analytical_curve(wire) {
|
||||||
crate::scene::model::instance_model::RenderInstance {
|
wire.render_instance = Some(
|
||||||
source_id: crate::scene::model::instance_model::next_source_id(),
|
crate::scene::model::instance_model::RenderInstance {
|
||||||
translation,
|
source_id: crate::scene::model::instance_model::next_source_id(),
|
||||||
},
|
translation,
|
||||||
);
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
out.extra_wires.extend(wires.iter().cloned());
|
out.extra_wires.extend(wires.iter().cloned());
|
||||||
first = Some((wires, translation));
|
first = Some((wires, translation));
|
||||||
@@ -1984,7 +2000,7 @@ fn expand_defn(
|
|||||||
);
|
);
|
||||||
let mut wires = sub.finalize("", ctx.selected, ctx.bg_color);
|
let mut wires = sub.finalize("", ctx.selected, ctx.bg_color);
|
||||||
for wire in &mut wires {
|
for wire in &mut wires {
|
||||||
if wire.render_instance.is_none() {
|
if !is_standalone_analytical_curve(wire) && wire.render_instance.is_none() {
|
||||||
wire.render_instance = Some(
|
wire.render_instance = Some(
|
||||||
crate::scene::model::instance_model::RenderInstance {
|
crate::scene::model::instance_model::RenderInstance {
|
||||||
source_id: crate::scene::model::instance_model::next_source_id(),
|
source_id: crate::scene::model::instance_model::next_source_id(),
|
||||||
@@ -2297,6 +2313,127 @@ fn emit_wire(
|
|||||||
lw.line_weight_px
|
lw.line_weight_px
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Analytical GPU circle / arc / ellipse fast path:
|
||||||
|
// If this LocalWire is a pure analytical curve (single tangent geometry, no fills/text/pick),
|
||||||
|
// and transforms cleanly with accum_xform into an analytical curve, emit it as a standalone
|
||||||
|
// wire in extra_wires. This lets partition_wires extract it into CircleGpu / EllipseGpu for
|
||||||
|
// pixel-perfect analytical rendering on the GPU instead of segmented lines.
|
||||||
|
if lw.tangent_geoms.len() == 1
|
||||||
|
&& lw.fill_tris.is_empty()
|
||||||
|
&& lw.pick_tris.is_empty()
|
||||||
|
&& lw.text_verts.is_empty()
|
||||||
|
{
|
||||||
|
if let Some(tangent) = transform_tangent(&lw.tangent_geoms[0], accum_xform) {
|
||||||
|
if matches!(
|
||||||
|
tangent,
|
||||||
|
TangentGeom::Circle { .. }
|
||||||
|
| TangentGeom::PlanarCircle { .. }
|
||||||
|
| TangentGeom::Arc { .. }
|
||||||
|
| TangentGeom::PlanarEllipse { .. }
|
||||||
|
) {
|
||||||
|
let contrast_bg = lw.contrast_bg.unwrap_or(ctx.bg_color);
|
||||||
|
let color = if lw.canvas_color {
|
||||||
|
ctx.bg_color
|
||||||
|
} else if lw.preserve_color {
|
||||||
|
final_color
|
||||||
|
} else {
|
||||||
|
crate::scene::view::render::adapt_to_bg(final_color, contrast_bg)
|
||||||
|
};
|
||||||
|
let bg_adapt: crate::scene::model::wire_model::BgAdapt =
|
||||||
|
(lw.canvas_color || !lw.preserve_color).then(|| {
|
||||||
|
Box::new(crate::scene::model::wire_model::BgAdaptInputs {
|
||||||
|
raw_color: final_color,
|
||||||
|
text_raw_colors: Vec::new(),
|
||||||
|
contrast_bg: lw.contrast_bg,
|
||||||
|
canvas_color: lw.canvas_color,
|
||||||
|
preserve_color: lw.preserve_color,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut points = Vec::with_capacity(lw.points.len());
|
||||||
|
let mut points_low = Vec::with_capacity(lw.points.len());
|
||||||
|
let mut min_x = f32::INFINITY;
|
||||||
|
let mut min_y = f32::INFINITY;
|
||||||
|
let mut max_x = f32::NEG_INFINITY;
|
||||||
|
let mut max_y = f32::NEG_INFINITY;
|
||||||
|
for (idx, p) in lw.points.iter().enumerate() {
|
||||||
|
let pl = lw.points_low.get(idx).copied().unwrap_or([0.0; 3]);
|
||||||
|
let point = accum_xform.apply(Vector3::new(
|
||||||
|
p[0] as f64 + pl[0] as f64,
|
||||||
|
p[1] as f64 + pl[1] as f64,
|
||||||
|
p[2] as f64 + pl[2] as f64,
|
||||||
|
));
|
||||||
|
let (hx, lx) = WireModel::split_ds(point.x);
|
||||||
|
let (hy, ly) = WireModel::split_ds(point.y);
|
||||||
|
let (hz, lz) = WireModel::split_ds(point.z);
|
||||||
|
if hx < min_x { min_x = hx; }
|
||||||
|
if hy < min_y { min_y = hy; }
|
||||||
|
if hx > max_x { max_x = hx; }
|
||||||
|
if hy > max_y { max_y = hy; }
|
||||||
|
points.push([hx, hy, hz]);
|
||||||
|
points_low.push([lx, ly, lz]);
|
||||||
|
}
|
||||||
|
let aabb = if min_x.is_infinite() {
|
||||||
|
WireModel::UNBOUNDED_AABB
|
||||||
|
} else {
|
||||||
|
[min_x, min_y, max_x, max_y]
|
||||||
|
};
|
||||||
|
let mut snap_pts = Vec::with_capacity(lw.snap_pts.len());
|
||||||
|
for (p, hint) in &lw.snap_pts {
|
||||||
|
let v = accum_xform.apply(Vector3::new(p.x, p.y, p.z));
|
||||||
|
snap_pts.push((glam::DVec3::new(v.x, v.y, v.z), *hint));
|
||||||
|
}
|
||||||
|
let mut key_vertices = Vec::with_capacity(lw.key_vertices.len());
|
||||||
|
for p in &lw.key_vertices {
|
||||||
|
let v = accum_xform.apply(Vector3::new(p[0], p[1], p[2]));
|
||||||
|
key_vertices.push([v.x, v.y, v.z]);
|
||||||
|
}
|
||||||
|
let local_depth = if d_range != (0.0, 1.0) {
|
||||||
|
Some(d_range.0)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let wire = WireModel {
|
||||||
|
bg_adapt,
|
||||||
|
point_marker: lw.point_marker,
|
||||||
|
taper_widths: Vec::new(),
|
||||||
|
pattern_stations: Vec::new(),
|
||||||
|
world_width: 0.0,
|
||||||
|
depth_override: local_depth,
|
||||||
|
display_visible: !lw.hide_unselected || ctx.selected,
|
||||||
|
plot_visible: lw.plot_visible,
|
||||||
|
fill_is_3d: false,
|
||||||
|
fill_is_2d_solid: false,
|
||||||
|
render_instance: None,
|
||||||
|
pick_tris: Vec::new(),
|
||||||
|
pick_tris_low: Vec::new(),
|
||||||
|
dash_from_start: false,
|
||||||
|
dash_align_end: None,
|
||||||
|
text_verts: Vec::new(),
|
||||||
|
name: String::new(),
|
||||||
|
points,
|
||||||
|
points_low,
|
||||||
|
color,
|
||||||
|
selected: ctx.selected,
|
||||||
|
pattern_length: final_pat_len,
|
||||||
|
pattern: final_pat,
|
||||||
|
line_weight_px: final_lw_px,
|
||||||
|
aci: final_aci,
|
||||||
|
snap_pts,
|
||||||
|
tangent_geoms: vec![tangent],
|
||||||
|
key_vertices,
|
||||||
|
aabb,
|
||||||
|
plinegen: lw.plinegen,
|
||||||
|
fill_tris: Vec::new(),
|
||||||
|
fill_tris_low: Vec::new(),
|
||||||
|
};
|
||||||
|
out.extra_wires.push(wire);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let station_values = pattern_station_values(&lw.pattern_stations, lw.points.len());
|
let station_values = pattern_station_values(&lw.pattern_stations, lw.points.len());
|
||||||
let station_map = decode_pattern_station_map(&lw.pattern_stations, lw.points.len());
|
let station_map = decode_pattern_station_map(&lw.pattern_stations, lw.points.len());
|
||||||
let transformed_stations = station_values.and_then(|(values, _)| {
|
let transformed_stations = station_values.and_then(|(values, _)| {
|
||||||
|
|||||||
@@ -11284,4 +11284,45 @@ mod layout_cache_tests {
|
|||||||
| crate::scene::model::wire_model::TangentGeom::Circle { .. }
|
| crate::scene::model::wire_model::TangentGeom::Circle { .. }
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn block_circles_and_arcs_extract_as_analytical_gpu_instances() {
|
||||||
|
let mut s = Scene::new();
|
||||||
|
// Create initial entities
|
||||||
|
let mut circle = acadrust::entities::Circle::default();
|
||||||
|
circle.radius = 50.0;
|
||||||
|
let c_h = s.add_entity(EntityType::Circle(circle));
|
||||||
|
|
||||||
|
let mut arc = acadrust::entities::Arc::default();
|
||||||
|
arc.radius = 25.0;
|
||||||
|
arc.start_angle = 0.0;
|
||||||
|
arc.end_angle = 3.14159;
|
||||||
|
let a_h = s.add_entity(EntityType::Arc(arc));
|
||||||
|
|
||||||
|
let line = acadrust::entities::Line::from_points(
|
||||||
|
acadrust::types::Vector3::new(0.0, 0.0, 0.0),
|
||||||
|
acadrust::types::Vector3::new(100.0, 100.0, 0.0),
|
||||||
|
);
|
||||||
|
let l_h = s.add_entity(EntityType::Line(line));
|
||||||
|
|
||||||
|
// Create block and place insert at (200, 300, 0)
|
||||||
|
let xform = acadrust::types::Transform::from_translation(acadrust::types::Vector3::new(200.0, 300.0, 0.0));
|
||||||
|
let _ = s.create_block_from_entities(
|
||||||
|
&[c_h, a_h, l_h],
|
||||||
|
"TEST_ANALYTICAL_BLOCK",
|
||||||
|
&acadrust::types::Transform::identity(),
|
||||||
|
&xform,
|
||||||
|
).unwrap();
|
||||||
|
|
||||||
|
let wires = s.model_tile_wires_arc(0, &Camera::default(), 1.0, 1000.0);
|
||||||
|
let depths = rustc_hash::FxHashMap::default();
|
||||||
|
let partitioned = crate::scene::pipeline::wire_arena::partition_wires(&wires, &depths);
|
||||||
|
|
||||||
|
// Both the circle and the arc inside the block must be extracted as analytical GPU circle instances!
|
||||||
|
assert!(
|
||||||
|
partitioned.circle_instances.len() >= 2,
|
||||||
|
"Expected at least 2 analytical circle instances from block, got {}",
|
||||||
|
partitioned.circle_instances.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -197,6 +197,9 @@ pub fn clip_wires(wires: &mut Vec<WireModel>, poly: &[[f64; 2]]) {
|
|||||||
} else {
|
} else {
|
||||||
w.pattern_stations.clear();
|
w.pattern_stations.clear();
|
||||||
}
|
}
|
||||||
|
if !w.tangent_geoms.is_empty() {
|
||||||
|
w.tangent_geoms.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !w.fill_tris.is_empty() {
|
if !w.fill_tris.is_empty() {
|
||||||
let local: Vec<[f32; 3]> = (0..w.fill_tris.len())
|
let local: Vec<[f32; 3]> = (0..w.fill_tris.len())
|
||||||
|
|||||||
@@ -215,6 +215,14 @@ pub fn extract_circle_instance(
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let (center, axis_x, axis_y, radius, start_angle, end_angle) = match wire.tangent_geoms[0] {
|
let (center, axis_x, axis_y, radius, start_angle, end_angle) = match wire.tangent_geoms[0] {
|
||||||
|
crate::scene::model::wire_model::TangentGeom::Circle { center, radius } => (
|
||||||
|
[center[0] as f64, center[1] as f64, center[2] as f64],
|
||||||
|
[1.0, 0.0, 0.0],
|
||||||
|
[0.0, 1.0, 0.0],
|
||||||
|
radius as f64,
|
||||||
|
0.0f32,
|
||||||
|
std::f32::consts::TAU,
|
||||||
|
),
|
||||||
crate::scene::model::wire_model::TangentGeom::PlanarCircle {
|
crate::scene::model::wire_model::TangentGeom::PlanarCircle {
|
||||||
center,
|
center,
|
||||||
axis_x,
|
axis_x,
|
||||||
|
|||||||
@@ -150,7 +150,8 @@ pub fn classify_wire(wire: &WireModel) -> WireKind {
|
|||||||
&& wire.text_verts.is_empty()
|
&& wire.text_verts.is_empty()
|
||||||
{
|
{
|
||||||
match &wire.tangent_geoms[0] {
|
match &wire.tangent_geoms[0] {
|
||||||
crate::scene::model::wire_model::TangentGeom::PlanarCircle { .. }
|
crate::scene::model::wire_model::TangentGeom::Circle { .. }
|
||||||
|
| crate::scene::model::wire_model::TangentGeom::PlanarCircle { .. }
|
||||||
| crate::scene::model::wire_model::TangentGeom::Arc { .. } => {
|
| crate::scene::model::wire_model::TangentGeom::Arc { .. } => {
|
||||||
if super::circle_gpu::extract_circle_instance(wire, 0.0).is_some() {
|
if super::circle_gpu::extract_circle_instance(wire, 0.0).is_some() {
|
||||||
return WireKind::Circle;
|
return WireKind::Circle;
|
||||||
@@ -214,7 +215,8 @@ pub fn partition_wires<'a>(
|
|||||||
&& wire.text_verts.is_empty()
|
&& wire.text_verts.is_empty()
|
||||||
{
|
{
|
||||||
match &wire.tangent_geoms[0] {
|
match &wire.tangent_geoms[0] {
|
||||||
crate::scene::model::wire_model::TangentGeom::PlanarCircle { .. }
|
crate::scene::model::wire_model::TangentGeom::Circle { .. }
|
||||||
|
| crate::scene::model::wire_model::TangentGeom::PlanarCircle { .. }
|
||||||
| crate::scene::model::wire_model::TangentGeom::Arc { .. } => {
|
| crate::scene::model::wire_model::TangentGeom::Arc { .. } => {
|
||||||
if let Some(inst) = super::circle_gpu::extract_circle_instance(wire, depth) {
|
if let Some(inst) = super::circle_gpu::extract_circle_instance(wire, depth) {
|
||||||
circle_instances.push(inst);
|
circle_instances.push(inst);
|
||||||
|
|||||||
Reference in new issue
Block a user