feat(render): solid fill for PolyfaceMesh and PolygonMesh entities

PolyfaceMesh and PolygonMesh were wireframe-only. They now render with
the same flat-color solid fill as Face3D entities.

- Add fill_tris: Vec<[f32;3]> to TruckEntity and WireModel
- PolyfaceMesh: fan-triangulate each face into fill_tris
- PolygonMesh: triangulate each grid quad into fill_tris
- tessellate.rs Lines branch: apply world_offset to fill_tris
- Face3DGpu::from_wires: collect fill_tris from all wires in addition
  to Face3D key_vertices quads
- upload_face3d: pass all_wires so fill_tris are batched into one draw call
This commit is contained in:
Hakan Seven 2026-05-06 23:24:26 +03:00
commit 048bb41aef
61 changed files with 151 additions and 21 deletions

View file

@ -1376,6 +1376,7 @@ impl H7CAD {
aabb: crate::scene::WireModel::UNBOUNDED_AABB, aabb: crate::scene::WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}; };
previews.push(guide); previews.push(guide);
} }

View file

@ -87,6 +87,7 @@ fn to_truck(arc: &Arc) -> TruckEntity {
snap_pts: vec![(cv, SnapHint::Center)], snap_pts: vec![(cv, SnapHint::Center)],
tangent_geoms: vec![tangent], tangent_geoms: vec![tangent],
key_vertices: vec![], key_vertices: vec![],
fill_tris: vec![],
}; };
} }
@ -101,6 +102,7 @@ fn to_truck(arc: &Arc) -> TruckEntity {
snap_pts: vec![(cv, SnapHint::Center)], snap_pts: vec![(cv, SnapHint::Center)],
tangent_geoms: vec![tangent], tangent_geoms: vec![tangent],
key_vertices: vec![], key_vertices: vec![],
fill_tris: vec![],
} }
} }

View file

@ -42,6 +42,7 @@ impl TruckConvertible for AttributeDefinition {
snap_pts: vec![(snap_pt, SnapHint::Insertion)], snap_pts: vec![(snap_pt, SnapHint::Insertion)],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![], key_vertices: vec![],
fill_tris: vec![],
}) })
} }
} }
@ -146,6 +147,7 @@ impl TruckConvertible for AttributeEntity {
snap_pts: vec![(snap_pt, SnapHint::Insertion)], snap_pts: vec![(snap_pt, SnapHint::Insertion)],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![], key_vertices: vec![],
fill_tris: vec![],
}) })
} }
} }

View file

@ -91,6 +91,7 @@ fn to_truck(circle: &Circle) -> TruckEntity {
snap_pts, snap_pts,
tangent_geoms: vec![tangent], tangent_geoms: vec![tangent],
key_vertices: vec![], key_vertices: vec![],
fill_tris: vec![],
}; };
} }
@ -104,6 +105,7 @@ fn to_truck(circle: &Circle) -> TruckEntity {
snap_pts, snap_pts,
tangent_geoms: vec![tangent], tangent_geoms: vec![tangent],
key_vertices: vec![], key_vertices: vec![],
fill_tris: vec![],
} }
} }

View file

@ -102,6 +102,7 @@ fn to_truck(ell: &Ellipse) -> TruckEntity {
snap_pts, snap_pts,
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![], key_vertices: vec![],
fill_tris: vec![],
} }
} else { } else {
let n = 32usize; let n = 32usize;
@ -133,6 +134,7 @@ fn to_truck(ell: &Ellipse) -> TruckEntity {
snap_pts: vec![(center_v3, SnapHint::Center)], snap_pts: vec![(center_v3, SnapHint::Center)],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices, key_vertices,
fill_tris: vec![],
} }
} }
} }

View file

@ -64,6 +64,7 @@ fn to_truck(leader: &Leader) -> TruckEntity {
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: tangents, tangent_geoms: tangents,
key_vertices: key_verts, key_vertices: key_verts,
fill_tris: vec![],
} }
} }

View file

