Tweaks to shadows.

Added shadow map resolution configuration, added seamless cubemaps,
documented all existing rendering options, and fixed a few Clippy
errors.
This commit is contained in:
Joshua Yanovski 2020-07-12 18:28:08 +02:00
parent 23b4058906
commit be438657c3
16 changed files with 355 additions and 110 deletions

View File

@ -298,7 +298,7 @@ magischen Gegenstände ergattern?"#,
"hud.settings.cloud_rendering_mode.regular": "Realistisch", "hud.settings.cloud_rendering_mode.regular": "Realistisch",
"hud.settings.fullscreen": "Vollbild", "hud.settings.fullscreen": "Vollbild",
"hud.settings.lighting_rendering_mode": "Beleuchtung", "hud.settings.lighting_rendering_mode": "Beleuchtung",
"hud.settings.lighting_rendering_mode.ashikmin": "Typ A", "hud.settings.lighting_rendering_mode.ashikhmin": "Typ A",
"hud.settings.lighting_rendering_mode.blinnphong": "Typ B", "hud.settings.lighting_rendering_mode.blinnphong": "Typ B",
"hud.settings.lighting_rendering_mode.lambertian": "Typ L", "hud.settings.lighting_rendering_mode.lambertian": "Typ L",
"hud.settings.shadow_rendering_mode": "Schatten", "hud.settings.shadow_rendering_mode": "Schatten",
@ -433,4 +433,4 @@ Willenskraft
"Hilfe, ich werde angegriffen!", "Hilfe, ich werde angegriffen!",
], ],
} }
) )

View File

@ -298,13 +298,14 @@ magically infused items?"#,
"hud.settings.fullscreen": "Fullscreen", "hud.settings.fullscreen": "Fullscreen",
"hud.settings.save_window_size": "Save window size", "hud.settings.save_window_size": "Save window size",
"hud.settings.lighting_rendering_mode": "Lighting Rendering Mode", "hud.settings.lighting_rendering_mode": "Lighting Rendering Mode",
"hud.settings.lighting_rendering_mode.ashikmin": "Type A", "hud.settings.lighting_rendering_mode.ashikhmin": "Type A",
"hud.settings.lighting_rendering_mode.blinnphong": "Type B", "hud.settings.lighting_rendering_mode.blinnphong": "Type B",
"hud.settings.lighting_rendering_mode.lambertian": "Type L", "hud.settings.lighting_rendering_mode.lambertian": "Type L",
"hud.settings.shadow_rendering_mode": "Shadow Rendering Mode", "hud.settings.shadow_rendering_mode": "Shadow Rendering Mode",
"hud.settings.shadow_rendering_mode.none": "None", "hud.settings.shadow_rendering_mode.none": "None",
"hud.settings.shadow_rendering_mode.cheap": "Cheap", "hud.settings.shadow_rendering_mode.cheap": "Cheap",
"hud.settings.shadow_rendering_mode.map": "Map", "hud.settings.shadow_rendering_mode.map": "Map",
"hud.settings.shadow_rendering_mode.map.resolution": "Resolution",
"hud.settings.lod_detail": "LoD Detail", "hud.settings.lod_detail": "LoD Detail",
"hud.settings.save_window_size": "Save window size", "hud.settings.save_window_size": "Save window size",

View File

