mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-24 20:00:02 +00:00
viewport: wire up the backface-culling setting
The "Backface Culling" checkbox persisted a value and reflected it, but nothing consumed AppSettings::backfaceCulling — the opaque pipeline hardcoded cullMode = Back, so toggling had no effect. Build a second opaque pipeline (cullMode None) alongside the culled one and pick between them per-frame from a backface_culling_ flag; setBackfaceCulling flips the flag and requests a redraw (no rebuild). ViewportWindow forwards it, and MainWindow applies the persisted value at startup and re-applies on change — same wiring as the nav preset. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -510,6 +510,15 @@ void MainWindow::setupStatus() {
|
|||||||
vp->applyNavPreset(AppSettings::navPresetName(preset));
|
vp->applyNavPreset(AppSettings::navPresetName(preset));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Backface culling: apply the persisted choice and re-apply live on change.
|
||||||
|
if (auto* vp = viewport_widget_->viewport())
|
||||||
|
vp->setBackfaceCulling(AppSettings::instance().backfaceCulling());
|
||||||
|
connect(&AppSettings::instance(), &AppSettings::backfaceCullingChanged, this,
|
||||||
|
[this](bool enabled) {
|
||||||
|
if (auto* vp = viewport_widget_->viewport())
|
||||||
|
vp->setBackfaceCulling(enabled);
|
||||||
|
});
|
||||||
|
|
||||||
connect(session_state_, &bonsaiviewer::SessionState::statusMessageChanged,
|
connect(session_state_, &bonsaiviewer::SessionState::statusMessageChanged,
|
||||||
this, [this](const QString& mode, const QString& detail) {
|
this, [this](const QString& mode, const QString& detail) {
|
||||||
status_mode_label_->setText(mode);
|
status_mode_label_->setText(mode);
|
||||||
|
|||||||
@@ -479,6 +479,12 @@ void ViewportCore::setNavPreset(const char* name) {
|
|||||||
nav_bindings_ = { B::Middle, M::Plain, B::Middle, M::Shift, B::Left, M::Plain };
|
nav_bindings_ = { B::Middle, M::Plain, B::Middle, M::Shift, B::Left, M::Plain };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ViewportCore::setBackfaceCulling(bool enabled) {
|
||||||
|
if (backface_culling_ == enabled) return;
|
||||||
|
backface_culling_ = enabled;
|
||||||
|
host_->requestFrame();
|
||||||
|
}
|
||||||
|
|
||||||
bool ViewportCore::frameSelection() {
|
bool ViewportCore::frameSelection() {
|
||||||
if (selection_.count() == 0) return false;
|
if (selection_.count() == 0) return false;
|
||||||
float lo[3] = { std::numeric_limits<float>::infinity(),
|
float lo[3] = { std::numeric_limits<float>::infinity(),
|
||||||
@@ -1150,6 +1156,19 @@ bool ViewportCore::buildPipelines() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- Backface-culling-off variant of the opaque pipeline -----------
|
||||||
|
// The "Backface Culling" setting picks between this and main_pipeline_ at
|
||||||
|
// draw time (opaque pass). Identical but cullMode None, so single-sided
|
||||||
|
// IFC meshes show their back faces.
|
||||||
|
WGPURenderPipelineDescriptor rp_desc_nc = rp_desc;
|
||||||
|
rp_desc_nc.label = svFromCStr("ifcviewer-wgpu.main_pipeline_no_cull");
|
||||||
|
rp_desc_nc.primitive.cullMode = WGPUCullMode_None;
|
||||||
|
main_pipeline_no_cull_ = wgpuDeviceCreateRenderPipeline(device_, &rp_desc_nc);
|
||||||
|
if (!main_pipeline_no_cull_) {
|
||||||
|
Log::warn() << "wgpu main no-cull render pipeline creation failed";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Transparent variant of the main pipeline ----------------------
|
// ---- Transparent variant of the main pipeline ----------------------
|
||||||
// Same shader, same layout, same vertex pulling, same depth test —
|
// Same shader, same layout, same vertex pulling, same depth test —
|
||||||
// differs only in:
|
// differs only in:
|
||||||
@@ -1796,6 +1815,7 @@ void ViewportCore::shutdown() {
|
|||||||
if (selection_flags_buffer_) { wgpuBufferRelease(selection_flags_buffer_); selection_flags_buffer_ = nullptr; }
|
if (selection_flags_buffer_) { wgpuBufferRelease(selection_flags_buffer_); selection_flags_buffer_ = nullptr; }
|
||||||
selection_flags_capacity_ = 0;
|
selection_flags_capacity_ = 0;
|
||||||
if (main_pipeline_) { wgpuRenderPipelineRelease(main_pipeline_); main_pipeline_ = nullptr; }
|
if (main_pipeline_) { wgpuRenderPipelineRelease(main_pipeline_); main_pipeline_ = nullptr; }
|
||||||
|
if (main_pipeline_no_cull_) { wgpuRenderPipelineRelease(main_pipeline_no_cull_); main_pipeline_no_cull_ = nullptr; }
|
||||||
if (main_pipeline_transparent_) { wgpuRenderPipelineRelease(main_pipeline_transparent_); main_pipeline_transparent_ = nullptr; }
|
if (main_pipeline_transparent_) { wgpuRenderPipelineRelease(main_pipeline_transparent_); main_pipeline_transparent_ = nullptr; }
|
||||||
section_gizmo_.destroy();
|
section_gizmo_.destroy();
|
||||||
if (main_shader_module_) { wgpuShaderModuleRelease(main_shader_module_); main_shader_module_ = nullptr; }
|
if (main_shader_module_) { wgpuShaderModuleRelease(main_shader_module_); main_shader_module_ = nullptr; }
|
||||||
@@ -6400,9 +6420,10 @@ void ViewportCore::render() {
|
|||||||
WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(enc, &pass_desc);
|
WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(enc, &pass_desc);
|
||||||
|
|
||||||
// Two-pass main render: opaque first, then transparent.
|
// Two-pass main render: opaque first, then transparent.
|
||||||
if (main_pipeline_ && main_pipeline_transparent_
|
if (main_pipeline_ && main_pipeline_no_cull_ && main_pipeline_transparent_
|
||||||
&& frame_bind_group_ && !models_gpu_.empty()) {
|
&& frame_bind_group_ && !models_gpu_.empty()) {
|
||||||
wgpuRenderPassEncoderSetPipeline(pass, main_pipeline_);
|
wgpuRenderPassEncoderSetPipeline(pass,
|
||||||
|
backface_culling_ ? main_pipeline_ : main_pipeline_no_cull_);
|
||||||
wgpuRenderPassEncoderSetBindGroup(pass, 0, frame_bind_group_, 0, nullptr);
|
wgpuRenderPassEncoderSetBindGroup(pass, 0, frame_bind_group_, 0, nullptr);
|
||||||
|
|
||||||
for (const auto& [session_model_id, m] : models_gpu_) {
|
for (const auto& [session_model_id, m] : models_gpu_) {
|
||||||
|
|||||||
@@ -223,6 +223,12 @@ public:
|
|||||||
void setNavPreset(const char* name);
|
void setNavPreset(const char* name);
|
||||||
const NavBindings& navBindings() const { return nav_bindings_; }
|
const NavBindings& navBindings() const { return nav_bindings_; }
|
||||||
|
|
||||||
|
// Toggle backface culling of opaque geometry. Off draws back faces too
|
||||||
|
// (useful for single-sided IFC meshes). Switches the opaque pipeline at
|
||||||
|
// draw time — no rebuild.
|
||||||
|
void setBackfaceCulling(bool enabled);
|
||||||
|
bool backfaceCulling() const { return backface_culling_; }
|
||||||
|
|
||||||
// Frame the current selection: union the selected objects' world AABBs and
|
// Frame the current selection: union the selected objects' world AABBs and
|
||||||
// fit the camera to them (same 1.30 padding as the desktop "F" hotkey).
|
// fit the camera to them (same 1.30 padding as the desktop "F" hotkey).
|
||||||
// No-op with an empty selection or no resolvable AABBs; returns whether it
|
// No-op with an empty selection or no resolvable AABBs; returns whether it
|
||||||
@@ -909,7 +915,9 @@ private:
|
|||||||
WGPUBindGroupLayout model_bgl_ = nullptr; // group 1
|
WGPUBindGroupLayout model_bgl_ = nullptr; // group 1
|
||||||
WGPUPipelineLayout pipeline_layout_ = nullptr;
|
WGPUPipelineLayout pipeline_layout_ = nullptr;
|
||||||
WGPURenderPipeline main_pipeline_ = nullptr;
|
WGPURenderPipeline main_pipeline_ = nullptr;
|
||||||
|
WGPURenderPipeline main_pipeline_no_cull_ = nullptr; // backface culling off
|
||||||
WGPURenderPipeline main_pipeline_transparent_ = nullptr;
|
WGPURenderPipeline main_pipeline_transparent_ = nullptr;
|
||||||
|
bool backface_culling_ = true;
|
||||||
// Section-plane gizmo, shared by desktop + web (both render via render()).
|
// Section-plane gizmo, shared by desktop + web (both render via render()).
|
||||||
// Lifted out of the Qt-coupled OverlayRenderer so one identical gizmo draws
|
// Lifted out of the Qt-coupled OverlayRenderer so one identical gizmo draws
|
||||||
// everywhere; the desktop's OverlayRenderer no longer draws it.
|
// everywhere; the desktop's OverlayRenderer no longer draws it.
|
||||||
|
|||||||
@@ -1499,6 +1499,10 @@ void ViewportWindow::applyNavPreset(const char* name) {
|
|||||||
select_button_ = toQtBtn(b.select); select_mods_ = toQtMod(b.select_mod);
|
select_button_ = toQtBtn(b.select); select_mods_ = toQtMod(b.select_mod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ViewportWindow::setBackfaceCulling(bool enabled) {
|
||||||
|
core_.setBackfaceCulling(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
// One-shot framebuffer capture → PNG
|
// One-shot framebuffer capture → PNG
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -247,6 +247,7 @@ public:
|
|||||||
// Sources the shared binding table from ViewportCore; called from init
|
// Sources the shared binding table from ViewportCore; called from init
|
||||||
// (env / persisted setting) and live from the Settings dialog.
|
// (env / persisted setting) and live from the Settings dialog.
|
||||||
void applyNavPreset(const char* name);
|
void applyNavPreset(const char* name);
|
||||||
|
void setBackfaceCulling(bool enabled);
|
||||||
|
|
||||||
|
|
||||||
// Queue a one-shot framebuffer capture: the next rendered frame is
|
// Queue a one-shot framebuffer capture: the next rendered frame is
|
||||||
|
|||||||
Reference in New Issue
Block a user