@ -50,6 +50,7 @@ fn to_truck(line: &Line) -> TruckEntity {
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: vec![tangent], tangent_geoms: vec![tangent],
key_vertices: kv, key_vertices: kv,
fill_tris: vec![],
}; };
} }
@ -61,6 +62,7 @@ fn to_truck(line: &Line) -> TruckEntity {
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: vec![tangent], tangent_geoms: vec![tangent],
key_vertices: kv, key_vertices: kv,
fill_tris: vec![],
} }
} }

View file

@ -106,7 +106,7 @@ fn thick_segments(
pts.push(off(last)); pts.push(off(last));
} }
} }
TruckEntity { object: TruckObject::Lines(pts), snap_pts: vec![], tangent_geoms: tangents, key_vertices: key_verts } TruckEntity { object: TruckObject::Lines(pts), snap_pts: vec![], tangent_geoms: tangents, key_vertices: key_verts, fill_tris: vec![] }
} }
fn to_truck(pline: &LwPolyline) -> TruckEntity { fn to_truck(pline: &LwPolyline) -> TruckEntity {
@ -117,6 +117,7 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![], key_vertices: vec![],
fill_tris: vec![],
}; };
} }
@ -237,6 +238,7 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: tgs, tangent_geoms: tgs,
key_vertices: kv, key_vertices: kv,
fill_tris: vec![],
}; };
} }
@ -305,6 +307,7 @@ fn to_truck(pline: &LwPolyline) -> TruckEntity {
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: tangents, tangent_geoms: tangents,
key_vertices: key_verts, key_vertices: key_verts,
fill_tris: vec![],
} }
} }

View file

@ -64,6 +64,7 @@ impl TruckConvertible for Face3D {
], ],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![p0, p1, p2, p3], key_vertices: vec![p0, p1, p2, p3],
fill_tris: vec![],
}) })
} }
} }
@ -182,9 +183,9 @@ impl TruckConvertible for PolygonMesh {
}; };
let mut pts: Vec<[f32; 3]> = Vec::new(); let mut pts: Vec<[f32; 3]> = Vec::new();
let mut fill_tris: Vec<[f32; 3]> = Vec::new();
// Rows (M direction). // Rows (M direction).
let m_end = if closed_m { m } else { m - 1 };
for i in 0..m { for i in 0..m {
pts.push([f32::NAN; 3]); pts.push([f32::NAN; 3]);
for j in 0..n { for j in 0..n {
@ -196,7 +197,6 @@ impl TruckConvertible for PolygonMesh {
} }
// Columns (N direction). // Columns (N direction).
let n_end = if closed_n { n } else { n - 1 };
for j in 0..n { for j in 0..n {
pts.push([f32::NAN; 3]); pts.push([f32::NAN; 3]);
for i in 0..m { for i in 0..m {
@ -206,13 +206,26 @@ impl TruckConvertible for PolygonMesh {
pts.push(pt(0, j)); pts.push(pt(0, j));
} }
} }
let _ = (m_end, n_end); // suppress warnings
// Fill: triangulate each grid quad (two triangles per cell).
let mi = if closed_m { m } else { m - 1 };
let ni = if closed_n { n } else { n - 1 };
for i in 0..mi {
for j in 0..ni {
let p00 = pt(i, j);
let p10 = pt((i + 1) % m, j);
let p01 = pt(i, (j + 1) % n);
let p11 = pt((i + 1) % m, (j + 1) % n);
fill_tris.extend_from_slice(&[p00, p10, p11, p00, p11, p01]);
}
}
Some(TruckEntity { Some(TruckEntity {
object: TruckObject::Lines(pts), object: TruckObject::Lines(pts),
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![], key_vertices: vec![],
fill_tris,
}) })
} }
} }
@ -294,6 +307,7 @@ impl TruckConvertible for PolyfaceMesh {
}; };
let mut pts: Vec<[f32; 3]> = Vec::new(); let mut pts: Vec<[f32; 3]> = Vec::new();
let mut fill_tris: Vec<[f32; 3]> = Vec::new();
for face in &self.faces { for face in &self.faces {
// Indices: 0 means unused. Negative = invisible edge (still render for wireframe). // Indices: 0 means unused. Negative = invisible edge (still render for wireframe).
@ -313,6 +327,15 @@ impl TruckConvertible for PolyfaceMesh {
} }
// Close the face polygon. // Close the face polygon.
pts.push(verts[0]); pts.push(verts[0]);
// Fan-triangulate the face for solid fill.
if verts.len() >= 3 {
for i in 1..(verts.len() - 1) {
fill_tris.push(verts[0]);
fill_tris.push(verts[i]);
fill_tris.push(verts[i + 1]);
}
}
} }
Some(TruckEntity { Some(TruckEntity {
@ -320,6 +343,7 @@ impl TruckConvertible for PolyfaceMesh {
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![], key_vertices: vec![],
fill_tris,
}) })
} }
} }