@ -30,8 +30,8 @@ uniform u_locals {
out vec4 tgt_color; out vec4 tgt_color;
void main() { void main() {
// tgt_color = vec4(MU_SCATTER, 1.0); tgt_color = vec4(MU_SCATTER, 1.0);
// return; return;
vec4 _clouds; vec4 _clouds;
vec3 cam_dir = normalize(f_pos - cam_pos.xyz); vec3 cam_dir = normalize(f_pos - cam_pos.xyz);

View File

@ -134,8 +134,8 @@ impl Client {
// We reduce the thread count by 1 to keep rendering smooth // We reduce the thread count by 1 to keep rendering smooth
thread_pool.set_num_threads((num_cpus::get() - 1).max(1)); thread_pool.set_num_threads((num_cpus::get() - 1).max(1));
let (network, f) = Network::new(Pid::new(), None); let (network, scheduler) = Network::new(Pid::new(), None);
thread_pool.execute(f); thread_pool.execute(scheduler);
let participant = block_on(network.connect(Address::Tcp(addr.into())))?; let participant = block_on(network.connect(Address::Tcp(addr.into())))?;
let mut stream = block_on(participant.open(10, PROMISES_ORDERED | PROMISES_CONSISTENCY))?; let mut stream = block_on(participant.open(10, PROMISES_ORDERED | PROMISES_CONSISTENCY))?;

View File

@ -4,7 +4,7 @@ use super::{
}; };
use crate::{ use crate::{
i18n::{list_localizations, LanguageMetadata, VoxygenLocalization}, i18n::{list_localizations, LanguageMetadata, VoxygenLocalization},
render::{AaMode, CloudMode, FluidMode, LightingMode, RenderMode, ShadowMode}, render::{AaMode, CloudMode, FluidMode, LightingMode, RenderMode, ShadowMapMode, ShadowMode},
ui::{fonts::ConrodVoxygenFonts, ImageSlider, ScaleMode, ToggleButton}, ui::{fonts::ConrodVoxygenFonts, ImageSlider, ScaleMode, ToggleButton},
window::GameInput, window::GameInput,
GlobalState, GlobalState,
@ -16,6 +16,7 @@ use conrod_core::{
widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget, widget_ids, Borderable, Color, Colorable, Labelable, Positionable, Sizeable, Widget,
WidgetCommon, WidgetCommon,
}; };
use core::convert::TryFrom;
const FPS_CHOICES: [u32; 11] = [15, 30, 40, 50, 60, 90, 120, 144, 240, 300, 500]; const FPS_CHOICES: [u32; 11] = [15, 30, 40, 50, 60, 90, 120, 144, 240, 300, 500];
@ -119,6 +120,9 @@ widget_ids! {
lighting_mode_list, lighting_mode_list,
shadow_mode_text, shadow_mode_text,
shadow_mode_list, shadow_mode_list,
shadow_mode_map_resolution_text,
shadow_mode_map_resolution_slider,
shadow_mode_map_resolution_value,
save_window_size_button, save_window_size_button,
audio_volume_slider, audio_volume_slider,
audio_volume_text, audio_volume_text,
@ -1884,6 +1888,8 @@ impl<'a> Widget for SettingsWindow<'a> {
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.ids.figure_dist_value, ui); .set(state.ids.figure_dist_value, ui);
let render_mode = self.global_state.settings.graphics.render_mode;
// AaMode // AaMode
Text::new(&self.localized_strings.get("hud.settings.antialiasing_mode")) Text::new(&self.localized_strings.get("hud.settings.antialiasing_mode"))
.down_from(state.ids.gamma_slider, 8.0) .down_from(state.ids.gamma_slider, 8.0)
@ -1910,9 +1916,7 @@ impl<'a> Widget for SettingsWindow<'a> {
]; ];
// Get which AA mode is currently active // Get which AA mode is currently active
let selected = mode_list let selected = mode_list.iter().position(|x| *x == render_mode.aa);
.iter()
.position(|x| *x == self.global_state.settings.graphics.render_mode.aa);
if let Some(clicked) = DropDownList::new(&mode_label_list, selected) if let Some(clicked) = DropDownList::new(&mode_label_list, selected)
.w_h(400.0, 22.0) .w_h(400.0, 22.0)
@ -1924,7 +1928,7 @@ impl<'a> Widget for SettingsWindow<'a> {
{ {
events.push(Event::ChangeRenderMode(RenderMode { events.push(Event::ChangeRenderMode(RenderMode {
aa: mode_list[clicked], aa: mode_list[clicked],
..self.global_state.settings.graphics.render_mode ..render_mode
})); }));
} }
@ -1949,9 +1953,7 @@ impl<'a> Widget for SettingsWindow<'a> {
]; ];
// Get which cloud rendering mode is currently active // Get which cloud rendering mode is currently active
let selected = mode_list let selected = mode_list.iter().position(|x| *x == render_mode.cloud);
.iter()
.position(|x| *x == self.global_state.settings.graphics.render_mode.cloud);
if let Some(clicked) = DropDownList::new(&mode_label_list, selected) if let Some(clicked) = DropDownList::new(&mode_label_list, selected)
.w_h(400.0, 22.0) .w_h(400.0, 22.0)
@ -1963,7 +1965,7 @@ impl<'a> Widget for SettingsWindow<'a> {
{ {
events.push(Event::ChangeRenderMode(RenderMode { events.push(Event::ChangeRenderMode(RenderMode {
cloud: mode_list[clicked], cloud: mode_list[clicked],
..self.global_state.settings.graphics.render_mode ..render_mode
})); }));
} }
@ -1990,9 +1992,7 @@ impl<'a> Widget for SettingsWindow<'a> {
]; ];
// Get which fluid rendering mode is currently active // Get which fluid rendering mode is currently active
let selected = mode_list let selected = mode_list.iter().position(|x| *x == render_mode.fluid);
.iter()
.position(|x| *x == self.global_state.settings.graphics.render_mode.fluid);
if let Some(clicked) = DropDownList::new(&mode_label_list, selected) if let Some(clicked) = DropDownList::new(&mode_label_list, selected)
.w_h(400.0, 22.0) .w_h(400.0, 22.0)
@ -2004,7 +2004,7 @@ impl<'a> Widget for SettingsWindow<'a> {
{ {
events.push(Event::ChangeRenderMode(RenderMode { events.push(Event::ChangeRenderMode(RenderMode {
fluid: mode_list[clicked], fluid: mode_list[clicked],
..self.global_state.settings.graphics.render_mode ..render_mode
})); }));
} }
@ -2021,14 +2021,14 @@ impl<'a> Widget for SettingsWindow<'a> {
.set(state.ids.lighting_mode_text, ui); .set(state.ids.lighting_mode_text, ui);
let mode_list = [ let mode_list = [
LightingMode::Ashikmin, LightingMode::Ashikhmin,
LightingMode::BlinnPhong, LightingMode::BlinnPhong,
LightingMode::Lambertian, LightingMode::Lambertian,
]; ];
let mode_label_list = [ let mode_label_list = [
&self &self
.localized_strings .localized_strings
.get("hud.settings.lighting_rendering_mode.ashikmin"), .get("hud.settings.lighting_rendering_mode.ashikhmin"),
&self &self
.localized_strings .localized_strings
.get("hud.settings.lighting_rendering_mode.blinnphong"), .get("hud.settings.lighting_rendering_mode.blinnphong"),
@ -2038,9 +2038,7 @@ impl<'a> Widget for SettingsWindow<'a> {
]; ];
// Get which lighting rendering mode is currently active // Get which lighting rendering mode is currently active
let selected = mode_list let selected = mode_list.iter().position(|x| *x == render_mode.lighting);
.iter()
.position(|x| *x == self.global_state.settings.graphics.render_mode.lighting);
if let Some(clicked) = DropDownList::new(&mode_label_list, selected) if let Some(clicked) = DropDownList::new(&mode_label_list, selected)
.w_h(400.0, 22.0) .w_h(400.0, 22.0)
@ -2052,7 +2050,7 @@ impl<'a> Widget for SettingsWindow<'a> {
{ {
events.push(Event::ChangeRenderMode(RenderMode { events.push(Event::ChangeRenderMode(RenderMode {
lighting: mode_list[clicked], lighting: mode_list[clicked],
..self.global_state.settings.graphics.render_mode ..render_mode
})); }));
} }
@ -2068,7 +2066,12 @@ impl<'a> Widget for SettingsWindow<'a> {
.color(TEXT_COLOR) .color(TEXT_COLOR)
.set(state.ids.shadow_mode_text, ui); .set(state.ids.shadow_mode_text, ui);
let mode_list = [ShadowMode::None, ShadowMode::Cheap, ShadowMode::Map]; let shadow_map_mode = ShadowMapMode::try_from(render_mode.shadow).ok();
let mode_list = [
ShadowMode::None,
ShadowMode::Cheap,
ShadowMode::Map(shadow_map_mode.unwrap_or_default()),
];
let mode_label_list = [ let mode_label_list = [
&self &self
.localized_strings .localized_strings
@ -2082,9 +2085,7 @@ impl<'a> Widget for SettingsWindow<'a> {
]; ];
// Get which shadow rendering mode is currently active // Get which shadow rendering mode is currently active
let selected = mode_list let selected = mode_list.iter().position(|x| *x == render_mode.shadow);
.iter()
.position(|x| *x == self.global_state.settings.graphics.render_mode.shadow);
if let Some(clicked) = DropDownList::new(&mode_label_list, selected) if let Some(clicked) = DropDownList::new(&mode_label_list, selected)
.w_h(400.0, 22.0) .w_h(400.0, 22.0)
@ -2096,10 +2097,56 @@ impl<'a> Widget for SettingsWindow<'a> {
{ {
events.push(Event::ChangeRenderMode(RenderMode { events.push(Event::ChangeRenderMode(RenderMode {
shadow: mode_list[clicked], shadow: mode_list[clicked],
..self.global_state.settings.graphics.render_mode ..render_mode
})); }));
} }
if let Some(shadow_map_mode) = shadow_map_mode {
// Display the shadow map mode if selected.
Text::new(
&self
.localized_strings
.get("hud.settings.shadow_rendering_mode.map.resolution"),
)
.right_from(state.ids.shadow_mode_list, 10.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.shadow_mode_map_resolution_text, ui);
if let Some(new_val) = ImageSlider::discrete(
(shadow_map_mode.resolution.log2() * 4.0).round() as i8,
-8,
8,
self.imgs.slider_indicator,
self.imgs.slider,
)
.w_h(104.0, 22.0)
.right_from(state.ids.shadow_mode_map_resolution_text, 8.0)
.track_breadth(12.0)
.slider_length(10.0)
.pad_track((5.0, 5.0))
.set(state.ids.shadow_mode_map_resolution_slider, ui)
{
events.push(Event::ChangeRenderMode(RenderMode {
shadow: ShadowMode::Map(ShadowMapMode {
resolution: 2.0f32.powf(f32::from(new_val) / 4.0),
..shadow_map_mode
}),
..render_mode
}));
}
// TODO: Consider fixing to avoid allocation (it's probably not a bottleneck but
// there's no reason to allocate for numbers).
Text::new(&format!("{}", shadow_map_mode.resolution))
.right_from(state.ids.shadow_mode_map_resolution_slider, 8.0)
.font_size(self.fonts.cyri.scale(14))
.font_id(self.fonts.cyri.conrod_id)
.color(TEXT_COLOR)
.set(state.ids.shadow_mode_map_resolution_value, ui);
}
// Fullscreen // Fullscreen
Text::new(&self.localized_strings.get("hud.settings.fullscreen")) Text::new(&self.localized_strings.get("hud.settings.fullscreen"))
.font_size(self.fonts.cyri.scale(14)) .font_size(self.fonts.cyri.scale(14))

View File

@ -10,6 +10,7 @@ pub enum RenderError {
IncludeError(glsl_include::Error), IncludeError(glsl_include::Error),
MappingError(gfx::mapping::Error), MappingError(gfx::mapping::Error),
CopyError(gfx::CopyError<[u16; 3], usize>), CopyError(gfx::CopyError<[u16; 3], usize>),
CustomError(String),
} }
impl From<gfx::PipelineStateError<String>> for RenderError { impl From<gfx::PipelineStateError<String>> for RenderError {

View File

@ -63,13 +63,38 @@ pub trait Pipeline {
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
/// Anti-aliasing modes /// Anti-aliasing modes
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] #[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum AaMode { pub enum AaMode {
None, None,
/// Fast approximate antialiasing.
///
/// This is a screen-space technique, and therefore
Fxaa, Fxaa,
/// Multisampling AA, up to 4 samples per pixel.
///
/// NOTE: MSAA modes don't (currently) work with greedy meshing, and will
/// also struggle in the futrue with deferred shading, so they may be
/// removed in the future.
MsaaX4, MsaaX4,
/// Multisampling AA, up to 8 samples per pixel.
///
/// NOTE: MSAA modes don't (currently) work with greedy meshing, and will
/// also struggle in the futrue with deferred shading, so they may be
/// removed in the future.
MsaaX8, MsaaX8,
/// Multisampling AA, up to 16 samples per pixel.
///
/// NOTE: MSAA modes don't (currently) work with greedy meshing, and will
/// also struggle in the futrue with deferred shading, so they may be
/// removed in the future.
MsaaX16, MsaaX16,
/// Super-sampling antialiasing, 4 samples per pixel.
///
/// Unlike MSAA, SSAA *always* performs 4 samples per pixel, rather than
/// trying to choose importance samples at boundary regions, so it works
/// much better with techniques like deferred rendering and greedy
/// meshing that (without significantly more work) invalidate the
/// GPU's assumptions about importance sampling.
SsaaX4, SsaaX4,
} }
@ -78,9 +103,38 @@ impl Default for AaMode {
} }
/// Cloud modes /// Cloud modes
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] #[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum CloudMode { pub enum CloudMode {
/// No clouds. On computers that can't handle loops well, have performance
/// issues in fragment shaders in general, or just have large
/// resolutions, this can be a *very* impactful performance difference.
/// Part of that is because of inefficiencies in how we implement
/// regular clouds. It is still not all that cheap on low-end machines, due
/// to many calculations being performed that use relatively expensive
/// functions, and at some point I'd like to both optimize the regular
/// sky shader further and create an even cheaper option.
None, None,
/// Volumetric clouds. This option can be *very* expensive on low-end
/// machines, to the point of making the game unusable, for several
/// reasons:
///
/// - The volumetric clouds use raymarching, which will cause catastrophic
/// performance degradation on GPUs without good support for loops. There
/// is an attempt to minimize the impact of this using a z-range check,
/// but on some low-end GPUs (such as some integraetd graphics cards) this
/// test doesn't appear to be able to be predicted well at shader
/// invocation time.
/// - The cloud computations themselves are fairly involved, further
/// degrading performance.
/// - Although the sky shader is always drawn at the outer edges of the
/// skybox, the clouds themselves are supposed to be positioned much
/// lower, which means the depth check for the skybox incorrectly cuts off
/// clouds in some places. To compensate for these cases (e.g. where
/// terrain is occluded by clouds from above, and the camera is above the
/// clouds), we currently branch to see if we need to render the clouds in
/// *every* fragment shader. For machines that can't optimize the check,
/// this is absurdly expensive, so we should look at alternatives in the
/// future that player better iwth the GPU.
Regular, Regular,
} }
@ -89,9 +143,28 @@ impl Default for CloudMode {
} }
/// Fluid modes /// Fluid modes
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] #[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum FluidMode { pub enum FluidMode {
/// "Cheap" water. This water implements no waves, no reflections, no
/// diffraction, and no light attenuation through water. As a result,
/// it can be much cheaper than shiny reflection.
Cheap, Cheap,
/// "Shiny" water. This water implements waves on the surfaces, some
/// attempt at reflections, and tries to compute accurate light
/// attenuation through water (this is what results in the
/// colors changing as you descend into deep water).
///
/// Unfortunately, the way the engine is currently set up, calculating
/// accurate attenuation is a bit difficult; we use estimates from
/// horizon maps for the current water altitude, which can both be off
/// by up to (max_altitude / 255) meters, only has per-chunk horizontal
/// resolution, and cannot handle edge cases like horizontal water (e.g.
/// waterfalls) well. We are okay with the latter, and will try to fix
/// the former soon.
///
/// Another issue is that we don't always know whether light is *blocked*,
/// which causes attenuation to be computed incorrectly; this can be
/// addressed by using shadow maps (at least for terrain).
Shiny, Shiny,
} }
@ -100,10 +173,22 @@ impl Default for FluidMode {
} }
/// Lighting modes /// Lighting modes
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] #[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum LightingMode { pub enum LightingMode {
Ashikmin, /// Ashikhmin-Shirley BRDF lighting model. Attempts to generate a
/// physically plausible (to some extent) lighting distribution.
///
/// This mdoel may not work as well with purely directional lighting, and is
/// more expensive than the otehr models.
Ashikhmin,
/// Standard Blinn-Phong shading, combing Lambertian diffuse reflections and
/// specular highlights.
BlinnPhong, BlinnPhong,
/// Standard Lambertian lighting model, with only diffuse reflections. The
/// cheapest lighting model by a decent margin, but the performance
/// dfifference between it and Blinn-Phong will probably only be
/// significant on low-end machines that are bottlenecked on fragment
/// shading.
Lambertian, Lambertian,
} }
@ -111,25 +196,67 @@ impl Default for LightingMode {
fn default() -> Self { LightingMode::BlinnPhong } fn default() -> Self { LightingMode::BlinnPhong }
} }
/// Shadow map settings.
#[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)]
pub struct ShadowMapMode {
/// Multiple of default resolution (default is currenttly the closest higher
/// power of two above the length of the longest diagonal of the screen
/// resolution, but this may change).
pub resolution: f32,
}
impl Default for ShadowMapMode {
fn default() -> Self { Self { resolution: 1.0 } }
}
/// Shadow modes /// Shadow modes
#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] #[derive(PartialEq, Clone, Copy, Debug, Serialize, Deserialize)]
pub enum ShadowMode { pub enum ShadowMode {
/// No shadows at all. By far the cheapest option.
None, None,
/// Point shadows (draw circles under figures, up to a configured maximum;
/// also render LOD shadows using horizon maps). Can be expensive on
/// some machines, probably mostly due to horizon mapping; the point
/// shadows are not rendered too efficiently, but that can probably
/// be addressed later.
Cheap, Cheap,
/// Multiple of resolution. /// Shadow map (render the scene from each light source, and also renders
Map, /* (f32) */ /// LOD shadows using horizon maps).
Map(ShadowMapMode),
} }
impl Default for ShadowMode { impl Default for ShadowMode {
fn default() -> Self { ShadowMode::Cheap } fn default() -> Self { ShadowMode::Cheap }
} }
impl core::convert::TryFrom<ShadowMode> for ShadowMapMode {
type Error = ();
/// Get the shadow map details if they exist.
fn try_from(value: ShadowMode) -> Result<Self, Self::Error> {
if let ShadowMode::Map(map) = value {
Ok(map)
} else {
Err(())
}
}
}
impl ShadowMode {
pub fn is_map(&self) -> bool { if let Self::Map(_) = self { true } else { false } }
}
/// Render modes /// Render modes
#[derive(PartialEq, Eq, Clone, Copy, Debug, Default, Serialize, Deserialize)] #[derive(PartialEq, Clone, Copy, Debug, Default, Serialize, Deserialize)]
pub struct RenderMode { pub struct RenderMode {
#[serde(default)]
pub aa: AaMode, pub aa: AaMode,
#[serde(default)]
pub cloud: CloudMode, pub cloud: CloudMode,
#[serde(default)]
pub fluid: FluidMode, pub fluid: FluidMode,
#[serde(default)]
pub lighting: LightingMode, pub lighting: LightingMode,
#[serde(default)]
pub shadow: ShadowMode, pub shadow: ShadowMode,
} }

