forked from 1b-agent/opencadstudio-upstream
fix(scene): preserve viewport settings
Reuse full VPort records during camera and tile synchronization so navigation cannot replace file-defined display state with defaults. Honor image, ground, IBL, and sun-backed sky parameters in background rendering.
This commit is contained in:
1 parent
0be9c41773
commit
15ab027d70
3 files changed
+357
-179
No files matched your search
+122
-47
@@ -122,9 +122,8 @@ impl Scene {
|
||||
pub fn restore_named_view(&mut self, view: &acadrust::tables::View) {
|
||||
use glam::Vec3;
|
||||
let cam = &mut *self.camera.borrow_mut();
|
||||
// view.target is the look-at point; view.direction is eye→target direction.
|
||||
// The stored direction points from the target toward the eye.
|
||||
cam.target = glam::DVec3::new(view.target.x, view.target.y, view.target.z);
|
||||
// direction in acadrust = from-target-to-eye (same as AutoCAD convention).
|
||||
let eye_dir = Vec3::new(
|
||||
view.direction.x as f32,
|
||||
view.direction.y as f32,
|
||||
@@ -720,16 +719,13 @@ impl Scene {
|
||||
)
|
||||
}
|
||||
|
||||
/// Reverse of `camera_from_vport`: write `cam`'s view target / direction
|
||||
/// / height onto a fresh VPort entry with the given `name` and screen
|
||||
/// rectangle (0..1 normalized, DXF bottom-left origin convention).
|
||||
fn vport_from_camera(
|
||||
fn apply_camera_to_vport(
|
||||
&self,
|
||||
name: &str,
|
||||
entry: &mut acadrust::tables::VPort,
|
||||
cam: &Camera,
|
||||
lower_left: acadrust::types::Vector2,
|
||||
upper_right: acadrust::types::Vector2,
|
||||
) -> acadrust::tables::VPort {
|
||||
) {
|
||||
let view_dir = cam.rotation * glam::Vec3::Z;
|
||||
let view_height = cam.ortho_size() * 2.0;
|
||||
let target_wcs = acadrust::types::Vector3 {
|
||||
@@ -737,7 +733,6 @@ impl Scene {
|
||||
y: (cam.target.y as f64) + [0.0_f64; 3][1],
|
||||
z: (cam.target.z as f64) + [0.0_f64; 3][2],
|
||||
};
|
||||
let mut entry = acadrust::tables::VPort::new(name);
|
||||
entry.lower_left = lower_left;
|
||||
entry.upper_right = upper_right;
|
||||
entry.view_target = target_wcs;
|
||||
@@ -747,12 +742,43 @@ impl Scene {
|
||||
z: view_dir.z as f64,
|
||||
};
|
||||
entry.view_height = view_height as f64;
|
||||
let (canvas_width, canvas_height) = self.selection.borrow().vp_size;
|
||||
let viewport_width = (upper_right.x - lower_left.x).abs() * canvas_width as f64;
|
||||
let viewport_height = (upper_right.y - lower_left.y).abs() * canvas_height as f64;
|
||||
if viewport_width > 1e-9 && viewport_height > 1e-9 {
|
||||
entry.aspect_ratio = viewport_width / viewport_height;
|
||||
}
|
||||
entry.perspective = cam.projection == view::camera::Projection::Perspective;
|
||||
entry.lens_length = (12.0 / (cam.fov_y * 0.5).tan().max(1e-6)) as f64;
|
||||
entry.view_center = acadrust::types::Vector2::ZERO;
|
||||
// Stored twist = -roll, matching the decoder (roll = -twist).
|
||||
entry.view_twist = -cam.roll() as f64;
|
||||
entry
|
||||
}
|
||||
|
||||
fn vport_rect_matches(
|
||||
entry: &acadrust::tables::VPort,
|
||||
lower_left: acadrust::types::Vector2,
|
||||
upper_right: acadrust::types::Vector2,
|
||||
) -> bool {
|
||||
const EPSILON: f64 = 1e-5;
|
||||
(entry.lower_left.x - lower_left.x).abs() <= EPSILON
|
||||
&& (entry.lower_left.y - lower_left.y).abs() <= EPSILON
|
||||
&& (entry.upper_right.x - upper_right.x).abs() <= EPSILON
|
||||
&& (entry.upper_right.y - upper_right.y).abs() <= EPSILON
|
||||
}
|
||||
|
||||
fn vport_contains_rect_center(
|
||||
entry: &acadrust::tables::VPort,
|
||||
lower_left: acadrust::types::Vector2,
|
||||
upper_right: acadrust::types::Vector2,
|
||||
) -> bool {
|
||||
const EPSILON: f64 = 1e-5;
|
||||
let center_x = (lower_left.x + upper_right.x) * 0.5;
|
||||
let center_y = (lower_left.y + upper_right.y) * 0.5;
|
||||
center_x >= entry.lower_left.x - EPSILON
|
||||
&& center_x <= entry.upper_right.x + EPSILON
|
||||
&& center_y >= entry.lower_left.y - EPSILON
|
||||
&& center_y <= entry.upper_right.y + EPSILON
|
||||
}
|
||||
|
||||
/// Convert a `ModelTile`'s normalized iced rectangle (top-left origin) to
|
||||
@@ -780,9 +806,7 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
|
||||
/// Restore `model_tiles` from VPort entries that a previous save left in
|
||||
/// the document. Native AutoCAD tiled model-space layouts are represented
|
||||
/// by duplicate `*Active` VPort entries.
|
||||
/// Restore model tiles from duplicate `*Active` VPort entries.
|
||||
/// Returns true on success — the caller skips `apply_active_vport_camera`
|
||||
/// in that case because the active tile's camera has already been loaded
|
||||
/// into `self.camera`.
|
||||
@@ -828,8 +852,7 @@ impl Scene {
|
||||
true
|
||||
}
|
||||
|
||||
/// Persist `model_tiles` to the VPort table. Native AutoCAD tiled model
|
||||
/// viewports are written as duplicate `*Active` entries.
|
||||
/// Persist model tiles as duplicate `*Active` VPort entries.
|
||||
fn save_model_tiles_to_vports(&mut self) {
|
||||
// Stash the live camera into the active tile so the about-to-write
|
||||
// snapshot reflects the user's most recent orbit / pan / zoom.
|
||||
@@ -843,6 +866,15 @@ impl Scene {
|
||||
}
|
||||
|
||||
let table_handle = self.document.vports.handle();
|
||||
let mut active_vports: Vec<acadrust::tables::VPort> = self
|
||||
.document
|
||||
.vports
|
||||
.iter()
|
||||
.filter(|v| is_active_vport_name(&v.name))
|
||||
.cloned()
|
||||
.collect();
|
||||
let source_vports = active_vports.clone();
|
||||
let inherited_vport = active_vports.first().cloned();
|
||||
let preserved_vps: Vec<acadrust::tables::VPort> = self
|
||||
.document
|
||||
.vports
|
||||
@@ -870,16 +902,88 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
|
||||
let mut scene_objects_changed = false;
|
||||
for tile in ordered_tiles {
|
||||
let (ll, ur) = Self::tile_rect_to_vport(tile.rect);
|
||||
let mut entry = self.vport_from_camera("*Active", &tile.camera, ll, ur);
|
||||
let exact = active_vports
|
||||
.iter()
|
||||
.position(|entry| Self::vport_rect_matches(entry, ll, ur));
|
||||
let inherited = exact.or_else(|| {
|
||||
active_vports
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, entry)| Self::vport_contains_rect_center(entry, ll, ur))
|
||||
.min_by(|(_, left), (_, right)| {
|
||||
let left_area = (left.upper_right.x - left.lower_left.x)
|
||||
* (left.upper_right.y - left.lower_left.y);
|
||||
let right_area = (right.upper_right.x - right.lower_left.x)
|
||||
* (right.upper_right.y - right.lower_left.y);
|
||||
left_area.total_cmp(&right_area)
|
||||
})
|
||||
.map(|(index, _)| index)
|
||||
});
|
||||
let mut entry = inherited
|
||||
.map(|index| active_vports.remove(index))
|
||||
.or_else(|| {
|
||||
source_vports
|
||||
.iter()
|
||||
.filter(|entry| Self::vport_contains_rect_center(entry, ll, ur))
|
||||
.min_by(|left, right| {
|
||||
let left_area = (left.upper_right.x - left.lower_left.x)
|
||||
* (left.upper_right.y - left.lower_left.y);
|
||||
let right_area = (right.upper_right.x - right.lower_left.x)
|
||||
* (right.upper_right.y - right.lower_left.y);
|
||||
left_area.total_cmp(&right_area)
|
||||
})
|
||||
.cloned()
|
||||
})
|
||||
.or_else(|| inherited_vport.clone())
|
||||
.unwrap_or_else(|| acadrust::tables::VPort::new("*Active"));
|
||||
let cloned = inherited.is_none();
|
||||
if cloned {
|
||||
entry.handle = acadrust::Handle::NULL;
|
||||
}
|
||||
entry.name = "*Active".to_string();
|
||||
self.apply_camera_to_vport(&mut entry, &tile.camera, ll, ur);
|
||||
entry.render_mode = tile.render_mode;
|
||||
// Each viewport persists its own grid display + grid-snap (#121).
|
||||
entry.grid_on = tile.grid_on;
|
||||
entry.snap_on = tile.snap_on;
|
||||
entry.handle = self.document.allocate_handle();
|
||||
if !entry.handle.is_valid() {
|
||||
entry.handle = self.document.allocate_handle();
|
||||
}
|
||||
if cloned && entry.sun_handle.is_valid() {
|
||||
let source_sun = self.document.objects.get(&entry.sun_handle).and_then(|object| {
|
||||
match object {
|
||||
acadrust::objects::ObjectType::ClassObject(value)
|
||||
if matches!(
|
||||
&value.data,
|
||||
acadrust::objects::ClassObjectData::Sun(_)
|
||||
) => Some(value.clone()),
|
||||
_ => None,
|
||||
}
|
||||
});
|
||||
if let Some(mut sun) = source_sun {
|
||||
let handle = self.document.allocate_handle();
|
||||
sun.handle = handle;
|
||||
sun.owner = entry.handle;
|
||||
sun.reactors.clear();
|
||||
sun.xdictionary_handle = None;
|
||||
self.document.objects.insert(
|
||||
handle,
|
||||
acadrust::objects::ObjectType::ClassObject(sun),
|
||||
);
|
||||
entry.sun_handle = handle;
|
||||
scene_objects_changed = true;
|
||||
}
|
||||
}
|
||||
self.document.vports.add_allow_duplicate(entry);
|
||||
}
|
||||
if scene_objects_changed {
|
||||
self.object_data_cache =
|
||||
crate::entities::object_data::build_cache(&self.document);
|
||||
self.lighting_cache.borrow_mut().clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// Mirror the live grid/snap toggles onto the active view's own store so the
|
||||
@@ -1000,31 +1104,6 @@ impl Scene {
|
||||
};
|
||||
|
||||
if self.current_layout == "Model" {
|
||||
let target_wcs = acadrust::types::Vector3 {
|
||||
x: (cam.target.x as f64) + [0.0_f64; 3][0],
|
||||
y: (cam.target.y as f64) + [0.0_f64; 3][1],
|
||||
z: (cam.target.z as f64) + [0.0_f64; 3][2],
|
||||
};
|
||||
|
||||
// Write back to the *Active VPort entry (may be overridden by DWG writer).
|
||||
if let Some(vp) = self
|
||||
.document
|
||||
.vports
|
||||
.iter_mut()
|
||||
.find(|v| is_active_vport_name(&v.name))
|
||||
{
|
||||
vp.view_target = target_wcs;
|
||||
vp.view_center = acadrust::types::Vector2::ZERO;
|
||||
vp.view_direction = vd3;
|
||||
vp.view_height = view_height as f64;
|
||||
vp.view_twist = twist;
|
||||
vp.perspective = cam.projection == view::camera::Projection::Perspective;
|
||||
vp.lens_length = (12.0 / (cam.fov_y * 0.5).tan().max(1e-6)) as f64;
|
||||
}
|
||||
|
||||
// Persist the tiled layout as duplicate `*Active` VPort entries.
|
||||
// The view lives entirely in the standard VPORT table now — no
|
||||
// app-specific View record.
|
||||
self.save_model_tiles_to_vports();
|
||||
true
|
||||
} else {
|
||||
@@ -1040,11 +1119,7 @@ impl Scene {
|
||||
if let Some(EntityType::Viewport(vp)) =
|
||||
self.document.get_entity_mut(sheet_handle)
|
||||
{
|
||||
// AutoCAD stores the paper-space view position in
|
||||
// `view_center` (DCS) with `view_target` at the origin —
|
||||
// writing it the other way round shifts the layout and
|
||||
// crashes nothing but renders the sheet off-place. Paper
|
||||
// space is always a plan view, so DCS == WCS XY here.
|
||||
// Paper-space position is stored in view_center (DCS).
|
||||
vp.view_center =
|
||||
acadrust::types::Vector3::new(target_wcs.x, target_wcs.y, 0.0);
|
||||
vp.view_target = acadrust::types::Vector3::ZERO;
|
||||
|
||||
+196
-117
@@ -34,6 +34,7 @@ struct ViewportLightingSettings {
|
||||
force_default: bool,
|
||||
default_type: i16,
|
||||
ambient: [f32; 3],
|
||||
sun_handle: Handle,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -94,6 +95,7 @@ impl Default for ViewportLightingSettings {
|
||||
force_default: true,
|
||||
default_type: 1,
|
||||
ambient: [0.18; 3],
|
||||
sun_handle: Handle::NULL,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1426,9 +1428,148 @@ fn crop_view_proj(view_proj: glam::Mat4, uo: f32, vo: f32, us: f32, vs: f32) ->
|
||||
crop * view_proj
|
||||
}
|
||||
|
||||
fn normalized_direction(value: [f64; 3], fallback: [f32; 3]) -> [f32; 3] {
|
||||
let length = (value[0] * value[0] + value[1] * value[1] + value[2] * value[2]).sqrt();
|
||||
if length <= 1e-12 {
|
||||
fallback
|
||||
} else {
|
||||
[
|
||||
(value[0] / length) as f32,
|
||||
(value[1] / length) as f32,
|
||||
(value[2] / length) as f32,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn solar_direction(
|
||||
sun: &acadrust::objects::Sun,
|
||||
geo: &acadrust::objects::GeoData,
|
||||
) -> Option<[f32; 3]> {
|
||||
if sun.julian_day < 1_000_000 {
|
||||
return None;
|
||||
}
|
||||
let daylight_ms = if sun.is_daylight_savings_on {
|
||||
3_600_000.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let jd = sun.julian_day as f64
|
||||
+ (sun.milliseconds as f64 - daylight_ms) / 86_400_000.0;
|
||||
let days = jd - 2_451_545.0;
|
||||
let mean_longitude = (280.460 + 0.985_647_4 * days).to_radians();
|
||||
let mean_anomaly = (357.528 + 0.985_600_3 * days).to_radians();
|
||||
let ecliptic_longitude = mean_longitude
|
||||
+ (1.915 * mean_anomaly.sin() + 0.020 * (2.0 * mean_anomaly).sin()).to_radians();
|
||||
let obliquity = (23.439 - 0.000_000_4 * days).to_radians();
|
||||
let right_ascension =
|
||||
(obliquity.cos() * ecliptic_longitude.sin()).atan2(ecliptic_longitude.cos());
|
||||
let declination = (obliquity.sin() * ecliptic_longitude.sin()).asin();
|
||||
let local_sidereal =
|
||||
(280.460_618_37 + 360.985_647_366_29 * days + geo.reference_point.x).to_radians();
|
||||
let hour_angle = (local_sidereal - right_ascension + std::f64::consts::PI)
|
||||
.rem_euclid(std::f64::consts::TAU)
|
||||
- std::f64::consts::PI;
|
||||
let latitude = geo.reference_point.y.to_radians();
|
||||
let east_component = -declination.cos() * hour_angle.sin();
|
||||
let north_component = declination.sin() * latitude.cos()
|
||||
- declination.cos() * hour_angle.cos() * latitude.sin();
|
||||
let up_component = declination.sin() * latitude.sin()
|
||||
+ declination.cos() * hour_angle.cos() * latitude.cos();
|
||||
if up_component <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
let north = normalized_direction(
|
||||
[geo.north_direction.x, geo.north_direction.y, 0.0],
|
||||
[0.0, 1.0, 0.0],
|
||||
);
|
||||
let east = [north[1], -north[0], 0.0];
|
||||
let up = normalized_direction(
|
||||
[geo.up_direction.x, geo.up_direction.y, geo.up_direction.z],
|
||||
[0.0, 0.0, 1.0],
|
||||
);
|
||||
Some(normalized_direction(
|
||||
[
|
||||
-(east[0] as f64 * east_component
|
||||
+ north[0] as f64 * north_component
|
||||
+ up[0] as f64 * up_component),
|
||||
-(east[1] as f64 * east_component
|
||||
+ north[1] as f64 * north_component
|
||||
+ up[1] as f64 * up_component),
|
||||
-(east[2] as f64 * east_component
|
||||
+ north[2] as f64 * north_component
|
||||
+ up[2] as f64 * up_component),
|
||||
],
|
||||
[0.0, 0.0, -1.0],
|
||||
))
|
||||
}
|
||||
|
||||
// ── Render-style helpers (impl Scene) ────────────────────────────────────
|
||||
|
||||
impl Scene {
|
||||
fn model_tile_vport(&self, index: usize) -> Option<&acadrust::tables::VPort> {
|
||||
let rect = self.model_tiles.borrow().get(index)?.rect;
|
||||
let lower_left = [rect.x as f64, (1.0 - rect.y - rect.height) as f64];
|
||||
let upper_right = [
|
||||
(rect.x + rect.width) as f64,
|
||||
(1.0 - rect.y) as f64,
|
||||
];
|
||||
const EPSILON: f64 = 1e-5;
|
||||
let exact = self
|
||||
.document
|
||||
.vports
|
||||
.iter()
|
||||
.filter(|value| value.name.eq_ignore_ascii_case("*Active"))
|
||||
.find(|value| {
|
||||
(value.lower_left.x - lower_left[0]).abs() <= EPSILON
|
||||
&& (value.lower_left.y - lower_left[1]).abs() <= EPSILON
|
||||
&& (value.upper_right.x - upper_right[0]).abs() <= EPSILON
|
||||
&& (value.upper_right.y - upper_right[1]).abs() <= EPSILON
|
||||
});
|
||||
exact.or_else(|| {
|
||||
let center_x = (lower_left[0] + upper_right[0]) * 0.5;
|
||||
let center_y = (lower_left[1] + upper_right[1]) * 0.5;
|
||||
self.document
|
||||
.vports
|
||||
.iter()
|
||||
.filter(|value| {
|
||||
value.name.eq_ignore_ascii_case("*Active")
|
||||
&& center_x >= value.lower_left.x - EPSILON
|
||||
&& center_x <= value.upper_right.x + EPSILON
|
||||
&& center_y >= value.lower_left.y - EPSILON
|
||||
&& center_y <= value.upper_right.y + EPSILON
|
||||
})
|
||||
.min_by(|left, right| {
|
||||
let left_area = (left.upper_right.x - left.lower_left.x)
|
||||
* (left.upper_right.y - left.lower_left.y);
|
||||
let right_area = (right.upper_right.x - right.lower_left.x)
|
||||
* (right.upper_right.y - right.lower_left.y);
|
||||
left_area.total_cmp(&right_area)
|
||||
})
|
||||
.or_else(|| {
|
||||
self.document
|
||||
.vports
|
||||
.iter()
|
||||
.find(|value| value.name.eq_ignore_ascii_case("*Active"))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn geolocation(&self) -> Option<&acadrust::objects::GeoData> {
|
||||
use acadrust::objects::ObjectType;
|
||||
|
||||
crate::entities::object_data::geo_objects(&self.object_data_cache)
|
||||
.iter()
|
||||
.find_map(|handle| match self.document.objects.get(handle) {
|
||||
Some(ObjectType::GeoData(value))
|
||||
if value.coordinate_type == 3
|
||||
&& value.reference_point.x.is_finite()
|
||||
&& value.reference_point.y.is_finite()
|
||||
&& value.reference_point.x.abs() <= 180.0
|
||||
&& value.reference_point.y.abs() <= 90.0 => Some(value),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn build_lighting_cache(
|
||||
&self,
|
||||
target_block: Handle,
|
||||
@@ -1436,92 +1577,11 @@ impl Scene {
|
||||
) -> Vec<SceneLight> {
|
||||
use acadrust::objects::{ClassObjectData, ObjectType};
|
||||
|
||||
fn normalized(value: [f64; 3], fallback: [f32; 3]) -> [f32; 3] {
|
||||
let length =
|
||||
(value[0] * value[0] + value[1] * value[1] + value[2] * value[2]).sqrt();
|
||||
if length <= 1e-12 {
|
||||
fallback
|
||||
} else {
|
||||
[
|
||||
(value[0] / length) as f32,
|
||||
(value[1] / length) as f32,
|
||||
(value[2] / length) as f32,
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
fn solar_direction(
|
||||
sun: &acadrust::objects::Sun,
|
||||
geo: &acadrust::objects::GeoData,
|
||||
) -> Option<[f32; 3]> {
|
||||
if sun.julian_day < 1_000_000 {
|
||||
return None;
|
||||
}
|
||||
let daylight_ms = if sun.is_daylight_savings_on {
|
||||
3_600_000.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let jd = sun.julian_day as f64
|
||||
+ (sun.milliseconds as f64 - daylight_ms) / 86_400_000.0;
|
||||
let days = jd - 2_451_545.0;
|
||||
let mean_longitude = (280.460 + 0.985_647_4 * days).to_radians();
|
||||
let mean_anomaly = (357.528 + 0.985_600_3 * days).to_radians();
|
||||
let ecliptic_longitude = mean_longitude
|
||||
+ (1.915 * mean_anomaly.sin()
|
||||
+ 0.020 * (2.0 * mean_anomaly).sin())
|
||||
.to_radians();
|
||||
let obliquity = (23.439 - 0.000_000_4 * days).to_radians();
|
||||
let right_ascension = (obliquity.cos() * ecliptic_longitude.sin())
|
||||
.atan2(ecliptic_longitude.cos());
|
||||
let declination =
|
||||
(obliquity.sin() * ecliptic_longitude.sin()).asin();
|
||||
let local_sidereal = (280.460_618_37
|
||||
+ 360.985_647_366_29 * days
|
||||
+ geo.reference_point.x)
|
||||
.to_radians();
|
||||
let hour_angle = (local_sidereal - right_ascension + std::f64::consts::PI)
|
||||
.rem_euclid(std::f64::consts::TAU)
|
||||
- std::f64::consts::PI;
|
||||
let latitude = geo.reference_point.y.to_radians();
|
||||
let east_component = -declination.cos() * hour_angle.sin();
|
||||
let north_component = declination.sin() * latitude.cos()
|
||||
- declination.cos() * hour_angle.cos() * latitude.sin();
|
||||
let up_component = declination.sin() * latitude.sin()
|
||||
+ declination.cos() * hour_angle.cos() * latitude.cos();
|
||||
if up_component <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
let north = normalized(
|
||||
[geo.north_direction.x, geo.north_direction.y, 0.0],
|
||||
[0.0, 1.0, 0.0],
|
||||
);
|
||||
let east = [north[1], -north[0], 0.0];
|
||||
let up = normalized(
|
||||
[geo.up_direction.x, geo.up_direction.y, geo.up_direction.z],
|
||||
[0.0, 0.0, 1.0],
|
||||
);
|
||||
Some(normalized(
|
||||
[
|
||||
-(east[0] as f64 * east_component
|
||||
+ north[0] as f64 * north_component
|
||||
+ up[0] as f64 * up_component),
|
||||
-(east[1] as f64 * east_component
|
||||
+ north[1] as f64 * north_component
|
||||
+ up[1] as f64 * up_component),
|
||||
-(east[2] as f64 * east_component
|
||||
+ north[2] as f64 * north_component
|
||||
+ up[2] as f64 * up_component),
|
||||
],
|
||||
[0.0, 0.0, -1.0],
|
||||
))
|
||||
}
|
||||
|
||||
fn converted(scene: &Scene, light: &acadrust::entities::Light) -> Option<SceneLight> {
|
||||
if !light.status {
|
||||
return None;
|
||||
}
|
||||
let mut direction = normalized(
|
||||
let mut direction = normalized_direction(
|
||||
[
|
||||
light.target.x - light.position.x,
|
||||
light.target.y - light.position.y,
|
||||
@@ -1687,19 +1747,7 @@ impl Scene {
|
||||
}
|
||||
}
|
||||
|
||||
// SUN is document-global. Keep it after entity lights so the four-light
|
||||
// shader limit prefers visible lights owned by this viewport's block.
|
||||
let geo = crate::entities::object_data::geo_objects(&self.object_data_cache)
|
||||
.iter()
|
||||
.find_map(|handle| match self.document.objects.get(handle) {
|
||||
Some(ObjectType::GeoData(value))
|
||||
if value.coordinate_type == 3
|
||||
&& value.reference_point.x.is_finite()
|
||||
&& value.reference_point.y.is_finite()
|
||||
&& value.reference_point.x.abs() <= 180.0
|
||||
&& value.reference_point.y.abs() <= 90.0 => Some(value),
|
||||
_ => None,
|
||||
});
|
||||
let geo = self.geolocation();
|
||||
for handle in crate::entities::object_data::sun_objects(&self.object_data_cache) {
|
||||
let Some(ObjectType::ClassObject(value)) = self.document.objects.get(handle) else {
|
||||
continue;
|
||||
@@ -1711,10 +1759,10 @@ impl Scene {
|
||||
continue;
|
||||
}
|
||||
let Some(geo) = geo else {
|
||||
break;
|
||||
continue;
|
||||
};
|
||||
let Some(direction) = solar_direction(sun, geo) else {
|
||||
break;
|
||||
continue;
|
||||
};
|
||||
let rgba = tess_util::aci_to_rgba(&sun.color);
|
||||
lights.push(SceneLight {
|
||||
@@ -1737,7 +1785,6 @@ impl Scene {
|
||||
web_rotation: [0.0; 3],
|
||||
web_enabled: false,
|
||||
});
|
||||
break;
|
||||
}
|
||||
lights
|
||||
}
|
||||
@@ -1756,6 +1803,7 @@ impl Scene {
|
||||
}
|
||||
let cache = self.lighting_cache.borrow();
|
||||
let lights = cache.get(&key).map(Vec::as_slice).unwrap_or_default();
|
||||
let settings = self.viewport_lighting_settings(viewport);
|
||||
let visible_lights: Vec<&SceneLight> = lights
|
||||
.iter()
|
||||
.filter(|light| match self.document.get_entity(light.handle) {
|
||||
@@ -1777,12 +1825,12 @@ impl Scene {
|
||||
object,
|
||||
acadrust::objects::ObjectType::ClassObject(value)
|
||||
if matches!(&value.data, acadrust::objects::ClassObjectData::Sun(_))
|
||||
)
|
||||
) && (!settings.sun_handle.is_valid()
|
||||
|| settings.sun_handle == light.handle)
|
||||
}),
|
||||
})
|
||||
.take(4)
|
||||
.collect();
|
||||
let settings = self.viewport_lighting_settings(viewport);
|
||||
uniforms.lighting[1..4].copy_from_slice(&settings.ambient);
|
||||
if settings.force_default || visible_lights.is_empty() {
|
||||
Self::apply_default_lighting(uniforms, &viewport.camera, settings.default_type);
|
||||
@@ -1933,11 +1981,13 @@ impl Scene {
|
||||
force_default: value.default_lighting,
|
||||
default_type: value.default_lighting_type,
|
||||
ambient: ambient(&value.ambient_color),
|
||||
sun_handle: value.sun_handle,
|
||||
};
|
||||
let from_table = |value: &acadrust::tables::VPort| ViewportLightingSettings {
|
||||
force_default: value.use_default_lights,
|
||||
default_type: value.default_lighting_type,
|
||||
ambient: ambient(&value.ambient_color),
|
||||
sun_handle: value.sun_handle,
|
||||
};
|
||||
|
||||
if viewport.paper_sheet {
|
||||
@@ -1951,13 +2001,7 @@ impl Scene {
|
||||
}
|
||||
} else if self.current_layout == "Model" {
|
||||
let index = viewport.tile_idx.unwrap_or(0);
|
||||
if let Some(value) = self
|
||||
.document
|
||||
.vports
|
||||
.iter()
|
||||
.filter(|value| value.name.eq_ignore_ascii_case("*Active"))
|
||||
.nth(index)
|
||||
{
|
||||
if let Some(value) = self.model_tile_vport(index) {
|
||||
return from_table(value);
|
||||
}
|
||||
}
|
||||
@@ -2025,13 +2069,7 @@ impl Scene {
|
||||
}
|
||||
} else if self.current_layout == "Model" {
|
||||
let index = viewport.tile_idx.unwrap_or(0);
|
||||
if let Some(value) = self
|
||||
.document
|
||||
.vports
|
||||
.iter()
|
||||
.filter(|value| value.name.eq_ignore_ascii_case("*Active"))
|
||||
.nth(index)
|
||||
{
|
||||
if let Some(value) = self.model_tile_vport(index) {
|
||||
return build(
|
||||
value.visual_style_handle,
|
||||
value.brightness,
|
||||
@@ -2079,7 +2117,16 @@ impl Scene {
|
||||
candidates.push(base.join(&source));
|
||||
if let Some(name) = source.file_name() {
|
||||
candidates.push(base.join(name));
|
||||
for folder in ["Textures", "textures", "Materials", "materials"] {
|
||||
for folder in [
|
||||
"Textures",
|
||||
"textures",
|
||||
"Materials",
|
||||
"materials",
|
||||
"Environments",
|
||||
"environments",
|
||||
"Render",
|
||||
"render",
|
||||
] {
|
||||
candidates.push(base.join(folder).join(name));
|
||||
}
|
||||
}
|
||||
@@ -2373,12 +2420,38 @@ impl Scene {
|
||||
}
|
||||
result
|
||||
}
|
||||
ClassObjectData::SkyLightBackground(_) => {
|
||||
ClassObjectData::SkyLightBackground(background) => {
|
||||
let mut result = ViewportBackgroundSettings::canvas(canvas);
|
||||
result.colors[0] = [0.18, 0.42, 0.78, 1.0];
|
||||
result.colors[1] = [1.0, 0.92, 0.72, 0.0];
|
||||
result.colors[2] = [0.78, 0.86, 0.95, 1.0];
|
||||
result.base = result.colors[2];
|
||||
result.params[0] = 6.0;
|
||||
if let Some(ObjectType::ClassObject(value)) =
|
||||
self.document.objects.get(&background.sun)
|
||||
{
|
||||
if let ClassObjectData::Sun(sun) = &value.data {
|
||||
if sun.is_on {
|
||||
if let Some(direction) = self
|
||||
.geolocation()
|
||||
.and_then(|geo| solar_direction(sun, geo))
|
||||
{
|
||||
result.params[1..4].copy_from_slice(&[
|
||||
-direction[0],
|
||||
-direction[1],
|
||||
-direction[2],
|
||||
]);
|
||||
let color = tess_util::aci_to_rgba(&sun.color);
|
||||
result.colors[1] = [
|
||||
color[0],
|
||||
color[1],
|
||||
color[2],
|
||||
sun.intensity.max(0.0) as f32,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
_ => ViewportBackgroundSettings::canvas(canvas),
|
||||
@@ -3467,7 +3540,13 @@ impl Scene {
|
||||
uniforms.background_image_params = display.background.image_params;
|
||||
uniforms.background_image_transform = display.background.image_transform;
|
||||
uniforms.environment_params = display.background.environment_params;
|
||||
uniforms.environment_view = glam::Mat4::from_quat(inst.camera.rotation);
|
||||
let environment_scale = (inst.camera.fov_y * 0.5).tan().max(1e-4);
|
||||
uniforms.environment_view = glam::Mat4::from_quat(inst.camera.rotation)
|
||||
* glam::Mat4::from_scale(glam::Vec3::new(
|
||||
visible_w / visible_h.max(1.0) * environment_scale,
|
||||
environment_scale,
|
||||
1.0,
|
||||
));
|
||||
uniforms.fog_color = display.background.fog_color;
|
||||
uniforms.fog_params = display.background.fog_params;
|
||||
uniforms.fog_distances = display.background.fog_distances;
|
||||
|
||||
+39
-15
@@ -66,8 +66,12 @@ fn vs_main(@builtin(vertex_index) index: u32) -> VertexOutput {
|
||||
return out;
|
||||
}
|
||||
|
||||
fn world_direction(ndc: vec2<f32>) -> vec3<f32> {
|
||||
return normalize((u.environment_view * vec4<f32>(ndc, -1.0, 0.0)).xyz);
|
||||
}
|
||||
|
||||
fn equirect_uv(ndc: vec2<f32>, rotation: f32) -> vec2<f32> {
|
||||
let direction = normalize((u.environment_view * vec4<f32>(ndc, -1.0, 0.0)).xyz);
|
||||
let direction = world_direction(ndc);
|
||||
let longitude = atan2(direction.x, -direction.z) + rotation;
|
||||
let latitude = asin(clamp(direction.y, -1.0, 1.0));
|
||||
return vec2<f32>(fract(longitude / (2.0 * 3.14159265359) + 0.5),
|
||||
@@ -75,8 +79,11 @@ fn equirect_uv(ndc: vec2<f32>, rotation: f32) -> vec2<f32> {
|
||||
}
|
||||
|
||||
fn image_uv(source_uv: vec2<f32>) -> vec2<f32> {
|
||||
var uv = (source_uv - u.background_image_transform.xy)
|
||||
/ max(abs(u.background_image_transform.zw), vec2<f32>(1e-6));
|
||||
var uv = source_uv;
|
||||
if u.background_image_params.x < 0.5 {
|
||||
uv = (source_uv - u.background_image_transform.xy)
|
||||
/ max(abs(u.background_image_transform.zw), vec2<f32>(1e-6));
|
||||
}
|
||||
if u.background_image_params.y > 0.5 {
|
||||
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);
|
||||
@@ -91,7 +98,7 @@ fn image_uv(source_uv: vec2<f32>) -> vec2<f32> {
|
||||
if u.background_image_params.z > 0.5 {
|
||||
return fract(uv);
|
||||
}
|
||||
return clamp(uv, vec2<f32>(0.0), vec2<f32>(1.0));
|
||||
return uv;
|
||||
}
|
||||
|
||||
@fragment
|
||||
@@ -111,24 +118,41 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
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 {
|
||||
let horizon = 0.5;
|
||||
if in.uv.y <= horizon {
|
||||
let sky = smoothstep(0.0, horizon, in.uv.y);
|
||||
color = mix(u.background_top.rgb, u.background_middle.rgb, sky);
|
||||
let direction = world_direction(in.ndc);
|
||||
if direction.z >= 0.0 {
|
||||
let sky = smoothstep(0.0, 1.0, direction.z);
|
||||
color = mix(u.background_middle.rgb, u.background_top.rgb, sky);
|
||||
} else {
|
||||
let ground = smoothstep(horizon, 1.0, in.uv.y);
|
||||
let underground = mix(u.background_bottom.rgb, u.background_aux0.rgb, ground);
|
||||
let distance_tint = mix(u.background_aux1.rgb, u.viewport_background.rgb, ground);
|
||||
color = mix(underground, distance_tint, 0.35);
|
||||
let depth = smoothstep(0.0, 1.0, -direction.z);
|
||||
let underground = mix(u.background_bottom.rgb, u.background_aux0.rgb, depth);
|
||||
let ground = mix(u.viewport_background.rgb, u.background_aux1.rgb, depth);
|
||||
color = mix(underground, ground, 0.65);
|
||||
}
|
||||
} else if mode == 4u {
|
||||
color = textureSample(background_texture, background_sampler, image_uv(in.uv)).rgb;
|
||||
let uv = image_uv(in.uv);
|
||||
if u.background_image_params.z < 0.5
|
||||
&& (any(uv < vec2<f32>(0.0)) || any(uv > vec2<f32>(1.0))) {
|
||||
color = u.viewport_background.rgb;
|
||||
} else {
|
||||
color = textureSample(background_texture, background_sampler, uv).rgb;
|
||||
}
|
||||
} else if mode == 5u {
|
||||
color = textureSample(background_texture, background_sampler,
|
||||
equirect_uv(in.ndc, u.background_params.w)).rgb;
|
||||
} else if mode == 6u {
|
||||
let t = smoothstep(0.0, 1.0, in.uv.y);
|
||||
color = mix(u.background_bottom.rgb, u.background_top.rgb, t);
|
||||
let direction = world_direction(in.ndc);
|
||||
let elevation = clamp(direction.z, 0.0, 1.0);
|
||||
color = mix(u.background_bottom.rgb, u.background_top.rgb,
|
||||
smoothstep(0.0, 1.0, elevation));
|
||||
let sun_length = length(u.background_params.yzw);
|
||||
if sun_length > 0.5 {
|
||||
let sun_direction = u.background_params.yzw / sun_length;
|
||||
let alignment = dot(direction, sun_direction);
|
||||
let glow = smoothstep(0.96, 1.0, alignment) * 0.18;
|
||||
let disk = smoothstep(0.9997, 0.99995, alignment);
|
||||
let intensity = max(u.background_middle.a, 0.0);
|
||||
color += u.background_middle.rgb * intensity * (glow + disk);
|
||||
}
|
||||
} else {
|
||||
color = u.viewport_background.rgb;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user