View file

@ -109,6 +109,7 @@ impl TruckConvertible for MLine {
snap_pts, snap_pts,
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: key_verts, key_vertices: key_verts,
fill_tris: vec![],
}) })
} }
} }

View file

@ -177,6 +177,7 @@ fn to_truck(t: &MText, document: &acadrust::CadDocument) -> TruckEntity {
snap_pts: vec![(insertion, SnapHint::Insertion)], snap_pts: vec![(insertion, SnapHint::Insertion)],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![], key_vertices: vec![],
fill_tris: vec![],
} }
} }

View file

@ -156,6 +156,7 @@ fn to_truck(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<TruckE
snap_pts, snap_pts,
tangent_geoms: tangents, tangent_geoms: tangents,
key_vertices: key_verts, key_vertices: key_verts,
fill_tris: vec![],
}) })
} }

View file

@ -22,6 +22,7 @@ fn to_truck(ole: &Ole2Frame) -> TruckEntity {
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![], key_vertices: vec![],
fill_tris: vec![],
}; };
} }
@ -42,6 +43,7 @@ fn to_truck(ole: &Ole2Frame) -> TruckEntity {
snap_pts: vec![(center, SnapHint::Center)], snap_pts: vec![(center, SnapHint::Center)],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![[x0, y0, z], [x1, y1, z]], key_vertices: vec![[x0, y0, z], [x1, y1, z]],
fill_tris: vec![],
} }
} }

View file

@ -22,6 +22,7 @@ fn to_truck(pt: &Point) -> TruckEntity {
snap_pts: vec![(snap, SnapHint::Node)], snap_pts: vec![(snap, SnapHint::Node)],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![], key_vertices: vec![],
fill_tris: vec![],
} }
} }

View file

@ -31,6 +31,7 @@ fn tessellate_polyline(pl: &Polyline) -> TruckEntity {
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: key_verts, key_vertices: key_verts,
fill_tris: vec![],
} }
} }
@ -127,6 +128,7 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> TruckEntity {
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![], key_vertices: vec![],
fill_tris: vec![],
}; };
} }
@ -204,7 +206,7 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> TruckEntity {
if i + 1 < kv.len() { pts.push([f32::NAN; 3]); } if i + 1 < kv.len() { pts.push([f32::NAN; 3]); }
} }
} }
return TruckEntity { object: TruckObject::Lines(pts), snap_pts: vec![], tangent_geoms: tgs, key_vertices: kv }; return TruckEntity { object: TruckObject::Lines(pts), snap_pts: vec![], tangent_geoms: tgs, key_vertices: kv, fill_tris: vec![] };
} }
for i in 0..seg_count { for i in 0..seg_count {
@ -271,6 +273,7 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> TruckEntity {
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: tangents, tangent_geoms: tangents,
key_vertices: key_verts, key_vertices: key_verts,
fill_tris: vec![],
} }
} }
@ -397,6 +400,7 @@ fn tessellate_polyline3d(pl: &Polyline3D) -> TruckEntity {
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: key_verts, key_vertices: key_verts,
fill_tris: vec![],
} }
} }

View file

