forked from 1b-agent/opencadstudio-upstream
feat(render): draw ACIS solids as B-rep feature edges, meshes as tri edges
Solid wireframes rendered every triangle edge of the tessellation, so a curved solid read as a dense mesh instead of its real outline. Now each ACIS solid's B-rep face-boundary edges are collected from the SAT `edge` records (straight edges as segments, ellipse/circle edges sampled along their bounded arc), body-transformed and split into the double-single pair, and stored on the MeshLodSet. The block-instance and top-level offset paths transform these edges alongside the mesh vertices. The batch builder routes a solid with feature edges into a dedicated LineList vertex buffer and skips its triangle wireframe; a mesh with no B-rep edges keeps its triangulation edges. Edge colour follows the display mode: the flat `fs_edge` shader draws the entity colour in the lines-only modes (wireframe, hidden-line) and a `fs_edge_black` twin forces black for the edge overlay in filled render modes. Verified: the Clinic storefront arch bars now yield ~14-16 clean edge segments (vs the full triangulation) bounded to the solid's true extent. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
a08cf477c1
commit
1065bc12b2
6 files changed
+285
-63
No files matched your search
@@ -142,20 +142,101 @@ fn tessellate_acis(
|
||||
color: [f32; 4],
|
||||
facet_res: f64,
|
||||
) -> Option<MeshLodSet> {
|
||||
if let Some(set) = crate::scene::convert::acis_to_truck::tessellate_sat_truck(sat, name.clone(), color, facet_res) {
|
||||
if std::env::var_os("OCS_TESS_DEBUG").is_some() {
|
||||
let tris = set.lods.first().map(|m| m.indices.len() / 3).unwrap_or(0);
|
||||
eprintln!("acis_tess[{name}]: truck ({tris} tris)");
|
||||
let mut set =
|
||||
if let Some(set) = crate::scene::convert::acis_to_truck::tessellate_sat_truck(sat, name.clone(), color, facet_res) {
|
||||
if std::env::var_os("OCS_TESS_DEBUG").is_some() {
|
||||
let tris = set.lods.first().map(|m| m.indices.len() / 3).unwrap_or(0);
|
||||
eprintln!("acis_tess[{name}]: truck ({tris} tris)");
|
||||
}
|
||||
set
|
||||
} else {
|
||||
// truck couldn't rebuild the shell — fall back to the bespoke sampler.
|
||||
let fallback = tessellate_sat_lods(sat, name.clone(), color, facet_res);
|
||||
eprintln!(
|
||||
"acis_tess[{name}]: manual fallback ({})",
|
||||
if fallback.is_some() { "ok" } else { "empty" }
|
||||
);
|
||||
fallback?
|
||||
};
|
||||
// Attach the B-rep face-boundary edges (body-transformed, split into the
|
||||
// double-single pair) so the solid's wireframe shows real edges.
|
||||
attach_feature_edges(&mut set, sat);
|
||||
Some(set)
|
||||
}
|
||||
|
||||
/// Collect the ACIS `edge` records as world-space polyline segments (pairs of
|
||||
/// endpoints), body-transform them, and store them on the set as the
|
||||
/// double-single `edge_verts` / `edge_verts_low`.
|
||||
fn attach_feature_edges(set: &mut MeshLodSet, sat: &SatDocument) {
|
||||
let xform = body_transform(sat);
|
||||
let seg_pts = collect_feature_edges(sat);
|
||||
set.edge_verts.reserve(seg_pts.len());
|
||||
set.edge_verts_low.reserve(seg_pts.len());
|
||||
for p in seg_pts {
|
||||
let (mut x, mut y, mut z) = (p[0], p[1], p[2]);
|
||||
if let Some((m, tr, scale)) = xform {
|
||||
let (lx, ly, lz) = (x, y, z);
|
||||
x = scale * (lx * m[0] + ly * m[3] + lz * m[6]) + tr[0];
|
||||
y = scale * (lx * m[1] + ly * m[4] + lz * m[7]) + tr[1];
|
||||
z = scale * (lx * m[2] + ly * m[5] + lz * m[8]) + tr[2];
|
||||
}
|
||||
return Some(set);
|
||||
let (hx, hy, hz) = (x as f32, y as f32, z as f32);
|
||||
set.edge_verts.push([hx, hy, hz]);
|
||||
set.edge_verts_low
|
||||
.push([(x - hx as f64) as f32, (y - hy as f64) as f32, (z - hz as f64) as f32]);
|
||||
}
|
||||
// truck couldn't rebuild the shell — fall back to the bespoke sampler.
|
||||
let fallback = tessellate_sat_lods(sat, name.clone(), color, facet_res);
|
||||
eprintln!(
|
||||
"acis_tess[{name}]: manual fallback ({})",
|
||||
if fallback.is_some() { "ok" } else { "empty" }
|
||||
);
|
||||
fallback
|
||||
}
|
||||
|
||||
/// Line-list endpoints (pairs) for every `edge` record: straight edges emit
|
||||
/// their two vertex endpoints; ellipse/circle edges are sampled along their
|
||||
/// bounded parametric arc. Points are in body-local space (pre-transform).
|
||||
fn collect_feature_edges(sat: &SatDocument) -> Vec<[f64; 3]> {
|
||||
const EDGE_SEGS: usize = 48;
|
||||
let mut out: Vec<[f64; 3]> = Vec::new();
|
||||
for er in sat.records_of_type("edge") {
|
||||
let Some(edge) = SatEdge::from_record(er) else {
|
||||
continue;
|
||||
};
|
||||
// Ordered points along the edge (≥2).
|
||||
let mut pts: Vec<[f64; 3]> = Vec::new();
|
||||
if let Some(cr) = sat.resolve(edge.curve()) {
|
||||
if let Some(ellipse) = SatEllipseCurve::from_record(cr) {
|
||||
pts = sample_ellipse_arc(&ellipse, edge.start_param(), edge.end_param(), EDGE_SEGS);
|
||||
// sample_ellipse_arc drops the end param; append the true end so
|
||||
// the polyline closes onto the shared vertex.
|
||||
if let Some(p) = vertex_point(sat, edge.end_vertex()) {
|
||||
pts.push(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
if pts.len() < 2 {
|
||||
// Straight edge (or unsampled curve): connect the two vertices.
|
||||
pts.clear();
|
||||
if let (Some(a), Some(b)) = (
|
||||
vertex_point(sat, edge.start_vertex()),
|
||||
vertex_point(sat, edge.end_vertex()),
|
||||
) {
|
||||
pts.push(a);
|
||||
pts.push(b);
|
||||
}
|
||||
}
|
||||
// Emit consecutive points as line-list segment pairs.
|
||||
for w in pts.windows(2) {
|
||||
out.push(w[0]);
|
||||
out.push(w[1]);
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Resolve a vertex pointer to its point coordinates.
|
||||
fn vertex_point(sat: &SatDocument, vptr: SatPointer) -> Option<[f64; 3]> {
|
||||
let vrec = sat.resolve(vptr)?;
|
||||
let vertex = SatVertex::from_record(vrec)?;
|
||||
let prec = sat.resolve(vertex.point())?;
|
||||
let point = SatPoint::from_record(prec)?;
|
||||
let (x, y, z) = point.position();
|
||||
Some([x, y, z])
|
||||
}
|
||||
|
||||
/// Tessellate a SAT document at all three LODs and bundle them into a
|
||||
@@ -1016,3 +1097,4 @@ fn norm3(v: [f64; 3]) -> [f64; 3] {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -567,6 +567,21 @@ fn offset_mesh_lod_set(mut set: MeshLodSet) -> MeshLodSet {
|
||||
if hy > max_y { max_y = hy; }
|
||||
}
|
||||
}
|
||||
// Re-split the feature edges the same way so they track the mesh at scale.
|
||||
{
|
||||
let n = set.edge_verts.len();
|
||||
if set.edge_verts_low.len() != n {
|
||||
set.edge_verts_low = vec![[0.0; 3]; n];
|
||||
}
|
||||
for (v, vl) in set.edge_verts.iter_mut().zip(set.edge_verts_low.iter_mut()) {
|
||||
let ax = v[0] as f64 + vl[0] as f64;
|
||||
let ay = v[1] as f64 + vl[1] as f64;
|
||||
let az = v[2] as f64 + vl[2] as f64;
|
||||
let (hx, hy, hz) = (ax as f32, ay as f32, az as f32);
|
||||
*v = [hx, hy, hz];
|
||||
*vl = [(ax - hx as f64) as f32, (ay - hy as f64) as f32, (az - hz as f64) as f32];
|
||||
}
|
||||
}
|
||||
if min_x.is_finite() {
|
||||
set.world_aabb = [min_x, min_y, max_x, max_y];
|
||||
}
|
||||
@@ -624,6 +639,23 @@ fn transform_block_mesh_lod_set(
|
||||
}
|
||||
}
|
||||
}
|
||||
// Apply the same INSERT transform to the feature edges.
|
||||
{
|
||||
let n = out.edge_verts.len();
|
||||
if out.edge_verts_low.len() != n {
|
||||
out.edge_verts_low = vec![[0.0; 3]; n];
|
||||
}
|
||||
for (v, vl) in out.edge_verts.iter_mut().zip(out.edge_verts_low.iter_mut()) {
|
||||
let w = xform.apply(Vector3::new(
|
||||
v[0] as f64 + vl[0] as f64,
|
||||
v[1] as f64 + vl[1] as f64,
|
||||
v[2] as f64 + vl[2] as f64,
|
||||
));
|
||||
let (hx, hy, hz) = (w.x as f32, w.y as f32, w.z as f32);
|
||||
*v = [hx, hy, hz];
|
||||
*vl = [(w.x - hx as f64) as f32, (w.y - hy as f64) as f32, (w.z - hz as f64) as f32];
|
||||
}
|
||||
}
|
||||
if min_x.is_finite() {
|
||||
out.world_aabb = [min_x, min_y, max_x, max_y];
|
||||
}
|
||||
|
||||
@@ -39,6 +39,13 @@ pub struct MeshModel {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MeshLodSet {
|
||||
pub lods: Vec<MeshModel>,
|
||||
/// Feature-edge line list (LOD-independent): pairs of endpoints, high half
|
||||
/// of the double-single. Populated for ACIS solids (the B-rep face-boundary
|
||||
/// edges) so their wireframe shows real edges rather than the triangulation.
|
||||
/// Empty for plain meshes — those fall back to triangle edges at batch time.
|
||||
pub edge_verts: Vec<[f32; 3]>,
|
||||
/// Low residual paired with `edge_verts`.
|
||||
pub edge_verts_low: Vec<[f32; 3]>,
|
||||
/// World XY AABB `[min_x, min_y, max_x, max_y]` of the mesh — used
|
||||
/// by the per-frame LOD selector to compute the projected pixel
|
||||
/// diagonal.
|
||||
@@ -76,7 +83,13 @@ impl MeshLodSet {
|
||||
/// Build a set from its LODs, computing the 3D AABB.
|
||||
pub fn from_lods(lods: Vec<MeshModel>) -> Self {
|
||||
let (world_aabb, z_aabb) = compute_mesh_aabb(&lods);
|
||||
Self { lods, world_aabb, z_aabb }
|
||||
Self {
|
||||
lods,
|
||||
edge_verts: Vec::new(),
|
||||
edge_verts_low: Vec::new(),
|
||||
world_aabb,
|
||||
z_aabb,
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrap a single MeshModel as a one-LOD set. Used by interactive
|
||||
|
||||
@@ -120,8 +120,14 @@ pub struct MeshBatchChunk {
|
||||
/// erase — the geometry behind them.
|
||||
pub transp_index_buffer: wgpu::Buffer,
|
||||
pub transp_index_count: u32,
|
||||
/// Triangle-edge line list (into `vertex_buffer`) for plain meshes that
|
||||
/// carry no B-rep edges — the tessellation wireframe.
|
||||
pub wire_index_buffer: wgpu::Buffer,
|
||||
pub wire_index_count: u32,
|
||||
/// B-rep feature edges of ACIS solids, as a standalone LineList vertex
|
||||
/// buffer (pairs of endpoints), drawn non-indexed. Empty for plain meshes.
|
||||
pub edge_vertex_buffer: wgpu::Buffer,
|
||||
pub edge_vertex_count: u32,
|
||||
}
|
||||
|
||||
fn make_chunk(
|
||||
@@ -130,6 +136,7 @@ fn make_chunk(
|
||||
indices: &[u32],
|
||||
transp_indices: &[u32],
|
||||
wire_indices: &[u32],
|
||||
edge_verts: &[MeshVertex],
|
||||
) -> MeshBatchChunk {
|
||||
// `create_buffer_init` with an empty slice yields a zero-sized buffer that
|
||||
// some backends reject for INDEX usage; a chunk can legitimately hold only
|
||||
@@ -144,19 +151,30 @@ fn make_chunk(
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
})
|
||||
};
|
||||
let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some("mesh.batch.vbuf"),
|
||||
contents: bytemuck::cast_slice(verts),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
let mk_vertex = |data: &[MeshVertex], label: &'static str| {
|
||||
let stub = [MeshVertex {
|
||||
position: [0.0; 3],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
color: [0.0; 4],
|
||||
position_low: [0.0; 3],
|
||||
}];
|
||||
let src = if data.is_empty() { &stub[..] } else { data };
|
||||
device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some(label),
|
||||
contents: bytemuck::cast_slice(src),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
})
|
||||
};
|
||||
MeshBatchChunk {
|
||||
vertex_buffer,
|
||||
vertex_buffer: mk_vertex(verts, "mesh.batch.vbuf"),
|
||||
index_buffer: mk_index(indices, "mesh.batch.ibuf"),
|
||||
index_count: indices.len() as u32,
|
||||
transp_index_buffer: mk_index(transp_indices, "mesh.batch.transp_ibuf"),
|
||||
transp_index_count: transp_indices.len() as u32,
|
||||
wire_index_buffer: mk_index(wire_indices, "mesh.batch.wire_ibuf"),
|
||||
wire_index_count: wire_indices.len() as u32,
|
||||
edge_vertex_buffer: mk_vertex(edge_verts, "mesh.batch.edge_vbuf"),
|
||||
edge_vertex_count: edge_verts.len() as u32,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,6 +200,7 @@ pub fn build_mesh_batch(device: &wgpu::Device, sets: &[MeshLodSet]) -> (Vec<Mesh
|
||||
let mut indices: Vec<u32> = Vec::new();
|
||||
let mut transp_indices: Vec<u32> = Vec::new();
|
||||
let mut wire_indices: Vec<u32> = Vec::new();
|
||||
let mut edge_verts: Vec<MeshVertex> = Vec::new();
|
||||
let mut total_tris = 0u64;
|
||||
for set in sets {
|
||||
let Some(mesh) = set.lods.iter().find(|m| !m.indices.is_empty()) else {
|
||||
@@ -200,15 +219,38 @@ pub fn build_mesh_batch(device: &wgpu::Device, sets: &[MeshLodSet]) -> (Vec<Mesh
|
||||
let mesh_tris = mesh.indices.len() / 3;
|
||||
total_tris += mesh_tris as u64;
|
||||
|
||||
// Feature edges present (ACIS solid) → emit the B-rep edges as a line
|
||||
// list and skip the triangulation wireframe. Absent (plain mesh) → keep
|
||||
// the triangle edges so the mesh still shows a wireframe.
|
||||
let has_feat = !set.edge_verts.is_empty();
|
||||
if has_feat {
|
||||
for i in 0..set.edge_verts.len() {
|
||||
edge_verts.push(MeshVertex {
|
||||
position: set.edge_verts[i],
|
||||
normal: [0.0, 1.0, 0.0],
|
||||
color: mesh.color,
|
||||
position_low: set.edge_verts_low.get(i).copied().unwrap_or([0.0; 3]),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// A single mesh larger than a whole chunk: emit as triangle-soup
|
||||
// sub-chunks (corners expanded, no vertex sharing) so each buffer fits.
|
||||
if mesh.verts.len() > max_verts || mesh_tris > max_tris {
|
||||
if !verts.is_empty() {
|
||||
chunks.push(make_chunk(device, &verts, &indices, &transp_indices, &wire_indices));
|
||||
if !verts.is_empty() || !edge_verts.is_empty() {
|
||||
chunks.push(make_chunk(
|
||||
device,
|
||||
&verts,
|
||||
&indices,
|
||||
&transp_indices,
|
||||
&wire_indices,
|
||||
&edge_verts,
|
||||
));
|
||||
verts.clear();
|
||||
indices.clear();
|
||||
transp_indices.clear();
|
||||
wire_indices.clear();
|
||||
edge_verts.clear();
|
||||
}
|
||||
let tris_per = (max_verts / 3).min(max_tris).max(1);
|
||||
let mut t = 0;
|
||||
@@ -222,14 +264,16 @@ pub fn build_mesh_batch(device: &wgpu::Device, sets: &[MeshLodSet]) -> (Vec<Mesh
|
||||
sv.push(vtx(ix[1] as usize));
|
||||
sv.push(vtx(ix[2] as usize));
|
||||
si.extend_from_slice(&[b, b + 1, b + 2]);
|
||||
swi.extend_from_slice(&[b, b + 1, b + 1, b + 2, b + 2, b]);
|
||||
if !has_feat {
|
||||
swi.extend_from_slice(&[b, b + 1, b + 1, b + 2, b + 2, b]);
|
||||
}
|
||||
}
|
||||
// The whole mesh shares one colour, so a sub-chunk is entirely
|
||||
// opaque or entirely transparent.
|
||||
if is_transp {
|
||||
chunks.push(make_chunk(device, &sv, &[], &si, &swi));
|
||||
chunks.push(make_chunk(device, &sv, &[], &si, &swi, &[]));
|
||||
} else {
|
||||
chunks.push(make_chunk(device, &sv, &si, &[], &swi));
|
||||
chunks.push(make_chunk(device, &sv, &si, &[], &swi, &[]));
|
||||
}
|
||||
t = end;
|
||||
}
|
||||
@@ -242,11 +286,19 @@ pub fn build_mesh_batch(device: &wgpu::Device, sets: &[MeshLodSet]) -> (Vec<Mesh
|
||||
&& (verts.len() + mesh.verts.len() > max_verts
|
||||
|| wire_indices.len() / 6 + mesh_tris > max_tris)
|
||||
{
|
||||
chunks.push(make_chunk(device, &verts, &indices, &transp_indices, &wire_indices));
|
||||
chunks.push(make_chunk(
|
||||
device,
|
||||
&verts,
|
||||
&indices,
|
||||
&transp_indices,
|
||||
&wire_indices,
|
||||
&edge_verts,
|
||||
));
|
||||
verts.clear();
|
||||
indices.clear();
|
||||
transp_indices.clear();
|
||||
wire_indices.clear();
|
||||
edge_verts.clear();
|
||||
}
|
||||
let base = verts.len() as u32;
|
||||
for i in 0..mesh.verts.len() {
|
||||
@@ -256,13 +308,22 @@ pub fn build_mesh_batch(device: &wgpu::Device, sets: &[MeshLodSet]) -> (Vec<Mesh
|
||||
for &idx in &mesh.indices {
|
||||
fill.push(base + idx);
|
||||
}
|
||||
for tri in mesh.indices.chunks_exact(3) {
|
||||
let (a, b, c) = (base + tri[0], base + tri[1], base + tri[2]);
|
||||
wire_indices.extend_from_slice(&[a, b, b, c, c, a]);
|
||||
if !has_feat {
|
||||
for tri in mesh.indices.chunks_exact(3) {
|
||||
let (a, b, c) = (base + tri[0], base + tri[1], base + tri[2]);
|
||||
wire_indices.extend_from_slice(&[a, b, b, c, c, a]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if !indices.is_empty() || !transp_indices.is_empty() {
|
||||
chunks.push(make_chunk(device, &verts, &indices, &transp_indices, &wire_indices));
|
||||
if !indices.is_empty() || !transp_indices.is_empty() || !edge_verts.is_empty() {
|
||||
chunks.push(make_chunk(
|
||||
device,
|
||||
&verts,
|
||||
&indices,
|
||||
&transp_indices,
|
||||
&wire_indices,
|
||||
&edge_verts,
|
||||
));
|
||||
}
|
||||
(chunks, total_tris)
|
||||
}
|
||||
|
||||
+53
-33
@@ -53,6 +53,8 @@ pub struct Pipeline {
|
||||
/// Wireframe 2D or Wireframe 3D so 3D solids draw as their
|
||||
/// triangle edges instead of filled faces.
|
||||
mesh_wireframe_pipeline: wgpu::RenderPipeline,
|
||||
/// Edge pipeline that forces black, for the edge overlay in filled modes.
|
||||
mesh_edge_black_pipeline: wgpu::RenderPipeline,
|
||||
/// Depth-only variant of the mesh pipeline (TriangleList, no color
|
||||
/// writes, writes depth). Used in HiddenLine mode so 3D solids
|
||||
/// occlude wires behind them without painting visible pixels.
|
||||
@@ -631,9 +633,12 @@ impl Pipeline {
|
||||
// only the input topology changes (LineList) and back-face
|
||||
// culling drops out (each triangle edge is shared between two
|
||||
// faces, one of which would otherwise hide the edge).
|
||||
let mesh_wireframe_pipeline =
|
||||
// Edge/wireframe pipeline (LineList). `fs_edge` outputs the flat entity
|
||||
// colour — no lighting — for the lines-only modes. A `fs_edge_black`
|
||||
// twin (below) forces black for the edge overlay in filled modes.
|
||||
let make_edge_pipeline = |label: &'static str, fs: &'static str| {
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("mesh.wireframe.pipeline"),
|
||||
label: Some(label),
|
||||
layout: Some(&mesh_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &mesh_shader,
|
||||
@@ -660,7 +665,7 @@ impl Pipeline {
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &mesh_shader,
|
||||
entry_point: Some("fs_main"),
|
||||
entry_point: Some(fs),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||
@@ -670,7 +675,10 @@ impl Pipeline {
|
||||
}),
|
||||
multiview: None,
|
||||
cache: None,
|
||||
});
|
||||
})
|
||||
};
|
||||
let mesh_wireframe_pipeline = make_edge_pipeline("mesh.wireframe.pipeline", "fs_edge");
|
||||
let mesh_edge_black_pipeline = make_edge_pipeline("mesh.edge_black.pipeline", "fs_edge_black");
|
||||
|
||||
// Depth-only variant — TriangleList, back-face culling stays on
|
||||
// (we only want front-facing fragments to write depth so wires
|
||||
@@ -1057,6 +1065,7 @@ impl Pipeline {
|
||||
mesh_transparent_pipeline,
|
||||
mesh_highlight_pipeline,
|
||||
mesh_wireframe_pipeline,
|
||||
mesh_edge_black_pipeline,
|
||||
mesh_depth_pipeline,
|
||||
face3d_pipeline,
|
||||
face3d_depth_pipeline,
|
||||
@@ -1642,23 +1651,8 @@ impl Pipeline {
|
||||
}
|
||||
pass.set_pipeline(&self.mesh_wireframe_pipeline);
|
||||
for c in &self.gpu_mesh_batch {
|
||||
if c.wire_index_count == 0 {
|
||||
continue;
|
||||
}
|
||||
pass.set_vertex_buffer(0, c.vertex_buffer.slice(..));
|
||||
pass.set_index_buffer(
|
||||
c.wire_index_buffer.slice(..),
|
||||
wgpu::IndexFormat::Uint32,
|
||||
);
|
||||
pass.draw_indexed(0..c.wire_index_count, 0, 0..1);
|
||||
}
|
||||
} else {
|
||||
if mesh_wireframe {
|
||||
pass.set_pipeline(&self.mesh_wireframe_pipeline);
|
||||
for c in &self.gpu_mesh_batch {
|
||||
if c.wire_index_count == 0 {
|
||||
continue;
|
||||
}
|
||||
// Plain-mesh triangulation edges.
|
||||
if c.wire_index_count != 0 {
|
||||
pass.set_vertex_buffer(0, c.vertex_buffer.slice(..));
|
||||
pass.set_index_buffer(
|
||||
c.wire_index_buffer.slice(..),
|
||||
@@ -1666,6 +1660,29 @@ impl Pipeline {
|
||||
);
|
||||
pass.draw_indexed(0..c.wire_index_count, 0, 0..1);
|
||||
}
|
||||
// ACIS solid B-rep feature edges (LineList, non-indexed).
|
||||
if c.edge_vertex_count != 0 {
|
||||
pass.set_vertex_buffer(0, c.edge_vertex_buffer.slice(..));
|
||||
pass.draw(0..c.edge_vertex_count, 0..1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if mesh_wireframe {
|
||||
pass.set_pipeline(&self.mesh_wireframe_pipeline);
|
||||
for c in &self.gpu_mesh_batch {
|
||||
if c.wire_index_count != 0 {
|
||||
pass.set_vertex_buffer(0, c.vertex_buffer.slice(..));
|
||||
pass.set_index_buffer(
|
||||
c.wire_index_buffer.slice(..),
|
||||
wgpu::IndexFormat::Uint32,
|
||||
);
|
||||
pass.draw_indexed(0..c.wire_index_count, 0, 0..1);
|
||||
}
|
||||
if c.edge_vertex_count != 0 {
|
||||
pass.set_vertex_buffer(0, c.edge_vertex_buffer.slice(..));
|
||||
pass.draw(0..c.edge_vertex_count, 0..1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Opaque fills first (they write depth).
|
||||
pass.set_pipeline(&self.mesh_pipeline);
|
||||
@@ -1707,21 +1724,24 @@ impl Pipeline {
|
||||
pass.draw_indexed(0..g.index_count, 0, 0..1);
|
||||
}
|
||||
}
|
||||
// *WithEdges variants: overlay wire-edge segments on top of the
|
||||
// shaded fill. The LessEqual depth test on the wireframe pipeline
|
||||
// keeps the edges visible over the fragments the solid pass wrote.
|
||||
// *WithEdges variants: overlay edge segments on top of the shaded
|
||||
// fill in black (mesh_edge_black_pipeline). The LessEqual depth
|
||||
// test keeps the edges visible over the fragments the fill wrote.
|
||||
if want_solid_with_edges {
|
||||
pass.set_pipeline(&self.mesh_wireframe_pipeline);
|
||||
pass.set_pipeline(&self.mesh_edge_black_pipeline);
|
||||
for c in &self.gpu_mesh_batch {
|
||||
if c.wire_index_count == 0 {
|
||||
continue;
|
||||
if c.wire_index_count != 0 {
|
||||
pass.set_vertex_buffer(0, c.vertex_buffer.slice(..));
|
||||
pass.set_index_buffer(
|
||||
c.wire_index_buffer.slice(..),
|
||||
wgpu::IndexFormat::Uint32,
|
||||
);
|
||||
pass.draw_indexed(0..c.wire_index_count, 0, 0..1);
|
||||
}
|
||||
if c.edge_vertex_count != 0 {
|
||||
pass.set_vertex_buffer(0, c.edge_vertex_buffer.slice(..));
|
||||
pass.draw(0..c.edge_vertex_count, 0..1);
|
||||
}
|
||||
pass.set_vertex_buffer(0, c.vertex_buffer.slice(..));
|
||||
pass.set_index_buffer(
|
||||
c.wire_index_buffer.slice(..),
|
||||
wgpu::IndexFormat::Uint32,
|
||||
);
|
||||
pass.draw_indexed(0..c.wire_index_count, 0, 0..1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,3 +79,17 @@ fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
|
||||
let rgb = in.color.rgb * clamp(diff, 0.0, 1.0);
|
||||
return vec4<f32>(rgb, in.color.a);
|
||||
}
|
||||
|
||||
// Edge fragment (LineList): flat entity colour, no lighting. Used for the
|
||||
// wireframe and hidden-line edge passes so lines read at their true colour.
|
||||
@fragment
|
||||
fn fs_edge(in: VertexOut) -> @location(0) vec4<f32> {
|
||||
return vec4<f32>(in.color.rgb, 1.0);
|
||||
}
|
||||
|
||||
// Edge fragment for filled render modes: force black so edges frame the shaded
|
||||
// fill regardless of the solid's colour.
|
||||
@fragment
|
||||
fn fs_edge_black(in: VertexOut) -> @location(0) vec4<f32> {
|
||||
return vec4<f32>(0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
Reference in new issue
Block a user