forked from 1b-agent/opencadstudio-upstream
feat(render): GPU analytical curve extraction for multi-arc polylines and blocks
This commit is contained in:
1 parent
6e01d464e5
commit
75ec08dc55
6 files changed
+186
-88
No files matched your search
Vendored
+17
-10
@@ -2318,19 +2318,26 @@ fn emit_wire(
|
||||
// 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
|
||||
if !lw.tangent_geoms.is_empty()
|
||||
&& 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 transformed_tangents: Option<Vec<TangentGeom>> = lw
|
||||
.tangent_geoms
|
||||
.iter()
|
||||
.map(|tg| transform_tangent(tg, accum_xform))
|
||||
.collect();
|
||||
if let Some(tangents) = transformed_tangents {
|
||||
if tangents.iter().all(|tangent| {
|
||||
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
|
||||
@@ -2421,7 +2428,7 @@ fn emit_wire(
|
||||
line_weight_px: final_lw_px,
|
||||
aci: final_aci,
|
||||
snap_pts,
|
||||
tangent_geoms: vec![tangent],
|
||||
tangent_geoms: tangents,
|
||||
key_vertices,
|
||||
aabb,
|
||||
plinegen: lw.plinegen,
|
||||
|
||||
@@ -11358,4 +11358,44 @@ mod layout_cache_tests {
|
||||
"Single bulge arc polyline must be extracted as analytical GPU arc instance"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_bulge_polyline_extracts_as_analytical_gpu_arcs() {
|
||||
let mut s = Scene::new();
|
||||
let mut pline = acadrust::entities::LwPolyline::new();
|
||||
// A circle represented as a closed 2-vertex polyline with two semicircle bulges (standard CAD polyline circle)
|
||||
pline.is_closed = true;
|
||||
pline.vertices = vec![
|
||||
acadrust::entities::LwVertex {
|
||||
location: acadrust::types::Vector2::new(0.0, 0.0),
|
||||
bulge: 1.0,
|
||||
start_width: 0.0,
|
||||
end_width: 0.0,
|
||||
vertex_id: 0,
|
||||
},
|
||||
acadrust::entities::LwVertex {
|
||||
location: acadrust::types::Vector2::new(100.0, 0.0),
|
||||
bulge: 1.0,
|
||||
start_width: 0.0,
|
||||
end_width: 0.0,
|
||||
vertex_id: 1,
|
||||
},
|
||||
];
|
||||
let _ = s.add_entity(EntityType::LwPolyline(pline));
|
||||
|
||||
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);
|
||||
|
||||
assert_eq!(
|
||||
partitioned.circle_instances.len(),
|
||||
2,
|
||||
"Multi-bulge closed polyline circle must be extracted as 2 analytical GPU arc instances"
|
||||
);
|
||||
assert_eq!(
|
||||
partitioned.regular.len(),
|
||||
0,
|
||||
"Multi-bulge pure arc polyline must not be sent to regular line arena"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -199,22 +199,13 @@ pub fn create_pipelines(
|
||||
(circle_pipeline, circle_xray_pipeline)
|
||||
}
|
||||
|
||||
/// Helper to extract an analytical circle or circular arc instance from a `WireModel`.
|
||||
pub fn extract_circle_instance(
|
||||
/// Helper to extract an analytical circle or circular arc instance from a single `TangentGeom`.
|
||||
pub fn extract_circle_instance_from_geom(
|
||||
geom: &crate::scene::model::wire_model::TangentGeom,
|
||||
wire: &crate::scene::WireModel,
|
||||
draw_depth: f32,
|
||||
) -> Option<CircleInstance> {
|
||||
// Only thin 2D planar circles or circular arcs have exactly 1 PlanarCircle / Arc tangent geometry
|
||||
// and no 3D mesh fills or text quads.
|
||||
if wire.tangent_geoms.len() != 1
|
||||
|| !wire.fill_tris.is_empty()
|
||||
|| !wire.pick_tris.is_empty()
|
||||
|| !wire.text_verts.is_empty()
|
||||
|| wire.render_instance.is_some()
|
||||
{
|
||||
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 *geom {
|
||||
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],
|
||||
@@ -271,6 +262,46 @@ pub fn extract_circle_instance(
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper to extract all analytical circle/arc instances from a `WireModel`.
|
||||
///
|
||||
/// Returns `Some(instances)` if the wire is non-empty, contains solely analytical
|
||||
/// circle/arc tangent geometries, and has no mesh/pick fills, text, or block instance.
|
||||
pub fn extract_circle_instances(
|
||||
wire: &crate::scene::WireModel,
|
||||
draw_depth: f32,
|
||||
) -> Option<Vec<CircleInstance>> {
|
||||
if wire.tangent_geoms.is_empty()
|
||||
|| !wire.fill_tris.is_empty()
|
||||
|| !wire.pick_tris.is_empty()
|
||||
|| !wire.text_verts.is_empty()
|
||||
|| wire.render_instance.is_some()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut instances = Vec::with_capacity(wire.tangent_geoms.len());
|
||||
for geom in &wire.tangent_geoms {
|
||||
if let Some(inst) = extract_circle_instance_from_geom(geom, wire, draw_depth) {
|
||||
instances.push(inst);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(instances)
|
||||
}
|
||||
|
||||
/// Helper to extract a single analytical circle or circular arc instance from a `WireModel`.
|
||||
pub fn extract_circle_instance(
|
||||
wire: &crate::scene::WireModel,
|
||||
draw_depth: f32,
|
||||
) -> Option<CircleInstance> {
|
||||
if wire.tangent_geoms.len() == 1 {
|
||||
extract_circle_instances(wire, draw_depth).and_then(|mut v| v.pop())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn split_ds_xyz(x: f64, y: f64, z: f64) -> ([f32; 3], [f32; 3]) {
|
||||
let hx = x as f32;
|
||||
let lx = (x - hx as f64) as f32;
|
||||
|
||||
@@ -199,19 +199,12 @@ pub fn create_pipelines(
|
||||
(ellipse_pipeline, ellipse_xray_pipeline)
|
||||
}
|
||||
|
||||
/// Helper to extract an analytical ellipse instance from a `WireModel`.
|
||||
pub fn extract_ellipse_instance(
|
||||
/// Helper to extract an analytical ellipse instance from a single `TangentGeom`.
|
||||
pub fn extract_ellipse_instance_from_geom(
|
||||
geom: &crate::scene::model::wire_model::TangentGeom,
|
||||
wire: &crate::scene::WireModel,
|
||||
draw_depth: f32,
|
||||
) -> Option<EllipseInstance> {
|
||||
if wire.tangent_geoms.len() != 1
|
||||
|| !wire.fill_tris.is_empty()
|
||||
|| !wire.pick_tris.is_empty()
|
||||
|| !wire.text_verts.is_empty()
|
||||
|| wire.render_instance.is_some()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let crate::scene::model::wire_model::TangentGeom::PlanarEllipse {
|
||||
center,
|
||||
major_axis,
|
||||
@@ -219,7 +212,7 @@ pub fn extract_ellipse_instance(
|
||||
minor_axis_ratio,
|
||||
start_param,
|
||||
end_param,
|
||||
} = wire.tangent_geoms[0] else {
|
||||
} = *geom else {
|
||||
return None;
|
||||
};
|
||||
|
||||
@@ -254,6 +247,43 @@ pub fn extract_ellipse_instance(
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper to extract all analytical ellipse instances from a `WireModel`.
|
||||
pub fn extract_ellipse_instances(
|
||||
wire: &crate::scene::WireModel,
|
||||
draw_depth: f32,
|
||||
) -> Option<Vec<EllipseInstance>> {
|
||||
if wire.tangent_geoms.is_empty()
|
||||
|| !wire.fill_tris.is_empty()
|
||||
|| !wire.pick_tris.is_empty()
|
||||
|| !wire.text_verts.is_empty()
|
||||
|| wire.render_instance.is_some()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut instances = Vec::with_capacity(wire.tangent_geoms.len());
|
||||
for geom in &wire.tangent_geoms {
|
||||
if let Some(inst) = extract_ellipse_instance_from_geom(geom, wire, draw_depth) {
|
||||
instances.push(inst);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(instances)
|
||||
}
|
||||
|
||||
/// Helper to extract a single analytical ellipse instance from a `WireModel`.
|
||||
pub fn extract_ellipse_instance(
|
||||
wire: &crate::scene::WireModel,
|
||||
draw_depth: f32,
|
||||
) -> Option<EllipseInstance> {
|
||||
if wire.tangent_geoms.len() == 1 {
|
||||
extract_ellipse_instances(wire, draw_depth).and_then(|mut v| v.pop())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn split_ds_xyz(x: f64, y: f64, z: f64) -> ([f32; 3], [f32; 3]) {
|
||||
let hx = x as f32;
|
||||
let lx = (x - hx as f64) as f32;
|
||||
|
||||
+30
-22
@@ -2421,8 +2421,8 @@ impl Pipeline {
|
||||
continue;
|
||||
}
|
||||
let depth = wire_gpu::wire_draw_depth(wire, depth_map);
|
||||
if let Some(inst) = circle_gpu::extract_circle_instance(wire, depth) {
|
||||
instances.push(inst);
|
||||
if let Some(insts) = circle_gpu::extract_circle_instances(wire, depth) {
|
||||
instances.extend(insts);
|
||||
}
|
||||
}
|
||||
self.upload_circles_from_instances(device, queue, &instances)
|
||||
@@ -2461,8 +2461,8 @@ impl Pipeline {
|
||||
continue;
|
||||
}
|
||||
let depth = wire_gpu::wire_draw_depth(wire, depth_map);
|
||||
if let Some(inst) = ellipse_gpu::extract_ellipse_instance(wire, depth) {
|
||||
instances.push(inst);
|
||||
if let Some(insts) = ellipse_gpu::extract_ellipse_instances(wire, depth) {
|
||||
instances.extend(insts);
|
||||
}
|
||||
}
|
||||
self.upload_ellipses_from_instances(device, queue, &instances)
|
||||
@@ -2656,18 +2656,22 @@ impl Pipeline {
|
||||
let mut selected_circles: Vec<CircleInstance> = Vec::new();
|
||||
for &wire in &selected_wires {
|
||||
let depth = wire_gpu::wire_draw_depth(wire, depth_map);
|
||||
if let Some(mut inst) = circle_gpu::extract_circle_instance(wire, depth) {
|
||||
if let Some(tint) = selected_tint {
|
||||
inst.color = tint;
|
||||
if let Some(insts) = circle_gpu::extract_circle_instances(wire, depth) {
|
||||
for mut inst in insts {
|
||||
if let Some(tint) = selected_tint {
|
||||
inst.color = tint;
|
||||
}
|
||||
selected_circles.push(inst);
|
||||
}
|
||||
selected_circles.push(inst);
|
||||
}
|
||||
}
|
||||
for &wire in &hover_wires {
|
||||
let depth = wire_gpu::wire_draw_depth(wire, depth_map);
|
||||
if let Some(mut inst) = circle_gpu::extract_circle_instance(wire, depth) {
|
||||
inst.color = WireModel::HOVER;
|
||||
selected_circles.push(inst);
|
||||
if let Some(insts) = circle_gpu::extract_circle_instances(wire, depth) {
|
||||
for mut inst in insts {
|
||||
inst.color = WireModel::HOVER;
|
||||
selected_circles.push(inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.gpu_selected_circles = if selected_circles.is_empty() {
|
||||
@@ -2684,18 +2688,22 @@ impl Pipeline {
|
||||
let mut selected_ellipses: Vec<EllipseInstance> = Vec::new();
|
||||
for &wire in &selected_wires {
|
||||
let depth = wire_gpu::wire_draw_depth(wire, depth_map);
|
||||
if let Some(mut inst) = ellipse_gpu::extract_ellipse_instance(wire, depth) {
|
||||
if let Some(tint) = selected_tint {
|
||||
inst.color = tint;
|
||||
if let Some(insts) = ellipse_gpu::extract_ellipse_instances(wire, depth) {
|
||||
for mut inst in insts {
|
||||
if let Some(tint) = selected_tint {
|
||||
inst.color = tint;
|
||||
}
|
||||
selected_ellipses.push(inst);
|
||||
}
|
||||
selected_ellipses.push(inst);
|
||||
}
|
||||
}
|
||||
for &wire in &hover_wires {
|
||||
let depth = wire_gpu::wire_draw_depth(wire, depth_map);
|
||||
if let Some(mut inst) = ellipse_gpu::extract_ellipse_instance(wire, depth) {
|
||||
inst.color = WireModel::HOVER;
|
||||
selected_ellipses.push(inst);
|
||||
if let Some(insts) = ellipse_gpu::extract_ellipse_instances(wire, depth) {
|
||||
for mut inst in insts {
|
||||
inst.color = WireModel::HOVER;
|
||||
selected_ellipses.push(inst);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.gpu_selected_ellipses = if selected_ellipses.is_empty() {
|
||||
@@ -2714,8 +2722,8 @@ impl Pipeline {
|
||||
.copied()
|
||||
.filter(|wire| {
|
||||
wire.render_instance.is_none()
|
||||
&& circle_gpu::extract_circle_instance(wire, 0.0).is_none()
|
||||
&& ellipse_gpu::extract_ellipse_instance(wire, 0.0).is_none()
|
||||
&& circle_gpu::extract_circle_instances(wire, 0.0).is_none()
|
||||
&& ellipse_gpu::extract_ellipse_instances(wire, 0.0).is_none()
|
||||
})
|
||||
.collect();
|
||||
let hover_regular: Vec<&WireModel> = hover_wires
|
||||
@@ -2723,8 +2731,8 @@ impl Pipeline {
|
||||
.copied()
|
||||
.filter(|wire| {
|
||||
wire.render_instance.is_none()
|
||||
&& circle_gpu::extract_circle_instance(wire, 0.0).is_none()
|
||||
&& ellipse_gpu::extract_ellipse_instance(wire, 0.0).is_none()
|
||||
&& circle_gpu::extract_circle_instances(wire, 0.0).is_none()
|
||||
&& ellipse_gpu::extract_ellipse_instances(wire, 0.0).is_none()
|
||||
})
|
||||
.collect();
|
||||
let selected_blocks: Vec<&WireModel> = selected_wires
|
||||
|
||||
@@ -144,25 +144,16 @@ pub fn classify_wire(wire: &WireModel) -> WireKind {
|
||||
if wire.render_instance.is_some() {
|
||||
return WireKind::Block;
|
||||
}
|
||||
if wire.tangent_geoms.len() == 1
|
||||
if !wire.tangent_geoms.is_empty()
|
||||
&& wire.fill_tris.is_empty()
|
||||
&& wire.pick_tris.is_empty()
|
||||
&& wire.text_verts.is_empty()
|
||||
{
|
||||
match &wire.tangent_geoms[0] {
|
||||
crate::scene::model::wire_model::TangentGeom::Circle { .. }
|
||||
| crate::scene::model::wire_model::TangentGeom::PlanarCircle { .. }
|
||||
| crate::scene::model::wire_model::TangentGeom::Arc { .. } => {
|
||||
if super::circle_gpu::extract_circle_instance(wire, 0.0).is_some() {
|
||||
return WireKind::Circle;
|
||||
}
|
||||
}
|
||||
crate::scene::model::wire_model::TangentGeom::PlanarEllipse { .. } => {
|
||||
if super::ellipse_gpu::extract_ellipse_instance(wire, 0.0).is_some() {
|
||||
return WireKind::Ellipse;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
if super::circle_gpu::extract_circle_instances(wire, 0.0).is_some() {
|
||||
return WireKind::Circle;
|
||||
}
|
||||
if super::ellipse_gpu::extract_ellipse_instances(wire, 0.0).is_some() {
|
||||
return WireKind::Ellipse;
|
||||
}
|
||||
}
|
||||
if !wire.points.is_empty() {
|
||||
@@ -209,27 +200,18 @@ pub fn partition_wires<'a>(
|
||||
continue;
|
||||
}
|
||||
let depth = super::wire_gpu::wire_draw_depth(wire, depth_map);
|
||||
if wire.tangent_geoms.len() == 1
|
||||
if !wire.tangent_geoms.is_empty()
|
||||
&& wire.fill_tris.is_empty()
|
||||
&& wire.pick_tris.is_empty()
|
||||
&& wire.text_verts.is_empty()
|
||||
{
|
||||
match &wire.tangent_geoms[0] {
|
||||
crate::scene::model::wire_model::TangentGeom::Circle { .. }
|
||||
| crate::scene::model::wire_model::TangentGeom::PlanarCircle { .. }
|
||||
| crate::scene::model::wire_model::TangentGeom::Arc { .. } => {
|
||||
if let Some(inst) = super::circle_gpu::extract_circle_instance(wire, depth) {
|
||||
circle_instances.push(inst);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
crate::scene::model::wire_model::TangentGeom::PlanarEllipse { .. } => {
|
||||
if let Some(inst) = super::ellipse_gpu::extract_ellipse_instance(wire, depth) {
|
||||
ellipse_instances.push(inst);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
if let Some(insts) = super::circle_gpu::extract_circle_instances(wire, depth) {
|
||||
circle_instances.extend(insts);
|
||||
continue;
|
||||
}
|
||||
if let Some(insts) = super::ellipse_gpu::extract_ellipse_instances(wire, depth) {
|
||||
ellipse_instances.extend(insts);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if !wire.points.is_empty() {
|
||||
|
||||
Reference in new issue
Block a user