@ -129,6 +129,7 @@ impl TruckConvertible for RasterImage {
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: corners.to_vec(), key_vertices: corners.to_vec(),
fill_tris: vec![],
}) })
} }
} }
@ -289,6 +290,7 @@ impl TruckConvertible for Wipeout {
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: corners.to_vec(), key_vertices: corners.to_vec(),
fill_tris: vec![],
}) })
} }
} }

View file

@ -34,6 +34,7 @@ impl TruckConvertible for Ray {
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![start], key_vertices: vec![start],
fill_tris: vec![],
}) })
} }
} }
@ -165,6 +166,7 @@ impl TruckConvertible for XLine {
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![[bp.x as f32, bp.y as f32, bp.z as f32]], key_vertices: vec![[bp.x as f32, bp.y as f32, bp.z as f32]],
fill_tris: vec![],
}) })
} }
} }

View file

@ -47,6 +47,7 @@ impl TruckConvertible for Shape {
snap_pts: vec![(snap_pt, SnapHint::Insertion)], snap_pts: vec![(snap_pt, SnapHint::Insertion)],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![[ox, oy, oz]], key_vertices: vec![[ox, oy, oz]],
fill_tris: vec![],
}) })
} }
} }

View file

@ -47,6 +47,7 @@ impl TruckConvertible for Solid {
snap_pts: snap, snap_pts: snap,
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![p0, p1, p2, p3], key_vertices: vec![p0, p1, p2, p3],
fill_tris: vec![],
}) })
} }
} }

View file

@ -19,6 +19,7 @@ fn to_truck(spl: &Spline) -> TruckEntity {
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![], key_vertices: vec![],
fill_tris: vec![],
}; };
} }
@ -92,6 +93,7 @@ fn to_truck(spl: &Spline) -> TruckEntity {
snap_pts: vec![], snap_pts: vec![],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices, key_vertices,
fill_tris: vec![],
} }
} }

View file

@ -137,6 +137,7 @@ impl TruckConvertible for Table {
snap_pts: vec![(origin, SnapHint::Insertion)], snap_pts: vec![(origin, SnapHint::Insertion)],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![], key_vertices: vec![],
fill_tris: vec![],
}) })
} }
} }

View file

@ -125,6 +125,7 @@ fn to_truck(t: &Text, document: &acadrust::CadDocument) -> TruckEntity {
snap_pts: vec![(snap_pt, SnapHint::Insertion)], snap_pts: vec![(snap_pt, SnapHint::Insertion)],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![], key_vertices: vec![],
fill_tris: vec![],
} }
} }

View file

@ -236,6 +236,7 @@ impl TruckConvertible for Tolerance {
snap_pts: vec![(snap_pt, SnapHint::Insertion)], snap_pts: vec![(snap_pt, SnapHint::Insertion)],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![], key_vertices: vec![],
fill_tris: vec![],
}) })
} }
} }

View file

@ -56,6 +56,7 @@ impl TruckConvertible for Underlay {
snap_pts: vec![(Vec3::from(origin), SnapHint::Node)], snap_pts: vec![(Vec3::from(origin), SnapHint::Node)],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: key, key_vertices: key,
fill_tris: vec![],
}) })
} else { } else {
// No clip boundary: draw a cross at insertion point. // No clip boundary: draw a cross at insertion point.
@ -65,6 +66,7 @@ impl TruckConvertible for Underlay {
snap_pts: vec![(Vec3::from(origin), SnapHint::Node)], snap_pts: vec![(Vec3::from(origin), SnapHint::Node)],
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: vec![origin], key_vertices: vec![origin],
fill_tris: vec![],
}) })
} }
} }

View file

@ -104,6 +104,7 @@ impl CadCommand for AlignedDimensionCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }
@ -140,5 +141,6 @@ fn preview_aligned(p1: Vec3, p2: Vec3, dim_pt: Vec3) -> WireModel {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
} }
} }

View file

@ -127,6 +127,7 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
} }
} }

View file

@ -103,5 +103,6 @@ fn preview_line(a: Vec3, b: Vec3) -> WireModel {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
} }
} }

View file

