feat(viewport): model space renders through active_viewports (step 4-5a/8)

build_viewports, active_viewports'un ViewportInstance listesinden
viewport başına ViewportData üretiyor (model: tüm model wire'ları;
paper viewport: layer-freeze'li alt küme). Model space artık
ViewportPane::model -> build_viewports üzerinden birleşik çoklu-viewport
yolundan render ediliyor — şimdilik tek tam-ekran viewport, davranış
birebir aynı.

Paper space hâlâ eski yolda (PaperCanvas + PaperViewportPane); sonraki
adımda birleşik yola taşınacak.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.7 committed 2026-05-27 19:55:49 +03:00
1 parent b073421b92
commit c83726c037
2 files changed
+132 -7

No files matched your search

+125 -1
View File
@@ -14,7 +14,7 @@ use std::sync::Arc;
use super::pipeline::viewcube::{hover_id, VIEWCUBE_PX};
use super::pipeline::MultiPipeline;
use super::tess_util;
use super::{HatchModel, ImageModel, MeshLodSet, Scene, Uniforms, WireModel};
use super::{HatchModel, ImageModel, MeshLodSet, Scene, Uniforms, ViewportInstance, WireModel};
// ── PaperViewportPipeline / PaperViewportPrimitive ────────────────────────
//
@@ -550,6 +550,130 @@ impl Scene {
}
}
/// Build the unified multi-viewport `Primitive` for the current layout.
/// Model layout → one full-window viewport (more once tiled); paper
/// layout → one viewport per floating content viewport. Each entry is
/// rendered into its own screen rectangle by its own inner pipeline.
pub(super) fn build_viewports(
&self,
bounds: Rectangle,
model_render_mode: acadrust::entities::ViewportRenderMode,
hover_region: Option<usize>,
) -> Primitive {
self.selection.borrow_mut().vp_size = (bounds.width, bounds.height);
if bounds.height > 0.0 {
self.set_render_aspect(bounds.width / bounds.height);
self.set_render_pixel_scale(bounds.width, bounds.height);
}
let canvas = (bounds.width.max(1.0), bounds.height.max(1.0));
let instances = self.active_viewports(canvas.0, canvas.1, model_render_mode);
let bg_color = if self.current_layout == "Model" {
self.bg_color
} else {
self.paper_bg_color
};
let viewports: Vec<ViewportData> = instances
.iter()
.map(|inst| self.viewport_data_for(inst, canvas, hover_region))
.collect();
Primitive {
viewports: if viewports.is_empty() {
// Paper layout with no content viewports still needs a
// (blank) pass so the widget clears to the sheet colour.
vec![self.viewport_data_for(
&ViewportInstance {
handle: acadrust::Handle::NULL,
screen_rect: Rectangle {
x: 0.0,
y: 0.0,
width: canvas.0,
height: canvas.1,
},
camera: self.camera.borrow().clone(),
render_mode: model_render_mode,
active: false,
},
canvas,
None,
)]
} else {
viewports
},
bg_color,
}
}
/// Build one `ViewportData` from a `ViewportInstance`: gathers the
/// viewport's geometry (full model for the Model view / `Handle::NULL`,
/// or the layer-frozen subset for a paper viewport), its camera
/// uniforms, and the normalized screen rectangle.
fn viewport_data_for(
&self,
inst: &ViewportInstance,
canvas: (f32, f32),
hover_region: Option<usize>,
) -> ViewportData {
let flags = render_mode_flags(inst.render_mode);
let view_wireframe = !flags.face3d_fill;
let base_arc = if inst.handle == acadrust::Handle::NULL {
self.entity_wires_arc()
} else {
self.model_wires_for_viewport_arc(inst.handle)
};
let (face3d_wires, other_wires) = split_face3d_wires(&base_arc, &self.document);
let all_wires = if self.interim_wire.is_none() && self.preview_wires.is_empty() {
Arc::new(other_wires)
} else {
let mut v = other_wires;
if let Some(iw) = &self.interim_wire {
v.push(iw.clone());
}
v.extend(self.preview_wires.iter().cloned());
Arc::new(v)
};
// Camera uniforms use the viewport's pixel size for aspect / ortho.
let bounds_px = Rectangle {
x: 0.0,
y: 0.0,
width: inst.screen_rect.width.max(1.0),
height: inst.screen_rect.height.max(1.0),
};
let mut uniforms =
Uniforms::new(&inst.camera, bounds_px, self.document.header.lineweight_display);
uniforms.flat_shade = if flags.flat_shade { 1.0 } else { 0.0 };
let screen_rect = Rectangle {
x: inst.screen_rect.x / canvas.0,
y: inst.screen_rect.y / canvas.1,
width: inst.screen_rect.width / canvas.0,
height: inst.screen_rect.height / canvas.1,
};
ViewportData {
wires: all_wires,
face3d_wires: Arc::new(face3d_wires),
hatches: self.hatch_models_arc(),
wipeout_hatches: self.wipeout_models_arc(),
images: self.images_arc(),
meshes: self.meshes_arc(),
uniforms,
cam_rotation: inst.camera.view_rotation_mat(),
// Only the active viewport gets the hovered-region highlight.
hover_region: if inst.active { hover_region } else { None },
show_viewcube: inst.active,
fill_mode: self.document.header.fill_mode,
view_wireframe,
mesh_fill: flags.mesh_fill,
show_3d_edges: flags.show_3d_edges,
hidden_line: flags.hidden_line,
geometry_epoch: self.geometry_epoch,
camera_generation: self.camera_generation,
screen_rect,
}
}
/// Build a Primitive that renders model-space content through a specific
/// paper-space viewport's camera, applying its layer-freeze list. The
/// render mode is read from the viewport entity itself (each paper-space
+7 -6
View File
@@ -141,12 +141,13 @@ impl<'a, Msg: std::fmt::Debug + Clone> shader::Program<Msg> for ViewportPane<'a>
bounds: Rectangle,
) -> Self::Primitive {
match &self.mode {
ViewportPaneMode::Model => self.scene.build_primitive(
state.hover_region,
bounds,
self.show_viewcube,
self.render_mode,
),
// Unified multi-viewport build: Model layout yields one
// full-window viewport (more once tiled); the camera, render
// mode and ViewCube come from `active_viewports`.
ViewportPaneMode::Model => {
self.scene
.build_viewports(bounds, self.render_mode, state.hover_region)
}
ViewportPaneMode::Paper { handle } => {
self.scene
.build_viewport_primitive(*handle, state.hover_region, bounds, false)