View File

@ -10,7 +10,7 @@ use super::{
}, },
texture::Texture, texture::Texture,
AaMode, CloudMode, FilterMethod, FluidMode, LightingMode, Pipeline, RenderError, RenderMode, AaMode, CloudMode, FilterMethod, FluidMode, LightingMode, Pipeline, RenderError, RenderMode,
ShadowMode, WrapMode, ShadowMapMode, ShadowMode, WrapMode,
}; };
use common::assets::{self, watch::ReloadIndicator}; use common::assets::{self, watch::ReloadIndicator};
use core::convert::TryFrom; use core::convert::TryFrom;
@ -144,20 +144,29 @@ impl Renderer {
/// Create a new `Renderer` from a variety of backend-specific components /// Create a new `Renderer` from a variety of backend-specific components
/// and the window targets. /// and the window targets.
pub fn new( pub fn new(
device: gfx_backend::Device, mut device: gfx_backend::Device,
mut factory: gfx_backend::Factory, mut factory: gfx_backend::Factory,
win_color_view: WinColorView, win_color_view: WinColorView,
win_depth_view: WinDepthView, win_depth_view: WinDepthView,
mode: RenderMode, mode: RenderMode,
) -> Result<Self, RenderError> { ) -> Result<Self, RenderError> {
// Enable seamless cubemaps globally, where available--they are essentially a
// strict improvement on regular cube maps.
//
// Note that since we only have to enable this once globally, there is no point
// in doing this on rerender.
Self::enable_seamless_cube_maps(&mut device);
let dims = win_color_view.get_dimensions(); let dims = win_color_view.get_dimensions();
let mut shader_reload_indicator = ReloadIndicator::new(); let mut shader_reload_indicator = ReloadIndicator::new();
let shadow_views = Self::create_shadow_views(&mut factory, (dims.0, dims.1)) let shadow_views = ShadowMapMode::try_from(mode.shadow).ok().and_then(|mode| {
.map_err(|err| { Self::create_shadow_views(&mut factory, (dims.0, dims.1), &mode)
warn!("Could not create shadow map views: {:?}", err); .map_err(|err| {
}) warn!("Could not create shadow map views: {:?}", err);
.ok(); })
.ok()
});
let ( let (
skybox_pipeline, skybox_pipeline,
@ -320,8 +329,10 @@ impl Renderer {
self.tgt_color_res = tgt_color_res; self.tgt_color_res = tgt_color_res;
self.tgt_color_view = tgt_color_view; self.tgt_color_view = tgt_color_view;
self.tgt_depth_stencil_view = tgt_depth_stencil_view; self.tgt_depth_stencil_view = tgt_depth_stencil_view;
if let Some(shadow_map) = self.shadow_map.as_mut() { if let (Some(shadow_map), ShadowMode::Map(mode)) =
match Self::create_shadow_views(&mut self.factory, (dims.0, dims.1)) { (self.shadow_map.as_mut(), self.mode.shadow)
{
match Self::create_shadow_views(&mut self.factory, (dims.0, dims.1), &mode) {
Ok(( Ok((
point_depth_stencil_view, point_depth_stencil_view,
point_res, point_res,
@ -407,6 +418,7 @@ impl Renderer {
fn create_shadow_views( fn create_shadow_views(
factory: &mut gfx_device_gl::Factory, factory: &mut gfx_device_gl::Factory,
size: (u16, u16), size: (u16, u16),
mode: &ShadowMapMode,
) -> Result< ) -> Result<
( (
ShadowDepthStencilView, ShadowDepthStencilView,
@ -418,17 +430,40 @@ impl Renderer {
), ),
RenderError, RenderError,
> { > {
let size = Vec2::new(size.0, size.1); // (Attempt to) apply resolution factor to shadow map resolution.
let resolution_factor = mode.resolution.clamped(0.25, 4.0);
fn vec2_result<T, E>(v: Vec2<Result<T, E>>) -> Result<Vec2<T>, E> {
Ok(Vec2::new(v.x?, v.y?))
};
let max_texture_size = Self::max_texture_size_raw(factory); let max_texture_size = Self::max_texture_size_raw(factory);
let size = vec2_result(Vec2::new(size.0, size.1).map(|e| {
let size = f32::from(e) * resolution_factor;
// NOTE: We know 0 <= e since we clamped the resolution factor to be between
// 0.25 and 4.0.
if size <= f32::from(max_texture_size) {
Ok(size as u16)
} else {
Err(RenderError::CustomError(format!(
"Resolution factor {:?} multiplied by screen resolution axis {:?} does not \
fit in a texture on this machine.",
resolution_factor, e
)))
}
}))?;
let levels = 1; //10; let levels = 1; //10;
let two_size = size.map(|e| { let two_size = vec2_result(size.map(|e| {
u16::checked_next_power_of_two(e) u16::checked_next_power_of_two(e)
.filter(|&e| e <= max_texture_size) .filter(|&e| e <= max_texture_size)
.expect( .ok_or_else(|| {
"Next power of two for max screen resolution axis does not fit in a texture \ RenderError::CustomError(format!(
on this machine.", "Next power of two for shadow map resolution axis {:?} does not fit in a \
) texture on this machine.",
}); e
))
})
}))?;
let min_size = size.reduce_min(); let min_size = size.reduce_min();
let max_size = size.reduce_max(); let max_size = size.reduce_max();
let _min_two_size = two_size.reduce_min(); let _min_two_size = two_size.reduce_min();
@ -437,23 +472,30 @@ impl Renderer {
// size of a diagonal along that axis. // size of a diagonal along that axis.
let diag_size = size.map(f64::from).magnitude(); let diag_size = size.map(f64::from).magnitude();
let diag_cross_size = f64::from(min_size) / f64::from(max_size) * diag_size; let diag_cross_size = f64::from(min_size) / f64::from(max_size) * diag_size;
let (diag_size, _diag_cross_size) = let (diag_size, _diag_cross_size) = if 0.0 < diag_size
if 0.0 < diag_size && diag_size <= f64::from(max_texture_size) { && diag_size <= f64::from(max_texture_size)
// NOTE: diag_cross_size must be non-negative, since it is the ratio of a {
// non-negative and a positive number (if max_size were zero, // NOTE: diag_cross_size must be non-negative, since it is the ratio of a
// diag_size would be 0 too). And it must be <= diag_size, // non-negative and a positive number (if max_size were zero,
// since min_size <= max_size. Therefore, if diag_size fits in a // diag_size would be 0 too). And it must be <= diag_size,
// u16, so does diag_cross_size. // since min_size <= max_size. Therefore, if diag_size fits in a
(diag_size as u16, diag_cross_size as u16) // u16, so does diag_cross_size.
} else { (diag_size as u16, diag_cross_size as u16)
panic!("Resolution of screen diagonal does not fit in a texture on this machine."); } else {
}; return Err(RenderError::CustomError(format!(
"Resolution of shadow map diagonal {:?} does not fit in a texture on this machine.",
diag_size
)));
};
let diag_two_size = u16::checked_next_power_of_two(diag_size) let diag_two_size = u16::checked_next_power_of_two(diag_size)
.filter(|&e| e <= max_texture_size) .filter(|&e| e <= max_texture_size)
.expect( .ok_or_else(|| {
"Next power of two for resolution of screen diagonal does not fit in a texture on \ RenderError::CustomError(format!(
this machine.", "Next power of two for resolution of shadow map diagonal {:?} does not fit in \
); a texture on this machine.",
diag_size
))
})?;
/* let color_cty = <<TgtColorFmt as gfx::format::Formatted>::Channel as gfx::format::ChannelTyped /* let color_cty = <<TgtColorFmt as gfx::format::Formatted>::Channel as gfx::format::ChannelTyped
>::get_channel_type(); >::get_channel_type();
let tgt_color_tex = factory.create_texture( let tgt_color_tex = factory.create_texture(
@ -619,7 +661,7 @@ impl Renderer {
/// Queue the clearing of the shadow targets ready for a new frame to be /// Queue the clearing of the shadow targets ready for a new frame to be
/// rendered. /// rendered.
pub fn clear_shadows(&mut self) { pub fn clear_shadows(&mut self) {
if self.mode.shadow != ShadowMode::Map { if !self.mode.shadow.is_map() {
return; return;
} }
if let Some(shadow_map) = self.shadow_map.as_mut() { if let Some(shadow_map) = self.shadow_map.as_mut() {
@ -633,18 +675,45 @@ impl Renderer {
} }
} }
/// NOTE: Supported by Vulkan (by default), DirectX 10+ (it seems--it's hard
/// to find proof of this, but Direct3D 10 apparently does it by
/// default, and 11 definitely does, so I assume it's natively supported
/// by DirectX itself), OpenGL 3.2+, and Metal (done by default). While
/// there may be some GPUs that don't quite support it correctly, the
/// impact is relatively small, so there is no reaosn not to enable it where
/// available.
#[allow(unsafe_code)]
fn enable_seamless_cube_maps(device: &mut gfx_backend::Device) {
unsafe {
// NOTE: Currently just fail silently rather than complain if the computer is on
// a version lower than 3.2, where seamless cubemaps were introduced.
if !device.get_info().is_version_supported(3, 2) {
// println!("whoops");
return;
}
// NOTE: Safe because GL_TEXTURE_CUBE_MAP_SEAMLESS is supported by OpenGL 3.2+
// (see https://www.khronos.org/opengl/wiki/Cubemap_Texture#Seamless_cubemap);
// enabling seamless cube maps should always be safe regardless of the state of
// the OpenGL context, so no further checks are needd.
device.with_gl(|gl| {
gl.Enable(gfx_gl::TEXTURE_CUBE_MAP_SEAMLESS);
});
}
}
/// NOTE: Supported by all but a handful of mobile GPUs /// NOTE: Supported by all but a handful of mobile GPUs
/// (see https://github.com/gpuweb/gpuweb/issues/480) /// (see https://github.com/gpuweb/gpuweb/issues/480)
/// so wgpu should support it too. /// so wgpu should support it too.
#[allow(unsafe_code)] #[allow(unsafe_code)]
fn set_depth_clamp(&mut self, depth_clamp: bool) { fn set_depth_clamp(device: &mut gfx_backend::Device, depth_clamp: bool) {
unsafe { unsafe {
// NOTE: Currently just fail silently rather than complain if the computer is on // NOTE: Currently just fail silently rather than complain if the computer is on
// a version lower than 3.3, though we probably will complain // a version lower than 3.3, though we probably will complain
// elsewhere regardless, since shadow mapping is an optional feature // elsewhere regardless, since shadow mapping is an optional feature
// and having depth clamping disabled won't cause undefined // and having depth clamping disabled won't cause undefined
// behavior, just incorrect shadowing from objects behind the viewer. // behavior, just incorrect shadowing from objects behind the viewer.
if !self.device.get_info().is_version_supported(3, 3) { if !device.get_info().is_version_supported(3, 3) {
// println!("whoops"); // println!("whoops");
return; return;
} }
@ -654,7 +723,7 @@ impl Renderer {
// may use different extensions. Also, enabling depth clamping should // may use different extensions. Also, enabling depth clamping should
// essentially always be safe regardless of the state of the OpenGL // essentially always be safe regardless of the state of the OpenGL
// context, so no further checks are needed. // context, so no further checks are needed.
self.device.with_gl(|gl| { device.with_gl(|gl| {
// println!("gl.Enable(gfx_gl::DEPTH_CLAMP) = {:?}", // println!("gl.Enable(gfx_gl::DEPTH_CLAMP) = {:?}",
// gl.IsEnabled(gfx_gl::DEPTH_CLAMP)); // gl.IsEnabled(gfx_gl::DEPTH_CLAMP));
if depth_clamp { if depth_clamp {
@ -676,18 +745,18 @@ impl Renderer {
/// Set up shadow rendering. /// Set up shadow rendering.
pub fn start_shadows(&mut self) { pub fn start_shadows(&mut self) {
if self.mode.shadow != ShadowMode::Map { if !self.mode.shadow.is_map() {
return; return;
} }
if let Some(_shadow_map) = self.shadow_map.as_mut() { if let Some(_shadow_map) = self.shadow_map.as_mut() {
self.encoder.flush(&mut self.device); self.encoder.flush(&mut self.device);
self.set_depth_clamp(true); Self::set_depth_clamp(&mut self.device, true);
} }
} }
/// Perform all queued draw calls for shadows. /// Perform all queued draw calls for shadows.
pub fn flush_shadows(&mut self) { pub fn flush_shadows(&mut self) {
if self.mode.shadow != ShadowMode::Map { if !self.mode.shadow.is_map() {
return; return;
} }
if let Some(_shadow_map) = self.shadow_map.as_mut() { if let Some(_shadow_map) = self.shadow_map.as_mut() {
@ -697,7 +766,7 @@ impl Renderer {
// let directed_encoder = &mut shadow_map.directed_encoder; // let directed_encoder = &mut shadow_map.directed_encoder;
// directed_encoder.flush(&mut self.device); // directed_encoder.flush(&mut self.device);
// Reset depth clamping. // Reset depth clamping.
self.set_depth_clamp(false); Self::set_depth_clamp(&mut self.device, false);
} }
} }
@ -1322,7 +1391,7 @@ impl Renderer {
* map: &Texture<LodColorFmt>, * map: &Texture<LodColorFmt>,
* horizon: &Texture<LodTextureFmt>, */ * horizon: &Texture<LodTextureFmt>, */
) { ) {
if self.mode.shadow != ShadowMode::Map { if !self.mode.shadow.is_map() {
return; return;
} }
// NOTE: Don't render shadows if the shader is not supported. // NOTE: Don't render shadows if the shader is not supported.
@ -1377,7 +1446,7 @@ impl Renderer {
* map: &Texture<LodColorFmt>, * map: &Texture<LodColorFmt>,
* horizon: &Texture<LodTextureFmt>, */ * horizon: &Texture<LodTextureFmt>, */
) { ) {
if self.mode.shadow != ShadowMode::Map { if !self.mode.shadow.is_map() {
return; return;
} }
// NOTE: Don't render shadows if the shader is not supported. // NOTE: Don't render shadows if the shader is not supported.
@ -1433,7 +1502,7 @@ impl Renderer {
* map: &Texture<LodColorFmt>, * map: &Texture<LodColorFmt>,
* horizon: &Texture<LodTextureFmt>, */ * horizon: &Texture<LodTextureFmt>, */
) { ) {
if self.mode.shadow != ShadowMode::Map { if !self.mode.shadow.is_map() {
return; return;
} }
// NOTE: Don't render shadows if the shader is not supported. // NOTE: Don't render shadows if the shader is not supported.
@ -1783,14 +1852,14 @@ fn create_pipelines(
CloudMode::Regular => "CLOUD_MODE_REGULAR", CloudMode::Regular => "CLOUD_MODE_REGULAR",
}, },
match mode.lighting { match mode.lighting {
LightingMode::Ashikmin => "LIGHTING_ALGORITHM_ASHIKHMIN", LightingMode::Ashikhmin => "LIGHTING_ALGORITHM_ASHIKHMIN",
LightingMode::BlinnPhong => "LIGHTING_ALGORITHM_BLINN_PHONG", LightingMode::BlinnPhong => "LIGHTING_ALGORITHM_BLINN_PHONG",
LightingMode::Lambertian => "CLOUD_MODE_NONE", LightingMode::Lambertian => "CLOUD_MODE_NONE",
}, },
match mode.shadow { match mode.shadow {
ShadowMode::None => "SHADOW_MODE_NONE", ShadowMode::None => "SHADOW_MODE_NONE",
ShadowMode::Map if has_shadow_views => "SHADOW_MODE_MAP", ShadowMode::Map(_) if has_shadow_views => "SHADOW_MODE_MAP",
ShadowMode::Cheap | ShadowMode::Map => "SHADOW_MODE_CHEAP", ShadowMode::Cheap | ShadowMode::Map(_) => "SHADOW_MODE_CHEAP",
}, },
); );

View File

@ -98,7 +98,7 @@ where
) -> Result<Self, RenderError> { ) -> Result<Self, RenderError> {
let (tex, srv) = factory let (tex, srv) = factory
.create_texture_immutable::<F>(kind, mipmap, data) .create_texture_immutable::<F>(kind, mipmap, data)
.map_err(|err| RenderError::CombinedError(err))?; .map_err(RenderError::CombinedError)?;
Ok(Self { Ok(Self {
tex, tex,

View File

@ -8,8 +8,8 @@ use crate::{
ecs::comp::Interpolated, ecs::comp::Interpolated,
mesh::greedy::GreedyMesh, mesh::greedy::GreedyMesh,
render::{ render::{
self, BoneMeshes, ColLightFmt, Consts, FigureBoneData, FigureLocals, FigureModel, Globals, BoneMeshes, ColLightFmt, Consts, FigureBoneData, FigureLocals, FigureModel, Globals, Light,
Light, RenderError, Renderer, Shadow, ShadowLocals, ShadowPipeline, Texture, RenderError, Renderer, Shadow, ShadowLocals, ShadowPipeline, Texture,
}, },
scene::{ scene::{
camera::{Camera, CameraMode}, camera::{Camera, CameraMode},
@ -1805,7 +1805,7 @@ impl FigureMgr {
) { ) {
let ecs = state.ecs(); let ecs = state.ecs();
if is_daylight && renderer.render_mode().shadow == render::ShadowMode::Map { if is_daylight && renderer.render_mode().shadow.is_map() {
( (
&ecs.entities(), &ecs.entities(),
&ecs.read_storage::<Pos>(), &ecs.read_storage::<Pos>(),

View File

@ -1,10 +1,9 @@
use core::{iter, mem}; use core::{iter, mem};
use hashbrown::HashMap; use hashbrown::HashMap;
use num::traits::Float; use num::traits::Float;
/* pub use vek::{ pub use vek::{geom::repr_simd::*, mat::repr_simd::column_major::Mat4, ops::*, vec::repr_simd::*};
geom::repr_simd::*, mat::repr_simd::column_major::Mat4, ops::*, vec::repr_simd::*, // pub use vek::{geom::repr_c::*, mat::repr_c::column_major::Mat4, ops::*,
}; */ // vec::repr_c::*};
pub use vek::{geom::repr_c::*, mat::repr_c::column_major::Mat4, ops::*, vec::repr_c::*};
pub fn aabb_to_points<T: Float>(bounds: Aabb<T>) -> [Vec3<T>; 8] { pub fn aabb_to_points<T: Float>(bounds: Aabb<T>) -> [Vec3<T>; 8] {
[ [

View File

@ -14,7 +14,7 @@ use self::{
use crate::{ use crate::{
audio::{music::MusicMgr, sfx::SfxMgr, AudioFrontend}, audio::{music::MusicMgr, sfx::SfxMgr, AudioFrontend},
render::{ render::{
self, create_pp_mesh, create_skybox_mesh, Consts, Globals, Light, Model, PostProcessLocals, create_pp_mesh, create_skybox_mesh, Consts, Globals, Light, Model, PostProcessLocals,
PostProcessPipeline, Renderer, Shadow, ShadowLocals, SkyboxLocals, SkyboxPipeline, PostProcessPipeline, Renderer, Shadow, ShadowLocals, SkyboxLocals, SkyboxPipeline,
}, },
settings::Settings, settings::Settings,
@ -553,9 +553,7 @@ impl Scene {
let sun_dir = scene_data.get_sun_dir(); let sun_dir = scene_data.get_sun_dir();
let is_daylight = sun_dir.z < 0.0/*0.6*/; let is_daylight = sun_dir.z < 0.0/*0.6*/;
if renderer.render_mode().shadow == render::ShadowMode::Map if renderer.render_mode().shadow.is_map() && (is_daylight || !lights.is_empty()) {
&& (is_daylight || !lights.is_empty())
{
/* // We treat the actual scene bounds as being clipped by the horizontal terrain bounds, but /* // We treat the actual scene bounds as being clipped by the horizontal terrain bounds, but
// expanded to contain the z values of all NPCs. This is potentially important to make // expanded to contain the z values of all NPCs. This is potentially important to make
// sure we don't clip out figures in front of the camera. // sure we don't clip out figures in front of the camera.
@ -1270,9 +1268,7 @@ impl Scene {
let cam_pos = self.camera.dependents().cam_pos + focus_pos.map(|e| e.trunc()); let cam_pos = self.camera.dependents().cam_pos + focus_pos.map(|e| e.trunc());
// would instead have this as an extension. // would instead have this as an extension.
if renderer.render_mode().shadow == render::ShadowMode::Map if renderer.render_mode().shadow.is_map() && (is_daylight || self.light_data.len() > 0) {
&& (is_daylight || self.light_data.len() > 0)
{
// Set up shadow mapping. // Set up shadow mapping.
renderer.start_shadows(); renderer.start_shadows();

View File

@ -1,9 +1,9 @@
use crate::{ use crate::{
mesh::{greedy::GreedyMesh, Meshable}, mesh::{greedy::GreedyMesh, Meshable},
render::{ render::{
self, ColLightFmt, ColLightInfo, Consts, FluidPipeline, Globals, Instances, Light, Mesh, ColLightFmt, ColLightInfo, Consts, FluidPipeline, Globals, Instances, Light, Mesh, Model,
Model, RenderError, Renderer, Shadow, ShadowLocals, ShadowPipeline, SpriteInstance, RenderError, Renderer, Shadow, ShadowLocals, ShadowPipeline, SpriteInstance, SpriteLocals,
SpriteLocals, SpritePipeline, TerrainLocals, TerrainPipeline, Texture, SpritePipeline, TerrainLocals, TerrainPipeline, Texture,
}, },
}; };
@ -2855,7 +2855,7 @@ impl<V: RectRasterableVol> Terrain<V> {
let collides_with_aabr = |a: math::Aabr<f32>, b: math::Aabr<f32>| { let collides_with_aabr = |a: math::Aabr<f32>, b: math::Aabr<f32>| {
a.min.partial_cmple(&b.max).reduce_and() && a.max.partial_cmpge(&b.min).reduce_and() a.min.partial_cmple(&b.max).reduce_and() && a.max.partial_cmpge(&b.min).reduce_and()
}; };
if ray_direction.z < 0.0 && renderer.render_mode().shadow == render::ShadowMode::Map { if ray_direction.z < 0.0 && renderer.render_mode().shadow.is_map() {
let visible_bounding_box = Aabb { let visible_bounding_box = Aabb {
min: visible_bounding_box.min - focus_off, min: visible_bounding_box.min - focus_off,
max: visible_bounding_box.max - focus_off, max: visible_bounding_box.max - focus_off,
@ -2962,7 +2962,7 @@ impl<V: RectRasterableVol> Terrain<V> {
min: -0.5, min: -0.5,
max: 0.5, max: 0.5,
}; */ }; */
/* if ray_direction.z < 0.0 && renderer.render_mode().shadow == render::ShadowMode::Map { /* if ray_direction.z < 0.0 && renderer.render_mode().shadow.is_map() {
let ray = if ray_direction.x.abs() * scene_bounding_box.size().d > ray_direction.z.abs() * chunk_sz { let ray = if ray_direction.x.abs() * scene_bounding_box.size().d > ray_direction.z.abs() * chunk_sz {
-ray_direction / ray_direction.x * chunk_sz -ray_direction / ray_direction.x * chunk_sz
} else { } else {
@ -3029,7 +3029,7 @@ impl<V: RectRasterableVol> Terrain<V> {
is_daylight: bool, is_daylight: bool,
focus_pos: Vec3<f32>, focus_pos: Vec3<f32>,
) { ) {
if !(renderer.render_mode().shadow == render::ShadowMode::Map) { if !renderer.render_mode().shadow.is_map() {
return; return;
}; };

View File

@ -832,7 +832,6 @@ impl PlayState for SessionState {
}, },
HudEvent::ChangeRenderMode(new_render_mode) => { HudEvent::ChangeRenderMode(new_render_mode) => {
// Do this first so if it crashes the setting isn't saved :) // Do this first so if it crashes the setting isn't saved :)
println!("Changing render mode: {:?}", new_render_mode);
global_state global_state
.window .window
.renderer_mut() .renderer_mut()

View File

@ -317,7 +317,7 @@ impl<'a> MapConfig<'a> {
let water_color_factor = 2.0; let water_color_factor = 2.0;
let g_water = 32.0 * water_color_factor; let g_water = 32.0 * water_color_factor;
let b_water = 64.0 * water_color_factor; let b_water = 64.0 * water_color_factor;
let column_rgb = column_rgb.unwrap_or(Rgb::new( let default_rgb = Rgb::new(
if is_shaded || is_temperature { if is_shaded || is_temperature {
1.0 1.0
} else { } else {
@ -325,7 +325,8 @@ impl<'a> MapConfig<'a> {
}, },
if is_shaded { 1.0 } else { alt }, if is_shaded { 1.0 } else { alt },
if is_shaded || is_humidity { 1.0 } else { 0.0 }, if is_shaded || is_humidity { 1.0 } else { 0.0 },
)); );
let column_rgb = column_rgb.unwrap_or(default_rgb);
let mut connections = [None; 8]; let mut connections = [None; 8];
let mut has_connections = false; let mut has_connections = false;
// TODO: Support non-river connections. // TODO: Support non-river connections.

View File

@ -122,6 +122,11 @@ pub fn cdf_irwin_hall<const N: usize>(weights: &[f32; N], samples: [f32; N]) ->
/// NOTE: Length should always be WORLD_SIZE.x * WORLD_SIZE.y. /// NOTE: Length should always be WORLD_SIZE.x * WORLD_SIZE.y.
pub type InverseCdf<F = f32> = Box<[(f32, F)]>; pub type InverseCdf<F = f32> = Box<[(f32, F)]>;
/// NOTE: First component is estimated horizon angles at each chunk; second
/// component is estimated heights of maximal occluder at each chunk (used
/// for making shadows volumetric).
pub type HorizonMap<A, H> = (Vec<A>, Vec<H>);
/// Computes the position Vec2 of a SimChunk from an index, where the index was /// Computes the position Vec2 of a SimChunk from an index, where the index was
/// generated by uniform_noise. /// generated by uniform_noise.
pub fn uniform_idx_as_vec2(idx: usize) -> Vec2<i32> { pub fn uniform_idx_as_vec2(idx: usize) -> Vec2<i32> {
@ -395,7 +400,7 @@ pub fn get_horizon_map<F: Float + Sync, A: Send, H: Send>(
h: impl Fn(usize) -> F + Sync, h: impl Fn(usize) -> F + Sync,
to_angle: impl Fn(F) -> A + Sync, to_angle: impl Fn(F) -> A + Sync,
to_height: impl Fn(F) -> H + Sync, to_height: impl Fn(F) -> H + Sync,
) -> Result<[(Vec<A>, Vec<H>); 2], ()> { ) -> Result<[HorizonMap<A, H>; 2], ()> {
if maxh < minh { if maxh < minh {
// maxh must be greater than minh // maxh must be greater than minh
return Err(()); return Err(());