@ -145,6 +145,7 @@ impl CadCommand for DimBaselineCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }

View file

@ -142,6 +142,7 @@ impl CadCommand for DimContinueCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }

View file

@ -96,6 +96,7 @@ impl CadCommand for DimJogLineCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }

View file

@ -104,6 +104,7 @@ impl CadCommand for DimTeditCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }

View file

@ -204,6 +204,7 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
} }
} }

View file

@ -115,6 +115,7 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
} }
} }

View file

@ -157,6 +157,7 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
} }
} }

View file

@ -368,6 +368,7 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
} }
} }

View file

@ -101,6 +101,7 @@ impl CadCommand for QdimCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }

View file

@ -114,5 +114,6 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
} }
} }

View file

@ -147,6 +147,7 @@ impl CadCommand for TableCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} else { } else {
None None

View file

@ -100,6 +100,7 @@ impl CadCommand for ToleranceCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }

View file

@ -136,6 +136,7 @@ impl CadCommand for AttdefCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }

View file

@ -529,5 +529,6 @@ fn line_wire(from: Vec3, to: Vec3) -> WireModel {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
} }
} }

View file

@ -90,6 +90,7 @@ impl CadCommand for LineCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }

View file

@ -115,6 +115,7 @@ impl CadCommand for MlineCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }

View file

@ -114,6 +114,7 @@ impl CadCommand for ImageCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }

View file

@ -88,6 +88,7 @@ impl CadCommand for RayCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }
@ -168,6 +169,7 @@ impl CadCommand for XLineCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }

View file

@ -85,6 +85,7 @@ impl CadCommand for RevCloudCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }

View file

@ -121,6 +121,7 @@ impl CadCommand for WipeoutCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
WipeoutMode::Polygonal => { WipeoutMode::Polygonal => {
@ -144,6 +145,7 @@ impl CadCommand for WipeoutCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }

View file

@ -78,6 +78,7 @@ impl CadCommand for AreaCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }

View file

@ -73,6 +73,7 @@ impl CadCommand for DistCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }

View file

@ -136,6 +136,7 @@ impl CadCommand for StretchCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
_ => None, _ => None,

View file

@ -100,6 +100,7 @@ impl CadCommand for MviewCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }

View file

@ -65,6 +65,7 @@ impl CadCommand for PlotWindowCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }

View file

@ -71,6 +71,7 @@ impl CadCommand for ZoomWindowCommand {
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
} }
} }

View file

@ -33,6 +33,9 @@ pub struct TruckEntity {
pub snap_pts: Vec<(Vec3, SnapHint)>, pub snap_pts: Vec<(Vec3, SnapHint)>,
pub tangent_geoms: Vec<TangentGeom>, pub tangent_geoms: Vec<TangentGeom>,
pub key_vertices: Vec<[f32; 3]>, pub key_vertices: Vec<[f32; 3]>,
/// Pre-triangulated fill geometry: flat list of [f32;3] vertices, 3 per triangle.
/// Non-empty for mesh-like entities (PolyfaceMesh, PolygonMesh) that need solid fill.
pub fill_tris: Vec<[f32; 3]>,
} }
pub fn convert(entity: &EntityType, document: &CadDocument) -> Option<TruckEntity> { pub fn convert(entity: &EntityType, document: &CadDocument) -> Option<TruckEntity> {

View file

@ -204,6 +204,7 @@ pub fn apply_along(
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}) })
.collect() .collect()
} }

View file

@ -3505,6 +3505,7 @@ fn tessellate_entity(
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}); });
return wires; return wires;
} }

View file

