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:
Hakan Seven committed 2026-08-12 13:11:12 +03:00
1 parent 0be9c41773
commit 15ab027d70
3 files changed
+357 -179

No files matched your search

+122 -47
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}