forked from 1b-agent/opencadstudio-upstream
feat(render): wireframe modes hide mesh & 3D fills
Wireframe 2D/3D modlarında Solid3D meshlerini edge-only çizmek için LineList topolojili bir mesh wireframe pipeline ve her üçgenin (a,b),(b,c),(c,a) kenarlarına genişletilmiş ayrı bir index buffer eklendi. PolyfaceMesh / PolygonMesh face fill'leri (fill_tris + dolu points) wireframe'de gizleniyor; text greek / MultiLeader gibi 2D fill'ler (points boş) etkilenmiyor. Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
1 parent
2145ab8a66
commit
8dbdebeedc
4 files changed
+131
-12
No files matched your search
@@ -62,6 +62,7 @@ impl Face3DGpu {
|
||||
device: &wgpu::Device,
|
||||
face3d_wires: &[WireModel],
|
||||
all_wires: &[WireModel],
|
||||
keep_3d_mesh_fills: bool,
|
||||
) -> Self {
|
||||
let mut vertices: Vec<Face3DVertex> = Vec::with_capacity(face3d_wires.len() * 6);
|
||||
|
||||
@@ -91,10 +92,20 @@ impl Face3DGpu {
|
||||
// AO-style dim to them would wash out user-picked colors. Wires
|
||||
// with both fill_tris and points (mesh edges + faces) keep the dim
|
||||
// so PolyfaceMesh / PolygonMesh still look 3-D-shaded.
|
||||
//
|
||||
// When `keep_3d_mesh_fills` is false (wireframe render modes),
|
||||
// skip wires that carry 3-D mesh face data (`points` + `fill_tris`)
|
||||
// while leaving 2-D fills (text greek, MultiLeader background)
|
||||
// alone — they're not "3D objects" the wireframe toggle should
|
||||
// hide.
|
||||
for wire in all_wires {
|
||||
if wire.fill_tris.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let is_3d_mesh_face = !wire.points.is_empty();
|
||||
if is_3d_mesh_face && !keep_3d_mesh_fills {
|
||||
continue;
|
||||
}
|
||||
let [r, g, b, a] = wire.color;
|
||||
let fill_color = if wire.points.is_empty() {
|
||||
[r, g, b, a]
|
||||
|
||||
@@ -53,6 +53,13 @@ pub struct MeshGpu {
|
||||
pub vertex_buffer: wgpu::Buffer,
|
||||
pub index_buffer: wgpu::Buffer,
|
||||
pub index_count: u32,
|
||||
/// Line-list index buffer: every triangle `(a, b, c)` from the
|
||||
/// solid index buffer is expanded into three segments
|
||||
/// `(a,b)(b,c)(c,a)`. Used by the wireframe-mode render path so 3D
|
||||
/// solids draw as their triangle edges without needing the
|
||||
/// `POLYGON_MODE_LINE` device feature.
|
||||
pub wire_index_buffer: wgpu::Buffer,
|
||||
pub wire_index_count: u32,
|
||||
}
|
||||
|
||||
/// GPU-side bundle of MeshLodSet — one MeshGpu per available LOD plus
|
||||
@@ -109,10 +116,27 @@ impl MeshGpu {
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
});
|
||||
|
||||
// Wireframe-mode index buffer: expand each triangle into its
|
||||
// three edge segments. Allocates ~2× the solid index count but
|
||||
// is cheap compared to mesh tessellation and only happens when
|
||||
// a new mesh is uploaded.
|
||||
let mut wire_indices: Vec<u32> = Vec::with_capacity(mesh.indices.len() * 2);
|
||||
for tri in mesh.indices.chunks_exact(3) {
|
||||
let (a, b, c) = (tri[0], tri[1], tri[2]);
|
||||
wire_indices.extend_from_slice(&[a, b, b, c, c, a]);
|
||||
}
|
||||
let wire_index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: Some(&format!("mesh.wire_ibuf.{}", mesh.name)),
|
||||
contents: bytemuck::cast_slice(&wire_indices),
|
||||
usage: wgpu::BufferUsages::INDEX,
|
||||
});
|
||||
|
||||
Self {
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
index_count: mesh.indices.len() as u32,
|
||||
wire_index_buffer,
|
||||
wire_index_count: wire_indices.len() as u32,
|
||||
}
|
||||
}
|
||||
}
|
||||
+91
-11
@@ -38,6 +38,11 @@ pub struct Pipeline {
|
||||
hatch_batched_pipeline: wgpu::RenderPipeline,
|
||||
image_pipeline: wgpu::RenderPipeline,
|
||||
mesh_pipeline: wgpu::RenderPipeline,
|
||||
/// Wireframe variant of the mesh pipeline (LineList topology, same
|
||||
/// vertex layout / shader). Used when the active render mode is
|
||||
/// Wireframe 2D or Wireframe 3D so 3D solids draw as their
|
||||
/// triangle edges instead of filled faces.
|
||||
mesh_wireframe_pipeline: wgpu::RenderPipeline,
|
||||
face3d_pipeline: wgpu::RenderPipeline,
|
||||
uniform_buffer: wgpu::Buffer,
|
||||
uniform_bind_group: wgpu::BindGroup,
|
||||
@@ -421,6 +426,51 @@ impl Pipeline {
|
||||
cache: None,
|
||||
});
|
||||
|
||||
// Wireframe variant — same shader / vertex layout / depth state,
|
||||
// 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 =
|
||||
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: Some("mesh.wireframe.pipeline"),
|
||||
layout: Some(&mesh_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &mesh_shader,
|
||||
entry_point: Some("vs_main"),
|
||||
buffers: &[mesh_gpu::MeshVertex::layout()],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
},
|
||||
primitive: wgpu::PrimitiveState {
|
||||
topology: wgpu::PrimitiveTopology::LineList,
|
||||
cull_mode: None,
|
||||
..Default::default()
|
||||
},
|
||||
depth_stencil: Some(wgpu::DepthStencilState {
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: wgpu::CompareFunction::LessEqual,
|
||||
stencil: wgpu::StencilState::default(),
|
||||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: MSAA_SAMPLES,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
fragment: Some(wgpu::FragmentState {
|
||||
module: &mesh_shader,
|
||||
entry_point: Some("fs_main"),
|
||||
targets: &[Some(wgpu::ColorTargetState {
|
||||
format,
|
||||
blend: Some(wgpu::BlendState::ALPHA_BLENDING),
|
||||
write_mask: wgpu::ColorWrites::ALL,
|
||||
})],
|
||||
compilation_options: wgpu::PipelineCompilationOptions::default(),
|
||||
}),
|
||||
multiview: None,
|
||||
cache: None,
|
||||
});
|
||||
|
||||
// ── Face3D pipeline ────────────────────────────────────────────────
|
||||
let face3d_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: Some("face3d.shader"),
|
||||
@@ -666,6 +716,7 @@ impl Pipeline {
|
||||
hatch_batched_pipeline,
|
||||
image_pipeline,
|
||||
mesh_pipeline,
|
||||
mesh_wireframe_pipeline,
|
||||
face3d_pipeline,
|
||||
uniform_buffer,
|
||||
uniform_bind_group,
|
||||
@@ -790,18 +841,32 @@ impl Pipeline {
|
||||
// outlines stay on the screen regardless of mode.
|
||||
self.gpu_face3d_edges = WireGpu::from_batch(device, face3d_wires);
|
||||
// Fill buffer: in wireframe mode we drop the 3DFACE quads
|
||||
// (those come from `face3d_wires.key_vertices`) but keep
|
||||
// `all_wires.fill_tris` — that channel carries 2D content like
|
||||
// text-LOD greek rectangles and PolyfaceMesh face triangles
|
||||
// which shouldn't disappear with the toggle.
|
||||
// (those come from `face3d_wires.key_vertices`) *and* the
|
||||
// PolyfaceMesh / PolygonMesh face triangles routed through
|
||||
// `all_wires.fill_tris` (Face3DGpu::from_wires recognises them
|
||||
// by the wire having both `points` and `fill_tris`). 2-D
|
||||
// fill_tris (text-LOD greek, MultiLeader background — `points`
|
||||
// empty) survive in either mode.
|
||||
let keep_3d_mesh_fills = !wireframe_only;
|
||||
let face3d_for_fill: &[WireModel] = if wireframe_only { &[] } else { face3d_wires };
|
||||
let has_any_2d_fill = all_wires
|
||||
.iter()
|
||||
.any(|w| !w.fill_tris.is_empty() && w.points.is_empty());
|
||||
let has_any_3d_fill = all_wires
|
||||
.iter()
|
||||
.any(|w| !w.fill_tris.is_empty() && !w.points.is_empty());
|
||||
let has_fills = !face3d_for_fill.is_empty()
|
||||
|| all_wires.iter().any(|w| !w.fill_tris.is_empty());
|
||||
|| has_any_2d_fill
|
||||
|| (keep_3d_mesh_fills && has_any_3d_fill);
|
||||
if !has_fills {
|
||||
self.gpu_face3d_fill = None;
|
||||
} else {
|
||||
self.gpu_face3d_fill =
|
||||
Some(Face3DGpu::from_wires(device, face3d_for_fill, all_wires));
|
||||
self.gpu_face3d_fill = Some(Face3DGpu::from_wires(
|
||||
device,
|
||||
face3d_for_fill,
|
||||
all_wires,
|
||||
keep_3d_mesh_fills,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -870,6 +935,7 @@ impl Pipeline {
|
||||
target: &wgpu::TextureView,
|
||||
clip_bounds: Rectangle<u32>,
|
||||
bg_color: [f32; 4],
|
||||
mesh_wireframe: bool,
|
||||
) {
|
||||
let vp = clip_bounds;
|
||||
let msaa = &self.msaa_view;
|
||||
@@ -997,7 +1063,16 @@ impl Pipeline {
|
||||
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.mesh_pipeline);
|
||||
// Wireframe mode swaps in the LineList pipeline + the
|
||||
// per-mesh `wire_index_buffer` (triangle indices expanded
|
||||
// into edge segments at upload time); everything else stays
|
||||
// identical so the same uniforms / vertex buffers feed both
|
||||
// paths.
|
||||
if mesh_wireframe {
|
||||
pass.set_pipeline(&self.mesh_wireframe_pipeline);
|
||||
} else {
|
||||
pass.set_pipeline(&self.mesh_pipeline);
|
||||
}
|
||||
pass.set_bind_group(0, &self.uniform_bind_group, &[]);
|
||||
for (i, set) in self.gpu_meshes.iter().enumerate() {
|
||||
if !self.mesh_visible.get(i).copied().unwrap_or(true) {
|
||||
@@ -1012,12 +1087,17 @@ impl Pipeline {
|
||||
let Some(mesh) = set.lods.get(level) else {
|
||||
continue;
|
||||
};
|
||||
if mesh.index_count == 0 {
|
||||
let (ibuf, icount) = if mesh_wireframe {
|
||||
(&mesh.wire_index_buffer, mesh.wire_index_count)
|
||||
} else {
|
||||
(&mesh.index_buffer, mesh.index_count)
|
||||
};
|
||||
if icount == 0 {
|
||||
continue;
|
||||
}
|
||||
pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
|
||||
pass.set_index_buffer(mesh.index_buffer.slice(..), wgpu::IndexFormat::Uint32);
|
||||
pass.draw_indexed(0..mesh.index_count, 0, 0..1);
|
||||
pass.set_index_buffer(ibuf.slice(..), wgpu::IndexFormat::Uint32);
|
||||
pass.draw_indexed(0..icount, 0, 0..1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-1
@@ -247,7 +247,11 @@ impl shader::Primitive for Primitive {
|
||||
target: &iced::wgpu::TextureView,
|
||||
clip: &Rectangle<u32>,
|
||||
) {
|
||||
pipeline.render(encoder, target, *clip, self.bg_color);
|
||||
// `mesh_fill` is false for Wireframe 2D / Wireframe 3D — flip
|
||||
// the draw path so meshes use the wireframe pipeline + the
|
||||
// pre-built triangle-edge index buffer.
|
||||
let mesh_wireframe = !self.mesh_fill;
|
||||
pipeline.render(encoder, target, *clip, self.bg_color, mesh_wireframe);
|
||||
if self.show_viewcube {
|
||||
pipeline.viewcube.render(encoder, target, *clip);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user