@ -52,16 +52,17 @@ pub struct Face3DGpu {
} }
impl Face3DGpu { impl Face3DGpu {
/// Build a batched GPU buffer from Face3D wire models. /// Build a batched GPU buffer from Face3D wire models and mesh fill_tris.
/// ///
/// Each WireModel's `key_vertices` holds the 4 corners in local space /// - `face3d_wires`: Face3D entities — `key_vertices` holds 4 quad corners;
/// (world_offset already applied by tessellate.rs). Two triangles are /// emits 2 triangles per face.
/// emitted per face: (p0,p1,p2) and (p0,p2,p3). /// - `all_wires`: all entity wires — `fill_tris` holds pre-triangulated
pub fn from_wires(device: &wgpu::Device, wires: &[WireModel]) -> Self { /// fill data for PolyfaceMesh / PolygonMesh.
let mut vertices: Vec<Face3DVertex> = Vec::with_capacity(wires.len() * 6); pub fn from_wires(device: &wgpu::Device, face3d_wires: &[WireModel], all_wires: &[WireModel]) -> Self {
let mut vertices: Vec<Face3DVertex> = Vec::with_capacity(face3d_wires.len() * 6);
for wire in wires { // Face3D quads (4 key_vertices → 2 triangles).
// key_vertices has exactly 4 entries for Face3D (p0..p3). for wire in face3d_wires {
if wire.key_vertices.len() < 4 { if wire.key_vertices.len() < 4 {
continue; continue;
} }
@ -69,17 +70,26 @@ impl Face3DGpu {
let fill_color = [r * 0.45, g * 0.45, b * 0.45, a]; let fill_color = [r * 0.45, g * 0.45, b * 0.45, a];
let p = &wire.key_vertices; let p = &wire.key_vertices;
let v = |i: usize| Face3DVertex { position: p[i], color: fill_color }; let v = |i: usize| Face3DVertex { position: p[i], color: fill_color };
// Triangle 1: p0, p1, p2
vertices.push(v(0)); vertices.push(v(0));
vertices.push(v(1)); vertices.push(v(1));
vertices.push(v(2)); vertices.push(v(2));
// Triangle 2: p0, p2, p3
vertices.push(v(0)); vertices.push(v(0));
vertices.push(v(2)); vertices.push(v(2));
vertices.push(v(3)); vertices.push(v(3));
} }
// PolyfaceMesh / PolygonMesh pre-triangulated fills.
for wire in all_wires {
if wire.fill_tris.is_empty() {
continue;
}
let [r, g, b, a] = wire.color;
let fill_color = [r * 0.45, g * 0.45, b * 0.45, a];
for &position in &wire.fill_tris {
vertices.push(Face3DVertex { position, color: fill_color });
}
}
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("face3d.vbuf"), label: Some("face3d.vbuf"),
contents: bytemuck::cast_slice(&vertices), contents: bytemuck::cast_slice(&vertices),

View file

@ -637,13 +637,15 @@ impl Pipeline {
/// Upload all 3DFACE entities as two batched GPU objects: /// Upload all 3DFACE entities as two batched GPU objects:
/// - `gpu_face3d_fill`: filled triangles (1 buffer, 1 draw call) /// - `gpu_face3d_fill`: filled triangles (1 buffer, 1 draw call)
/// - `gpu_face3d_edges`: merged edge wires (1 buffer, 1 draw call) /// - `gpu_face3d_edges`: merged edge wires (1 buffer, 1 draw call)
pub fn upload_face3d(&mut self, device: &wgpu::Device, face3d_wires: &[WireModel]) { pub fn upload_face3d(&mut self, device: &wgpu::Device, face3d_wires: &[WireModel], all_wires: &[WireModel]) {
if face3d_wires.is_empty() { let has_fills = !face3d_wires.is_empty()
|| all_wires.iter().any(|w| !w.fill_tris.is_empty());
if !has_fills {
self.gpu_face3d_fill = None; self.gpu_face3d_fill = None;
self.gpu_face3d_edges = vec![]; self.gpu_face3d_edges = vec![];
return; return;
} }
self.gpu_face3d_fill = Some(Face3DGpu::from_wires(device, face3d_wires)); self.gpu_face3d_fill = Some(Face3DGpu::from_wires(device, face3d_wires, all_wires));
self.gpu_face3d_edges = WireGpu::from_batch(device, face3d_wires); self.gpu_face3d_edges = WireGpu::from_batch(device, face3d_wires);
} }

View file

@ -128,7 +128,7 @@ impl shader::Primitive for Primitive {
pipeline.upload_images(device, queue, &self.images[..]); pipeline.upload_images(device, queue, &self.images[..]);
pipeline.upload_meshes(device, &self.meshes[..]); pipeline.upload_meshes(device, &self.meshes[..]);
pipeline.upload_wires(device, &self.wires[..]); pipeline.upload_wires(device, &self.wires[..]);
pipeline.upload_face3d(device, &self.face3d_wires[..]); pipeline.upload_face3d(device, &self.face3d_wires[..], &self.wires[..]);
pipeline.cached_epoch = self.geometry_epoch; pipeline.cached_epoch = self.geometry_epoch;
} }
pipeline.compute_wire_scissors(self.uniforms.view_proj, clip_size.width, clip_size.height); pipeline.compute_wire_scissors(self.uniforms.view_proj, clip_size.width, clip_size.height);

View file

@ -147,6 +147,7 @@ pub fn tessellate(
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}; };
} }
@ -177,6 +178,7 @@ pub fn tessellate(
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}; };
} }
_ => {} _ => {}
@ -205,6 +207,7 @@ pub fn tessellate(
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}; };
} }
} }
@ -231,6 +234,7 @@ pub fn tessellate(
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}; };
} }
} }
@ -248,6 +252,9 @@ pub fn tessellate(
let key_vertices: Vec<[f32; 3]> = te.key_vertices.into_iter() let key_vertices: Vec<[f32; 3]> = te.key_vertices.into_iter()
.map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32]) .map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32])
.collect(); .collect();
let fill_tris: Vec<[f32; 3]> = te.fill_tris.into_iter()
.map(|[x, y, z]| [x - ox as f32, y - oy as f32, z - oz as f32])
.collect();
return WireModel { return WireModel {
name, name,
points: local_pts, points: local_pts,
@ -262,7 +269,8 @@ pub fn tessellate(
key_vertices, key_vertices,
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris,
}; };
} }
@ -291,6 +299,7 @@ pub fn tessellate(
plinegen: false, plinegen: false,
vp_scissor: None, vp_scissor: None,
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
fill_tris: vec![],
}; };
} }
@ -339,6 +348,7 @@ pub fn tessellate(
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
} }
} }
@ -397,6 +407,7 @@ pub fn tessellate_dimension(
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}]; }];
if let Some(text) = dimension_text_entity(dim) { if let Some(text) = dimension_text_entity(dim) {
@ -454,6 +465,7 @@ fn tessellate_leader_single(
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
}; };
} }
@ -508,6 +520,7 @@ fn tessellate_leader_single(
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
} }
} }
@ -637,6 +650,7 @@ fn tessellate_multileader_single(
aabb: WireModel::UNBOUNDED_AABB, aabb: WireModel::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
} }
} }

View file

@ -66,6 +66,9 @@ pub struct WireModel {
/// Paper-space bounding box [x0, y0, x1, y1] for GPU scissor clipping. /// Paper-space bounding box [x0, y0, x1, y1] for GPU scissor clipping.
/// Set only for viewport-projected wires in paper-space layouts. /// Set only for viewport-projected wires in paper-space layouts.
pub vp_scissor: Option<[f32; 4]>, pub vp_scissor: Option<[f32; 4]>,
/// Pre-triangulated solid fill: flat vertex list, 3 per triangle (world-offset applied).
/// Non-empty only for PolyfaceMesh / PolygonMesh entities.
pub fill_tris: Vec<[f32; 3]>,
} }
impl WireModel { impl WireModel {
@ -93,6 +96,7 @@ impl WireModel {
aabb: Self::UNBOUNDED_AABB, aabb: Self::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: vec![],
} }
} }
@ -194,6 +198,7 @@ impl Default for WireModel {
aabb: Self::UNBOUNDED_AABB, aabb: Self::UNBOUNDED_AABB,
plinegen: true, plinegen: true,
vp_scissor: None, vp_scissor: None,
fill_tris: Vec::new(),
} }
} }
} }