forked from 1b-agent/opencadstudio-upstream
View backgrounds: reference blend, image adjust, layouts, cleanup, style name
- Gradients: the middle colour sits (horizon - 0.5) viewport widths from the centre and blends over height viewport heights each side; two colours blend over a viewport height each side. - Image backgrounds: offset in device pixels (right, down), negative scale mirrors, fitted with aspect ratio covers the viewport; the dialog keeps an image's offset and scale. - Layouts: a model-space view restored on a sheet asks for the viewport, enters it and gives it the view's camera, background and visual style; inside a viewport the view goes there; a paper-space view's background is kept on the sheet viewport but not drawn; floating viewports draw their background. - Background objects no view or viewport uses any more are erased; a view without a background restored where the viewport already is leaves the viewport's background. - The viewport's named visual style (Realistic, ...) shows by name and the render-mode picker records its style on the viewport record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1cf13971dc
commit
9ef8f4bedf
12 files changed
+349
-70
No files matched your search
@@ -891,7 +891,8 @@ impl OpenCADStudio {
|
|||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("Usage: VIEW DELETE <name>").as_ref());
|
.push_error(crate::t!("Usage: VIEW DELETE <name>").as_ref());
|
||||||
} else {
|
} else {
|
||||||
if self.tabs[i].scene.document.views.remove(&name).is_some() {
|
if let Some(view) = self.tabs[i].scene.document.views.remove(&name) {
|
||||||
|
crate::scene::erase_background(&mut self.tabs[i].scene.document, view.background_handle);
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_output(crate::tf!("View '{}' deleted.", name).as_ref());
|
.push_output(crate::tf!("View '{}' deleted.", name).as_ref());
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -53,12 +53,33 @@ impl OpenCADStudio {
|
|||||||
.to_string(),
|
.to_string(),
|
||||||
visual_style: name_of(&visual_styles, view.visual_style_handle),
|
visual_style: name_of(&visual_styles, view.visual_style_handle),
|
||||||
live_section: name_of(&live_sections, view.live_section_handle),
|
live_section: name_of(&live_sections, view.live_section_handle),
|
||||||
|
paper_space: view.paper_space,
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
let scene = &self.tabs[i].scene;
|
||||||
|
let sheet_viewports = (scene.current_layout != "Model" && scene.active_viewport.is_none()).then(|| {
|
||||||
|
scene
|
||||||
|
.layout_content_viewports()
|
||||||
|
.iter()
|
||||||
|
.filter_map(|handle| match document.get_entity(*handle) {
|
||||||
|
Some(codec::EntityType::Viewport(value)) => Some((
|
||||||
|
handle.value(),
|
||||||
|
[
|
||||||
|
value.center.x - value.width / 2.0,
|
||||||
|
value.center.y - value.height / 2.0,
|
||||||
|
value.center.x + value.width / 2.0,
|
||||||
|
value.center.y + value.height / 2.0,
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
ViewNames {
|
ViewNames {
|
||||||
views,
|
views,
|
||||||
visual_styles: visual_styles.into_iter().map(|(name, _)| name).collect(),
|
visual_styles: visual_styles.into_iter().map(|(name, _)| name).collect(),
|
||||||
live_sections: live_sections.into_iter().map(|(name, _)| name).collect(),
|
live_sections: live_sections.into_iter().map(|(name, _)| name).collect(),
|
||||||
|
sheet_viewports,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,16 +153,40 @@ impl OpenCADStudio {
|
|||||||
if !doomed.is_empty() {
|
if !doomed.is_empty() {
|
||||||
self.push_undo_snapshot(i, "-VIEW");
|
self.push_undo_snapshot(i, "-VIEW");
|
||||||
for name in &doomed {
|
for name in &doomed {
|
||||||
self.tabs[i].scene.document.views.remove(name);
|
let removed = self.tabs[i].scene.document.views.remove(name);
|
||||||
|
if let Some(view) = removed {
|
||||||
|
crate::scene::erase_background(&mut self.tabs[i].scene.document, view.background_handle);
|
||||||
|
}
|
||||||
self.tabs[i].scene.document.delete_layer_state(&snapshot_name(name));
|
self.tabs[i].scene.document.delete_layer_state(&snapshot_name(name));
|
||||||
}
|
}
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"RESTOREVP" => {
|
||||||
|
let (name, handle) = arg.split_once('\u{1f}').unwrap_or((arg, ""));
|
||||||
|
let Some(view) = self.tabs[i].scene.document.views.get(name).cloned() else {
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
let Some(handle) = u64::from_str_radix(handle, 16).ok().map(Handle::new) else {
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
// The reference enters the selected viewport and restores the
|
||||||
|
// view in it.
|
||||||
|
let task = self.update(Message::EnterViewport(handle));
|
||||||
|
self.restore_view_in_active_viewport(i, &view);
|
||||||
|
return task;
|
||||||
|
}
|
||||||
"RESTORE" => {
|
"RESTORE" => {
|
||||||
let Some(view) = self.tabs[i].scene.document.views.get(arg).cloned() else {
|
let Some(view) = self.tabs[i].scene.document.views.get(arg).cloned() else {
|
||||||
return Task::none();
|
return Task::none();
|
||||||
};
|
};
|
||||||
|
if self.tabs[i].scene.current_layout != "Model"
|
||||||
|
&& self.tabs[i].scene.active_viewport.is_some()
|
||||||
|
&& !view.paper_space
|
||||||
|
{
|
||||||
|
self.restore_view_in_active_viewport(i, &view);
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
let before = self.tabs[i].scene.active_gaze_dir();
|
let before = self.tabs[i].scene.active_gaze_dir();
|
||||||
self.tabs[i].scene.restore_named_view(&view);
|
self.tabs[i].scene.restore_named_view(&view);
|
||||||
self.apply_view_render_mode(i, &view);
|
self.apply_view_render_mode(i, &view);
|
||||||
@@ -250,6 +295,22 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A model-space view restored in the entered paper-space viewport, with
|
||||||
|
/// its background and visual style.
|
||||||
|
fn restore_view_in_active_viewport(&mut self, i: usize, view: &codec::tables::View) {
|
||||||
|
let Some(viewport) = self.tabs[i].scene.active_viewport else { return };
|
||||||
|
self.tabs[i].scene.restore_named_view_in_viewport(view, viewport);
|
||||||
|
if let Some(ObjectType::VisualStyle(style)) =
|
||||||
|
self.tabs[i].scene.document.objects.get(&view.visual_style_handle)
|
||||||
|
{
|
||||||
|
if let Some((mode, _)) = crate::modules::view::visual_style::mode_for_style_name(&style.description) {
|
||||||
|
self.tabs[i].scene.set_active_viewport_render_mode(mode);
|
||||||
|
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
/// Add or replace a named view, keeping the record (and so its settings)
|
/// Add or replace a named view, keeping the record (and so its settings)
|
||||||
/// of a view saved again under the same name.
|
/// of a view saved again under the same name.
|
||||||
fn save_named_view(&mut self, i: usize, mut view: codec::tables::View) {
|
fn save_named_view(&mut self, i: usize, mut view: codec::tables::View) {
|
||||||
@@ -257,6 +318,7 @@ impl OpenCADStudio {
|
|||||||
let background = self.tabs[i].scene.active_background();
|
let background = self.tabs[i].scene.active_background();
|
||||||
let document = &mut self.tabs[i].scene.document;
|
let document = &mut self.tabs[i].scene.document;
|
||||||
view.background_handle = crate::scene::copy_background(document, background);
|
view.background_handle = crate::scene::copy_background(document, background);
|
||||||
|
let mut replaced = Handle::NULL;
|
||||||
match document.views.get(&view.name).cloned() {
|
match document.views.get(&view.name).cloned() {
|
||||||
Some(old) => {
|
Some(old) => {
|
||||||
view.name = old.name;
|
view.name = old.name;
|
||||||
@@ -268,6 +330,7 @@ impl OpenCADStudio {
|
|||||||
if view.background_handle.is_null() {
|
if view.background_handle.is_null() {
|
||||||
view.background_handle = old.background_handle;
|
view.background_handle = old.background_handle;
|
||||||
}
|
}
|
||||||
|
replaced = old.background_handle;
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
view.handle = document.allocate_handle();
|
view.handle = document.allocate_handle();
|
||||||
@@ -280,6 +343,7 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
document.views.add_or_replace(view);
|
document.views.add_or_replace(view);
|
||||||
|
crate::scene::erase_background(document, replaced);
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -290,6 +354,7 @@ impl OpenCADStudio {
|
|||||||
};
|
};
|
||||||
self.push_undo_snapshot(i, "-VIEW");
|
self.push_undo_snapshot(i, "-VIEW");
|
||||||
let document = &mut self.tabs[i].scene.document;
|
let document = &mut self.tabs[i].scene.document;
|
||||||
|
let mut replaced = Handle::NULL;
|
||||||
if view.handle.is_null() {
|
if view.handle.is_null() {
|
||||||
view.handle = document.allocate_handle();
|
view.handle = document.allocate_handle();
|
||||||
}
|
}
|
||||||
@@ -297,7 +362,7 @@ impl OpenCADStudio {
|
|||||||
list.into_iter().find(|(entry, _)| entry.eq_ignore_ascii_case(value)).map_or(Handle::NULL, |(_, h)| h)
|
list.into_iter().find(|(entry, _)| entry.eq_ignore_ascii_case(value)).map_or(Handle::NULL, |(_, h)| h)
|
||||||
};
|
};
|
||||||
match setting {
|
match setting {
|
||||||
"BACKGROUND" => view.background_handle = Handle::NULL,
|
"BACKGROUND" => replaced = std::mem::replace(&mut view.background_handle, Handle::NULL),
|
||||||
"VISUALSTYLE" => view.visual_style_handle = find(crate::io::visual_styles::offered(document)),
|
"VISUALSTYLE" => view.visual_style_handle = find(crate::io::visual_styles::offered(document)),
|
||||||
"SECTION" => {
|
"SECTION" => {
|
||||||
let sections: Vec<(String, Handle)> = document
|
let sections: Vec<(String, Handle)> = document
|
||||||
@@ -351,6 +416,7 @@ impl OpenCADStudio {
|
|||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
document.views.add_or_replace(view);
|
document.views.add_or_replace(view);
|
||||||
|
crate::scene::erase_background(document, replaced);
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -381,6 +447,8 @@ impl OpenCADStudio {
|
|||||||
image: String::new(),
|
image: String::new(),
|
||||||
position: BgPosition::Center,
|
position: BgPosition::Center,
|
||||||
aspect: false,
|
aspect: false,
|
||||||
|
offset: [0.0, 0.0],
|
||||||
|
scale: [1.0, 1.0],
|
||||||
canvas: [0, 1, 2].map(|k| (canvas[k].clamp(0.0, 1.0) * 255.0).round() as u8),
|
canvas: [0, 1, 2].map(|k| (canvas[k].clamp(0.0, 1.0) * 255.0).round() as u8),
|
||||||
};
|
};
|
||||||
let document = &self.tabs[i].scene.document;
|
let document = &self.tabs[i].scene.document;
|
||||||
@@ -407,6 +475,8 @@ impl OpenCADStudio {
|
|||||||
BgPosition::Center
|
BgPosition::Center
|
||||||
};
|
};
|
||||||
state.aspect = value.maintain_aspect_ratio;
|
state.aspect = value.maintain_aspect_ratio;
|
||||||
|
state.offset = [value.offset.x, value.offset.y];
|
||||||
|
state.scale = [value.scale.x, value.scale.y];
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -518,8 +588,8 @@ impl OpenCADStudio {
|
|||||||
fit_to_screen: state.position == BgPosition::Stretch,
|
fit_to_screen: state.position == BgPosition::Stretch,
|
||||||
maintain_aspect_ratio: state.aspect,
|
maintain_aspect_ratio: state.aspect,
|
||||||
use_tiling: state.position == BgPosition::Tile,
|
use_tiling: state.position == BgPosition::Tile,
|
||||||
offset: codec::types::Vector2::new(0.0, 0.0),
|
offset: codec::types::Vector2::new(state.offset[0], state.offset[1]),
|
||||||
scale: codec::types::Vector2::new(1.0, 1.0),
|
scale: codec::types::Vector2::new(state.scale[0], state.scale[1]),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -533,7 +603,8 @@ impl OpenCADStudio {
|
|||||||
crate::scene::add_background(document, data)
|
crate::scene::add_background(document, data)
|
||||||
});
|
});
|
||||||
if let Some(view) = document.views.get_mut(&name) {
|
if let Some(view) = document.views.get_mut(&name) {
|
||||||
view.background_handle = handle;
|
let old = std::mem::replace(&mut view.background_handle, handle);
|
||||||
|
crate::scene::erase_background(document, old);
|
||||||
}
|
}
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
}
|
}
|
||||||
@@ -549,7 +620,6 @@ impl OpenCADStudio {
|
|||||||
/// A restored view's visual style becomes the model tab's render mode
|
/// A restored view's visual style becomes the model tab's render mode
|
||||||
/// (the closest of OCS's modes); a view without one leaves it.
|
/// (the closest of OCS's modes); a view without one leaves it.
|
||||||
pub(crate) fn apply_view_render_mode(&mut self, i: usize, view: &codec::tables::View) {
|
pub(crate) fn apply_view_render_mode(&mut self, i: usize, view: &codec::tables::View) {
|
||||||
use codec::entities::ViewportRenderMode as M;
|
|
||||||
if self.tabs[i].scene.current_layout != "Model" {
|
if self.tabs[i].scene.current_layout != "Model" {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -558,19 +628,12 @@ impl OpenCADStudio {
|
|||||||
else {
|
else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let (mode, label) = match style.description.as_str() {
|
use crate::modules::view::visual_style;
|
||||||
"2dWireframe" => (M::Wireframe2D, "Wireframe 2D"),
|
let Some((mode, named)) = visual_style::mode_for_style_name(&style.description) else {
|
||||||
"Wireframe" => (M::Wireframe3D, "Wireframe 3D"),
|
return;
|
||||||
"Hidden" => (M::HiddenLine, "Hidden Line"),
|
|
||||||
"Flat" => (M::FlatShaded, "Flat Shaded"),
|
|
||||||
"FlatWithEdges" => (M::FlatShadedWithEdges, "Flat Shaded + Edges"),
|
|
||||||
"GouraudWithEdges" | "Shaded with edges" => {
|
|
||||||
(M::GouraudShadedWithEdges, "Gouraud Shaded + Edges")
|
|
||||||
}
|
|
||||||
"Gouraud" | "Realistic" | "Shaded" | "Conceptual" | "Shades of Gray" | "Sketchy"
|
|
||||||
| "X-Ray" => (M::GouraudShaded, "Gouraud Shaded"),
|
|
||||||
_ => return,
|
|
||||||
};
|
};
|
||||||
self.apply_render_mode(i, mode, label);
|
// A style of its own keeps its name; the render modes keep theirs.
|
||||||
|
let label = if named { style.description.clone() } else { visual_style::label_for(mode).to_string() };
|
||||||
|
self.apply_render_mode(i, mode, &label);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1091,6 +1091,10 @@ impl OpenCADStudio {
|
|||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
self.apply_render_mode(i, mode, label);
|
self.apply_render_mode(i, mode, label);
|
||||||
|
// The viewport record carries the style the mode is saved as.
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.set_active_visual_style_named(crate::modules::view::visual_style::style_name_for(mode));
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_output(crate::tf!("Visual style: {label}").as_ref());
|
.push_output(crate::tf!("Visual style: {label}").as_ref());
|
||||||
// The point cloud move gizmo follows the visual style.
|
// The point cloud move gizmo follows the visual style.
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ pub(super) fn viewport_controls<'a>(
|
|||||||
tile_count: usize,
|
tile_count: usize,
|
||||||
render_mode_menu_open: bool,
|
render_mode_menu_open: bool,
|
||||||
render_mode_preview: Option<codec::entities::ViewportRenderMode>,
|
render_mode_preview: Option<codec::entities::ViewportRenderMode>,
|
||||||
|
style_name: Option<String>,
|
||||||
) -> Element<'a, Message> {
|
) -> Element<'a, Message> {
|
||||||
let render_modes: Vec<RenderModeChoice> = crate::modules::view::visual_style::VISUAL_STYLES
|
let render_modes: Vec<RenderModeChoice> = crate::modules::view::visual_style::VISUAL_STYLES
|
||||||
.iter()
|
.iter()
|
||||||
@@ -98,7 +99,7 @@ pub(super) fn viewport_controls<'a>(
|
|||||||
// flyout keeps the current style unchanged while hovering; a click commits.
|
// flyout keeps the current style unchanged while hovering; a click commits.
|
||||||
let picker_head = button(
|
let picker_head = button(
|
||||||
row![
|
row![
|
||||||
text(RenderModeChoice(render_mode).to_string()).size(11),
|
text(style_name.unwrap_or_else(|| RenderModeChoice(render_mode).to_string())).size(11),
|
||||||
crate::ui::icons::themed_arrow_toggle(render_mode_menu_open, 9.0),
|
crate::ui::icons::themed_arrow_toggle(render_mode_menu_open, 9.0),
|
||||||
]
|
]
|
||||||
.spacing(6)
|
.spacing(6)
|
||||||
@@ -379,7 +380,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_viewport_controls_construction() {
|
fn test_viewport_controls_construction() {
|
||||||
for style in crate::modules::view::visual_style::VISUAL_STYLES {
|
for style in crate::modules::view::visual_style::VISUAL_STYLES {
|
||||||
let _el = viewport_controls(style.mode, true, true, true, 1, false, None);
|
let _el = viewport_controls(style.mode, true, true, true, 1, false, None, None);
|
||||||
let _el_open = viewport_controls(
|
let _el_open = viewport_controls(
|
||||||
style.mode,
|
style.mode,
|
||||||
false,
|
false,
|
||||||
@@ -388,6 +389,7 @@ mod tests {
|
|||||||
2,
|
2,
|
||||||
true,
|
true,
|
||||||
Some(M::Wireframe3D),
|
Some(M::Wireframe3D),
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1297,6 +1297,18 @@ bg={bg_ms:.1}ms n={view_count}"
|
|||||||
let rect = tab.scene.active_model_tile_bounds(vw, vh);
|
let rect = tab.scene.active_model_tile_bounds(vw, vh);
|
||||||
// Unified control chip: split buttons + render-mode picker +
|
// Unified control chip: split buttons + render-mode picker +
|
||||||
// grid / grid-snap toggles, for the active Model tile.
|
// grid / grid-snap toggles, for the active Model tile.
|
||||||
|
// A named style (Realistic, …) the tile draws as shows by name.
|
||||||
|
let style_name = match tab.scene.document.objects.get(&tab.scene.active_visual_style()) {
|
||||||
|
Some(codec::objects::ObjectType::VisualStyle(style)) => {
|
||||||
|
match crate::modules::view::visual_style::mode_for_style_name(&style.description) {
|
||||||
|
Some((mode, true)) if mode == tab.render_mode => {
|
||||||
|
Some(crate::t!(style.description.as_str()).into_owned())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
let bar = viewport_controls(
|
let bar = viewport_controls(
|
||||||
tab.render_mode,
|
tab.render_mode,
|
||||||
self.show_grid,
|
self.show_grid,
|
||||||
@@ -1305,6 +1317,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
|||||||
tab.scene.model_tiles.borrow().len(),
|
tab.scene.model_tiles.borrow().len(),
|
||||||
self.render_mode_menu_open,
|
self.render_mode_menu_open,
|
||||||
self.render_mode_preview,
|
self.render_mode_preview,
|
||||||
|
style_name,
|
||||||
);
|
);
|
||||||
// Adaptive: DensitySwap measures the bar's real width every frame
|
// Adaptive: DensitySwap measures the bar's real width every frame
|
||||||
// (reported into `render_bar_w`, which the ViewCube reads to decide
|
// (reported into `render_bar_w`, which the ViewCube reads to decide
|
||||||
@@ -1397,6 +1410,7 @@ bg={bg_ms:.1}ms n={view_count}"
|
|||||||
0,
|
0,
|
||||||
self.render_mode_menu_open,
|
self.render_mode_menu_open,
|
||||||
self.render_mode_preview,
|
self.render_mode_preview,
|
||||||
|
None,
|
||||||
);
|
);
|
||||||
let adaptive: Element<'_, Message> = DensitySwap::new(vec![
|
let adaptive: Element<'_, Message> = DensitySwap::new(vec![
|
||||||
iced::widget::opaque(bar),
|
iced::widget::opaque(bar),
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ pub struct ViewInfo {
|
|||||||
pub category: String,
|
pub category: String,
|
||||||
pub visual_style: Option<String>,
|
pub visual_style: Option<String>,
|
||||||
pub live_section: Option<String>,
|
pub live_section: Option<String>,
|
||||||
|
/// Saved in paper space.
|
||||||
|
pub paper_space: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// What -VIEW checks typed names against.
|
/// What -VIEW checks typed names against.
|
||||||
@@ -65,6 +67,10 @@ pub struct ViewNames {
|
|||||||
pub views: Vec<ViewInfo>,
|
pub views: Vec<ViewInfo>,
|
||||||
pub visual_styles: Vec<String>,
|
pub visual_styles: Vec<String>,
|
||||||
pub live_sections: Vec<String>,
|
pub live_sections: Vec<String>,
|
||||||
|
/// On a layout's sheet (no viewport entered): the layout's viewports as
|
||||||
|
/// (handle, [min x, min y, max x, max y]), one of which takes a
|
||||||
|
/// model-space view being restored.
|
||||||
|
pub sheet_viewports: Option<Vec<(u64, [f64; 4])>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
@@ -106,6 +112,7 @@ enum Step {
|
|||||||
List,
|
List,
|
||||||
Delete,
|
Delete,
|
||||||
Restore,
|
Restore,
|
||||||
|
RestoreViewport(String),
|
||||||
Save,
|
Save,
|
||||||
WindowName,
|
WindowName,
|
||||||
WindowFirst(String),
|
WindowFirst(String),
|
||||||
@@ -350,6 +357,7 @@ impl CadCommand for DashViewCommand {
|
|||||||
Step::List | Step::SettingViewList(_) => "Enter view name(s) to list <*>:".into(),
|
Step::List | Step::SettingViewList(_) => "Enter view name(s) to list <*>:".into(),
|
||||||
Step::Delete => "Enter view name(s) to delete:".into(),
|
Step::Delete => "Enter view name(s) to delete:".into(),
|
||||||
Step::Restore => "Enter view name to restore:".into(),
|
Step::Restore => "Enter view name to restore:".into(),
|
||||||
|
Step::RestoreViewport(_) => "Select Viewport for view:".into(),
|
||||||
Step::Save | Step::WindowName => "Enter view name to save:".into(),
|
Step::Save | Step::WindowName => "Enter view name to save:".into(),
|
||||||
Step::WindowFirst(_) => "Specify first corner:".into(),
|
Step::WindowFirst(_) => "Specify first corner:".into(),
|
||||||
Step::WindowSecond(..) => "Specify opposite corner:".into(),
|
Step::WindowSecond(..) => "Specify opposite corner:".into(),
|
||||||
@@ -421,9 +429,19 @@ impl CadCommand for DashViewCommand {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Step::Restore => match self.view(t) {
|
Step::Restore => match self.view(t) {
|
||||||
|
// On a sheet a model-space view goes into a viewport the
|
||||||
|
// user selects.
|
||||||
|
Some(view) if !view.paper_space && self.names.sheet_viewports.is_some() => {
|
||||||
|
self.step = Step::RestoreViewport(view.name.clone());
|
||||||
|
CmdResult::ReportMeasurement("Restoring Model space view.".into())
|
||||||
|
}
|
||||||
Some(view) => CmdResult::Dispatch(format!("-VIEW >RESTORE {}", view.name)),
|
Some(view) => CmdResult::Dispatch(format!("-VIEW >RESTORE {}", view.name)),
|
||||||
None => Self::end(&format!("Cannot find view \"{t}\".")),
|
None => Self::end(&format!("Cannot find view \"{t}\".")),
|
||||||
},
|
},
|
||||||
|
Step::RestoreViewport(name) => {
|
||||||
|
self.step = Step::RestoreViewport(name);
|
||||||
|
CmdResult::ReportError("*Invalid selection*".into())
|
||||||
|
}
|
||||||
Step::Save => CmdResult::Dispatch(format!("-VIEW >SAVE {t}")),
|
Step::Save => CmdResult::Dispatch(format!("-VIEW >SAVE {t}")),
|
||||||
Step::WindowName => {
|
Step::WindowName => {
|
||||||
self.step = Step::WindowFirst(t.to_string());
|
self.step = Step::WindowFirst(t.to_string());
|
||||||
@@ -494,6 +512,19 @@ impl CadCommand for DashViewCommand {
|
|||||||
"-VIEW >WINDOW {}\u{1f}{},{}\u{1f}{},{}",
|
"-VIEW >WINDOW {}\u{1f}{},{}\u{1f}{},{}",
|
||||||
name, first.x, first.y, pt.x, pt.y
|
name, first.x, first.y, pt.x, pt.y
|
||||||
)),
|
)),
|
||||||
|
Step::RestoreViewport(name) => {
|
||||||
|
// The topmost viewport under the pick.
|
||||||
|
let hit = self.names.sheet_viewports.iter().flatten().rev().find(|(_, [x0, y0, x1, y1])| {
|
||||||
|
pt.x >= *x0 && pt.x <= *x1 && pt.y >= *y0 && pt.y <= *y1
|
||||||
|
});
|
||||||
|
match hit {
|
||||||
|
Some((handle, _)) => CmdResult::Dispatch(format!("-VIEW >RESTOREVP {name}\u{1f}{handle:X}")),
|
||||||
|
None => {
|
||||||
|
self.step = Step::RestoreViewport(name);
|
||||||
|
CmdResult::NeedPoint
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
step => {
|
step => {
|
||||||
self.step = step;
|
self.step = step;
|
||||||
CmdResult::NeedPoint
|
CmdResult::NeedPoint
|
||||||
@@ -503,7 +534,7 @@ impl CadCommand for DashViewCommand {
|
|||||||
|
|
||||||
fn on_enter(&mut self) -> CmdResult {
|
fn on_enter(&mut self) -> CmdResult {
|
||||||
match std::mem::replace(&mut self.step, Step::Main) {
|
match std::mem::replace(&mut self.step, Step::Main) {
|
||||||
Step::Main | Step::Delete | Step::Restore | Step::Save | Step::WindowName => {
|
Step::Main | Step::Delete | Step::Restore | Step::RestoreViewport(_) | Step::Save | Step::WindowName => {
|
||||||
CmdResult::Dispatch("-VIEW >END".into())
|
CmdResult::Dispatch("-VIEW >END".into())
|
||||||
}
|
}
|
||||||
Step::Orthographic => CmdResult::Dispatch("-VIEW >PRESET TOP".into()),
|
Step::Orthographic => CmdResult::Dispatch("-VIEW >PRESET TOP".into()),
|
||||||
|
|||||||
@@ -106,6 +106,38 @@ pub fn label_for(mode: Mode) -> &'static str {
|
|||||||
.unwrap_or("Wireframe 2D")
|
.unwrap_or("Wireframe 2D")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The render mode a named visual style draws as, and whether the name is a
|
||||||
|
/// style of its own (Realistic, Conceptual, …) rather than one of the modes.
|
||||||
|
pub fn mode_for_style_name(name: &str) -> Option<(Mode, bool)> {
|
||||||
|
Some(match name {
|
||||||
|
"2dWireframe" => (Mode::Wireframe2D, false),
|
||||||
|
"Wireframe" => (Mode::Wireframe3D, false),
|
||||||
|
"Hidden" => (Mode::HiddenLine, false),
|
||||||
|
"Flat" => (Mode::FlatShaded, false),
|
||||||
|
"FlatWithEdges" => (Mode::FlatShadedWithEdges, false),
|
||||||
|
"Gouraud" => (Mode::GouraudShaded, false),
|
||||||
|
"GouraudWithEdges" => (Mode::GouraudShadedWithEdges, false),
|
||||||
|
"Shaded with edges" => (Mode::GouraudShadedWithEdges, true),
|
||||||
|
"Realistic" | "Shaded" | "Conceptual" | "Shades of Gray" | "Sketchy" | "X-Ray" => {
|
||||||
|
(Mode::GouraudShaded, true)
|
||||||
|
}
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The name of the visual style a render mode is saved as.
|
||||||
|
pub fn style_name_for(mode: Mode) -> &'static str {
|
||||||
|
match mode {
|
||||||
|
Mode::Wireframe2D => "2dWireframe",
|
||||||
|
Mode::Wireframe3D => "Wireframe",
|
||||||
|
Mode::HiddenLine => "Hidden",
|
||||||
|
Mode::FlatShaded => "Flat",
|
||||||
|
Mode::FlatShadedWithEdges => "FlatWithEdges",
|
||||||
|
Mode::GouraudShaded => "Gouraud",
|
||||||
|
Mode::GouraudShadedWithEdges => "GouraudWithEdges",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The style a style keyword names. Only the seven exist; nothing maps onto a
|
/// The style a style keyword names. Only the seven exist; nothing maps onto a
|
||||||
/// nearest neighbour, because there is no longer anything else to map from.
|
/// nearest neighbour, because there is no longer anything else to map from.
|
||||||
pub fn mode_for_keyword(keyword: &str) -> Option<Mode> {
|
pub fn mode_for_keyword(keyword: &str) -> Option<Mode> {
|
||||||
|
|||||||
+134
-9
@@ -214,11 +214,28 @@ impl Scene {
|
|||||||
|
|
||||||
/// Restore camera to a named view from the document view table.
|
/// Restore camera to a named view from the document view table.
|
||||||
pub fn restore_named_view(&mut self, view: &codec::tables::View) {
|
pub fn restore_named_view(&mut self, view: &codec::tables::View) {
|
||||||
use glam::Vec3;
|
let before = {
|
||||||
// The model viewport takes a copy of the view's background, or none.
|
let cam = self.camera.borrow();
|
||||||
|
(cam.rotation, cam.target, cam.distance)
|
||||||
|
};
|
||||||
|
self.restore_named_view_camera(view);
|
||||||
|
let unchanged = {
|
||||||
|
let cam = self.camera.borrow();
|
||||||
|
cam.rotation.abs_diff_eq(before.0, 1e-6)
|
||||||
|
&& cam.target.abs_diff_eq(before.1, 1e-6 * before.1.length().max(1.0))
|
||||||
|
&& (cam.distance - before.2).abs() <= 1e-6 * before.2.abs().max(1.0)
|
||||||
|
};
|
||||||
|
// The model viewport takes a copy of the view's background; a view
|
||||||
|
// without one clears it, unless the view is what the viewport already
|
||||||
|
// shows (the reference leaves the viewport as it is then).
|
||||||
|
if self.current_layout != "Model" && self.active_viewport.is_none() && view.paper_space {
|
||||||
|
self.set_sheet_background(view);
|
||||||
|
}
|
||||||
if self.current_layout == "Model" {
|
if self.current_layout == "Model" {
|
||||||
let background = copy_background(&mut self.document, view.background_handle);
|
if view.background_handle.is_valid() || !unchanged {
|
||||||
self.set_active_background(background);
|
let background = copy_background(&mut self.document, view.background_handle);
|
||||||
|
self.set_active_background(background);
|
||||||
|
}
|
||||||
if view.visual_style_handle.is_valid() {
|
if view.visual_style_handle.is_valid() {
|
||||||
if let Some(handle) = self.active_tile_vport_handle() {
|
if let Some(handle) = self.active_tile_vport_handle() {
|
||||||
if let Some(value) = self.document.vports.iter_mut().find(|v| v.handle == handle) {
|
if let Some(value) = self.document.vports.iter_mut().find(|v| v.handle == handle) {
|
||||||
@@ -227,7 +244,50 @@ impl Scene {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let cam = &mut *self.camera.borrow_mut();
|
}
|
||||||
|
|
||||||
|
fn restore_named_view_camera(&mut self, view: &codec::tables::View) {
|
||||||
|
named_view_camera(view, &mut self.camera.borrow_mut());
|
||||||
|
self.camera_generation += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Restores a model-space view into a paper-space viewport: its camera,
|
||||||
|
/// a copy of the view's background (or none) and the view's visual style.
|
||||||
|
pub fn restore_named_view_in_viewport(&mut self, view: &codec::tables::View, viewport: Handle) {
|
||||||
|
let mut camera = self.navigation_camera();
|
||||||
|
named_view_camera(view, &mut camera);
|
||||||
|
self.apply_navigation_camera(camera);
|
||||||
|
let background = copy_background(&mut self.document, view.background_handle);
|
||||||
|
let mut old = Handle::NULL;
|
||||||
|
if let Some(EntityType::Viewport(value)) = self.document.get_entity_mut(viewport) {
|
||||||
|
old = std::mem::replace(&mut value.background_handle, background);
|
||||||
|
if view.visual_style_handle.is_valid() {
|
||||||
|
value.visual_style_handle = view.visual_style_handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
erase_background(&mut self.document, old);
|
||||||
|
self.notify_viewport_changed(viewport);
|
||||||
|
self.camera_generation += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The paper-space viewport of the current layout takes a copy of a
|
||||||
|
/// paper-space view's background (kept with the layout; the sheet itself
|
||||||
|
/// is not drawn over).
|
||||||
|
fn set_sheet_background(&mut self, view: &codec::tables::View) {
|
||||||
|
let sheet = self.current_layout_sheet_viewport_handle();
|
||||||
|
let background = copy_background(&mut self.document, view.background_handle);
|
||||||
|
let old = match self.document.get_entity_mut(sheet) {
|
||||||
|
Some(EntityType::Viewport(value)) => std::mem::replace(&mut value.background_handle, background),
|
||||||
|
_ => background,
|
||||||
|
};
|
||||||
|
erase_background(&mut self.document, old);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Points a camera along a named view: direction, target and height.
|
||||||
|
fn named_view_camera(view: &codec::tables::View, cam: &mut view::camera::Camera) {
|
||||||
|
use glam::Vec3;
|
||||||
|
{
|
||||||
// The stored direction points from the target toward the eye.
|
// The stored direction points from the target toward the eye.
|
||||||
let eye_dir = Vec3::new(
|
let eye_dir = Vec3::new(
|
||||||
view.direction.x as f32,
|
view.direction.x as f32,
|
||||||
@@ -254,22 +314,55 @@ impl Scene {
|
|||||||
} else {
|
} else {
|
||||||
cam.distance
|
cam.distance
|
||||||
};
|
};
|
||||||
self.camera_generation += 1;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Scene {
|
||||||
/// The background the active model-space viewport shows (null for the
|
/// The background the active model-space viewport shows (null for the
|
||||||
/// drawing area colour).
|
/// drawing area colour).
|
||||||
pub fn active_background(&self) -> Handle {
|
pub fn active_background(&self) -> Handle {
|
||||||
|
if self.current_layout != "Model" {
|
||||||
|
// Inside a viewport, that viewport's; the sheet's is not saved
|
||||||
|
// with a paper-space view.
|
||||||
|
return match self.active_viewport.and_then(|handle| self.document.get_entity(handle)) {
|
||||||
|
Some(EntityType::Viewport(value)) => value.background_handle,
|
||||||
|
_ => Handle::NULL,
|
||||||
|
};
|
||||||
|
}
|
||||||
self.active_tile_vport_handle()
|
self.active_tile_vport_handle()
|
||||||
.and_then(|handle| self.document.vports.iter().find(|value| value.handle == handle))
|
.and_then(|handle| self.document.vports.iter().find(|value| value.handle == handle))
|
||||||
.map_or(Handle::NULL, |value| value.background_handle)
|
.map_or(Handle::NULL, |value| value.background_handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The visual style of the active Model tile's viewport record.
|
||||||
|
pub fn active_visual_style(&self) -> Handle {
|
||||||
|
self.active_tile_vport_handle()
|
||||||
|
.and_then(|handle| self.document.vports.iter().find(|value| value.handle == handle))
|
||||||
|
.map_or(Handle::NULL, |value| value.visual_style_handle)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Records the visual style named `name` on the active Model tile's
|
||||||
|
/// viewport record, when the drawing has it.
|
||||||
|
pub fn set_active_visual_style_named(&mut self, name: &str) {
|
||||||
|
let style = self.document.objects.iter().find_map(|(handle, object)| match object {
|
||||||
|
codec::objects::ObjectType::VisualStyle(style) if style.description == name => Some(*handle),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
let (Some(style), Some(handle)) = (style, self.active_tile_vport_handle()) else { return };
|
||||||
|
if let Some(value) = self.document.vports.iter_mut().find(|value| value.handle == handle) {
|
||||||
|
value.visual_style_handle = style;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn set_active_background(&mut self, background: Handle) {
|
fn set_active_background(&mut self, background: Handle) {
|
||||||
let Some(handle) = self.active_tile_vport_handle() else { return };
|
let Some(handle) = self.active_tile_vport_handle() else { return };
|
||||||
if let Some(value) = self.document.vports.iter_mut().find(|value| value.handle == handle) {
|
let Some(value) = self.document.vports.iter_mut().find(|value| value.handle == handle) else {
|
||||||
value.background_handle = background;
|
return;
|
||||||
self.camera_generation += 1;
|
};
|
||||||
|
let old = std::mem::replace(&mut value.background_handle, background);
|
||||||
|
self.camera_generation += 1;
|
||||||
|
if old != background {
|
||||||
|
erase_background(&mut self.document, old);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1713,6 +1806,38 @@ pub fn add_background(
|
|||||||
handle
|
handle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Erases a background object once no view or viewport refers to it (the
|
||||||
|
/// reference erases a view's background when it is replaced, cleared or the
|
||||||
|
/// view deleted, and a viewport's when the viewport takes another).
|
||||||
|
pub fn erase_background(document: &mut codec::CadDocument, handle: Handle) {
|
||||||
|
use codec::objects::{ClassObjectData as D, ObjectType};
|
||||||
|
if !handle.is_valid()
|
||||||
|
|| document.views.iter().any(|view| view.background_handle == handle)
|
||||||
|
|| document.vports.iter().any(|vport| vport.background_handle == handle)
|
||||||
|
|| document.entities().any(|entity| {
|
||||||
|
matches!(entity, codec::EntityType::Viewport(viewport) if viewport.background_handle == handle)
|
||||||
|
})
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let owner = match document.objects.get(&handle) {
|
||||||
|
Some(ObjectType::ClassObject(object))
|
||||||
|
if matches!(
|
||||||
|
object.data,
|
||||||
|
D::SolidBackground(_) | D::GradientBackground(_) | D::ImageBackground(_)
|
||||||
|
| D::GroundPlaneBackground(_) | D::SkyLightBackground(_) | D::IblBackground(_)
|
||||||
|
) =>
|
||||||
|
{
|
||||||
|
object.owner
|
||||||
|
}
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
document.objects.remove(&handle);
|
||||||
|
if let Some(ObjectType::Dictionary(entries)) = document.objects.get_mut(&owner) {
|
||||||
|
entries.entries.retain(|(_, entry)| *entry != handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A copy of a background object (the reference gives each view and
|
/// A copy of a background object (the reference gives each view and
|
||||||
/// viewport its own); null for none.
|
/// viewport its own); null for none.
|
||||||
pub fn copy_background(document: &mut codec::CadDocument, handle: Handle) -> Handle {
|
pub fn copy_background(document: &mut codec::CadDocument, handle: Handle) -> Handle {
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ pub enum BlockScalePolicy {
|
|||||||
// blocks and/or free functions). Pure text-move from the original mod.rs.
|
// blocks and/or free functions). Pure text-move from the original mod.rs.
|
||||||
mod boundary;
|
mod boundary;
|
||||||
mod camera_ops;
|
mod camera_ops;
|
||||||
pub use camera_ops::{add_background, copy_background};
|
pub use camera_ops::{add_background, copy_background, erase_background};
|
||||||
pub(crate) mod centerline;
|
pub(crate) mod centerline;
|
||||||
pub(crate) mod centermark;
|
pub(crate) mod centermark;
|
||||||
pub(crate) mod dimension_assoc;
|
pub(crate) mod dimension_assoc;
|
||||||
|
|||||||
+14
-10
@@ -434,8 +434,10 @@ impl shader::Primitive for Primitive {
|
|||||||
// The scene works in logical pixels; an image background keeps
|
// The scene works in logical pixels; an image background keeps
|
||||||
// its size in device pixels.
|
// its size in device pixels.
|
||||||
let mut uniforms = vp.uniforms;
|
let mut uniforms = vp.uniforms;
|
||||||
uniforms.background_image_transform[2] /= scale.max(1e-3);
|
// The image's size and offset are in device pixels.
|
||||||
uniforms.background_image_transform[3] /= scale.max(1e-3);
|
for value in &mut uniforms.background_image_transform {
|
||||||
|
*value /= scale.max(1e-3);
|
||||||
|
}
|
||||||
inner.upload_uniforms(device, queue, &uniforms);
|
inner.upload_uniforms(device, queue, &uniforms);
|
||||||
|
|
||||||
// ── Scene-render cache ────────────────────────────────────────
|
// ── Scene-render cache ────────────────────────────────────────
|
||||||
@@ -2557,20 +2559,18 @@ impl Scene {
|
|||||||
if viewport.paper_sheet {
|
if viewport.paper_sheet {
|
||||||
let handle = self.current_layout_sheet_viewport_handle();
|
let handle = self.current_layout_sheet_viewport_handle();
|
||||||
if let Some(EntityType::Viewport(value)) = self.document.get_entity(handle) {
|
if let Some(EntityType::Viewport(value)) = self.document.get_entity(handle) {
|
||||||
return build(
|
// The sheet keeps its page and desk: a paper-space view's
|
||||||
value.visual_style_handle,
|
// background is kept with the layout but not drawn.
|
||||||
value.brightness,
|
return build(value.visual_style_handle, value.brightness, value.contrast, Handle::NULL);
|
||||||
value.contrast,
|
|
||||||
value.background_handle,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else if viewport.handle.is_valid() {
|
} else if viewport.handle.is_valid() {
|
||||||
if let Some(EntityType::Viewport(value)) = self.document.get_entity(viewport.handle) {
|
if let Some(EntityType::Viewport(value)) = self.document.get_entity(viewport.handle) {
|
||||||
|
let flat = value.render_mode == codec::entities::ViewportRenderMode::Wireframe2D;
|
||||||
return build(
|
return build(
|
||||||
value.visual_style_handle,
|
value.visual_style_handle,
|
||||||
value.brightness,
|
value.brightness,
|
||||||
value.contrast,
|
value.contrast,
|
||||||
value.background_handle,
|
if flat { Handle::NULL } else { value.background_handle },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if self.current_layout == "Model" {
|
} else if self.current_layout == "Model" {
|
||||||
@@ -4907,7 +4907,11 @@ impl Scene {
|
|||||||
// (hatched) frame once it settles. Only applied to the on-screen
|
// (hatched) frame once it settles. Only applied to the on-screen
|
||||||
// Model / paper content — the paper *sheet* keeps its fills.
|
// Model / paper content — the paper *sheet* keeps its fills.
|
||||||
skip_hatch: self.hatch_lod_enabled() && navigating,
|
skip_hatch: self.hatch_lod_enabled() && navigating,
|
||||||
skip_background: !inst.paper_sheet && self.current_layout != "Model",
|
// A floating viewport shows the page behind it unless it has a
|
||||||
|
// background of its own.
|
||||||
|
skip_background: !inst.paper_sheet
|
||||||
|
&& self.current_layout != "Model"
|
||||||
|
&& display.background.params[0] < 0.5,
|
||||||
geometry_epoch: self.geometry_epoch,
|
geometry_epoch: self.geometry_epoch,
|
||||||
camera_generation: self.camera_generation,
|
camera_generation: self.camera_generation,
|
||||||
wire_content_id,
|
wire_content_id,
|
||||||
|
|||||||
+27
-27
@@ -81,22 +81,25 @@ fn equirect_uv(ndc: vec2<f32>, rotation: f32) -> vec2<f32> {
|
|||||||
fn image_uv(source_uv: vec2<f32>) -> vec2<f32> {
|
fn image_uv(source_uv: vec2<f32>) -> vec2<f32> {
|
||||||
var uv = source_uv;
|
var uv = source_uv;
|
||||||
if u.background_image_params.x < 0.5 {
|
if u.background_image_params.x < 0.5 {
|
||||||
// Not fitted: the image at its own pixel size (times its scale),
|
// Not fitted: the image at its own pixel size times its scale (a
|
||||||
// centred on the viewport and moved by its offset.
|
// negative scale mirrors it), centred on the viewport and moved by
|
||||||
let pixel = (source_uv - vec2<f32>(0.5)) * max(u.viewport_size, vec2<f32>(1.0));
|
// its offset in pixels, right and down.
|
||||||
uv = pixel / max(abs(u.background_image_transform.zw), vec2<f32>(1e-6))
|
let pixel = (source_uv - vec2<f32>(0.5)) * max(u.viewport_size, vec2<f32>(1.0))
|
||||||
+ vec2<f32>(0.5) - u.background_image_transform.xy;
|
- u.background_image_transform.xy;
|
||||||
|
let size = u.background_image_transform.zw;
|
||||||
|
let safe = select(size, vec2<f32>(1e-6), abs(size) < vec2<f32>(1e-6));
|
||||||
|
uv = pixel / safe + vec2<f32>(0.5);
|
||||||
return select(uv, fract(uv), u.background_image_params.z > 0.5);
|
return select(uv, fract(uv), u.background_image_params.z > 0.5);
|
||||||
}
|
}
|
||||||
|
// Fitted: the image fills the viewport; keeping its aspect ratio it
|
||||||
|
// covers the viewport and the overflow is cut off evenly.
|
||||||
if u.background_image_params.y > 0.5 {
|
if u.background_image_params.y > 0.5 {
|
||||||
let image_aspect = max(u.background_image_params.w, 1e-6);
|
let image_aspect = max(u.background_image_params.w, 1e-6);
|
||||||
let viewport_aspect = max(u.viewport_size.x / max(u.viewport_size.y, 1.0), 1e-6);
|
let viewport_aspect = max(u.viewport_size.x / max(u.viewport_size.y, 1.0), 1e-6);
|
||||||
if image_aspect > viewport_aspect {
|
if image_aspect > viewport_aspect {
|
||||||
let scale = viewport_aspect / image_aspect;
|
uv.x = (uv.x - 0.5) * (viewport_aspect / image_aspect) + 0.5;
|
||||||
uv.y = (uv.y - 0.5) / scale + 0.5;
|
|
||||||
} else {
|
} else {
|
||||||
let scale = image_aspect / viewport_aspect;
|
uv.y = (uv.y - 0.5) * (image_aspect / viewport_aspect) + 0.5;
|
||||||
uv.x = (uv.x - 0.5) / scale + 0.5;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if u.background_image_params.z > 0.5 {
|
if u.background_image_params.z > 0.5 {
|
||||||
@@ -112,28 +115,25 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
|||||||
if mode == 1u {
|
if mode == 1u {
|
||||||
color = u.viewport_background.rgb;
|
color = u.viewport_background.rgb;
|
||||||
} else if mode == 2u {
|
} else if mode == 2u {
|
||||||
// Bottom to top across the viewport, the "up" direction turned
|
// Bottom to top along the "up" direction turned counter-clockwise by
|
||||||
// counter-clockwise by the rotation; in pixels, so the angle holds
|
// the rotation, in pixels from the viewport centre. As the reference
|
||||||
// whatever the viewport's shape, and spanning corner to corner.
|
// draws it: the middle colour sits (horizon - 0.5) viewport widths
|
||||||
|
// from the centre and blends linearly into the top and bottom colours
|
||||||
|
// over height viewport heights on each side; two colours are one blend
|
||||||
|
// over a viewport height on each side of the centre.
|
||||||
let angle = u.background_params.w;
|
let angle = u.background_params.w;
|
||||||
let up = vec2<f32>(-sin(angle), cos(angle));
|
let up = vec2<f32>(-sin(angle), cos(angle));
|
||||||
let size = max(u.viewport_size, vec2<f32>(1.0));
|
let size = max(u.viewport_size, vec2<f32>(1.0));
|
||||||
let pixel = (vec2<f32>(in.uv.x, 1.0 - in.uv.y) - vec2<f32>(0.5)) * size;
|
let pixel = (vec2<f32>(in.uv.x, 1.0 - in.uv.y) - vec2<f32>(0.5)) * size;
|
||||||
let extent = max(abs(up.x) * size.x + abs(up.y) * size.y, 1.0);
|
let along = dot(pixel, up);
|
||||||
let coordinate = clamp(dot(pixel, up) / extent + 0.5, 0.0, 1.0);
|
let three = abs(u.background_params.z) > 1e-6;
|
||||||
let horizon = clamp(u.background_params.y, 0.0, 1.0);
|
let middle_at = select(0.0, (u.background_params.y - 0.5) * size.x, three);
|
||||||
let half_height = abs(u.background_params.z) * 0.5;
|
let half_band = select(1.0, abs(u.background_params.z), three) * size.y;
|
||||||
if half_height <= 1e-6 {
|
let middle = select(mix(u.background_bottom.rgb, u.background_top.rgb, 0.5),
|
||||||
// Two colours: one blend from the bottom to the top, over twice
|
u.background_middle.rgb, three);
|
||||||
// the viewport (the reference's two-colour gradient reaches the
|
let lower = clamp((along - middle_at + half_band) / half_band, 0.0, 1.0);
|
||||||
// end colours a viewport beyond the edges).
|
let upper = clamp((along - middle_at) / half_band, 0.0, 1.0);
|
||||||
color = mix(u.background_bottom.rgb, u.background_top.rgb, 0.25 + 0.5 * coordinate);
|
color = mix(mix(u.background_bottom.rgb, middle, lower), u.background_top.rgb, upper);
|
||||||
} else {
|
|
||||||
let lower = clamp((coordinate - (horizon - half_height)) / half_height, 0.0, 1.0);
|
|
||||||
let upper = clamp((coordinate - horizon) / half_height, 0.0, 1.0);
|
|
||||||
let low_color = mix(u.background_bottom.rgb, u.background_middle.rgb, lower);
|
|
||||||
color = mix(low_color, u.background_top.rgb, upper);
|
|
||||||
}
|
|
||||||
} else if mode == 3u {
|
} else if mode == 3u {
|
||||||
let direction = world_direction(in.ndc);
|
let direction = world_direction(in.ndc);
|
||||||
if direction.z >= 0.0 {
|
if direction.z >= 0.0 {
|
||||||
|
|||||||
@@ -2206,6 +2206,9 @@ pub struct ViewBackgroundState {
|
|||||||
pub image: String,
|
pub image: String,
|
||||||
pub position: BgPosition,
|
pub position: BgPosition,
|
||||||
pub aspect: bool,
|
pub aspect: bool,
|
||||||
|
/// The image's offset (pixels) and scale, kept from the view's image.
|
||||||
|
pub offset: [f64; 2],
|
||||||
|
pub scale: [f64; 2],
|
||||||
/// The drawing area colour, shown for no background.
|
/// The drawing area colour, shown for no background.
|
||||||
pub canvas: [u8; 3],
|
pub canvas: [u8; 3],
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user