1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
use cgmath::One;
use ordered_float::NotNan;
use crate::math::{FreeCoordinate, Rgb, Rgba};
/// Options for controlling rendering (not affecting gameplay except informationally).
///
/// Some options may be ignored by some renderers, such as when they request a particular
/// implementation approach or debug visualization. Renderers should make an effort to
/// report such failings via [`Flaws`](crate::camera::Flaws).
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[serde(default)]
#[non_exhaustive]
pub struct GraphicsOptions {
/// Whether and how to draw fog obscuring the view distance limit.
///
/// TODO: Implement fog in raytracer.
pub fog: FogOption,
/// Field of view, in degrees from top to bottom edge of the viewport.
pub fov_y: NotNan<FreeCoordinate>,
/// Method to use to remap colors to fit within the displayable range.
pub tone_mapping: ToneMappingOperator,
/// “Camera exposure” value: a scaling factor from scene luminance to displayed
/// luminance. Note that the exact interpretation of this depends on the chosen
/// [`tone_mapping`](ToneMappingOperator).
pub exposure: ExposureOption,
/// Distance, in unit cubes, from the camera to the farthest visible point.
///
/// TODO: Implement view distance limit (and fog) in raytracer.
pub view_distance: NotNan<FreeCoordinate>,
/// Style in which to draw the lighting of [`Space`](crate::space::Space)s.
/// This does not affect the *computation* of lighting.
pub lighting_display: LightingOption,
/// Method/fidelity to use for transparency.
pub transparency: TransparencyOption,
/// Whether to show the HUD or other UI elements.
///
/// This does not affect UI state or clickability; it purely controls display.
/// It is intended for the purpose of asking a [renderer] to produce an image
/// of the scene without any UI.
///
/// The cursor is not currently considered part of the UI. This may be revisited
/// later. The “info text” is controlled separately by
/// [`debug_info_text`](Self::debug_info_text).
///
/// [renderer]: crate::camera::HeadlessRenderer
pub show_ui: bool,
/// Whether to apply antialiasing techniques.
pub antialiasing: AntialiasingOption,
/// Whether to use frustum culling for drawing only in-view chunks and objects.
///
/// This option is for debugging and performance testing and should not have any
/// visible effects.
pub use_frustum_culling: bool,
/// Draw text overlay showing debug information.
pub debug_info_text: bool,
/// Draw boxes around [`Behavior`]s attached to parts of [`Space`]s.
/// This may also eventually include further in-world diagnostic information.
///
/// [`Behavior`]: crate::behavior::Behavior
/// [`Space`]: crate::space::Space
pub debug_behaviors: bool,
/// Draw boxes around chunk borders and some debug info.
pub debug_chunk_boxes: bool,
/// Draw collision boxes for some objects.
pub debug_collision_boxes: bool,
/// Draw the light rays that contribute to the selected block.
pub debug_light_rays_at_cursor: bool,
}
impl GraphicsOptions {
/// Constrain fields to valid/practical values.
#[must_use]
pub fn repair(mut self) -> Self {
self.fov_y = self.fov_y.clamp(NotNan::from(1), NotNan::from(189));
self.view_distance = self
.view_distance
.clamp(NotNan::from(1), NotNan::from(10000));
self
}
}
impl Default for GraphicsOptions {
fn default() -> Self {
Self {
fog: FogOption::Abrupt,
fov_y: NotNan::from(90),
// TODO: Change tone mapping default once we have a good implementation.
tone_mapping: ToneMappingOperator::Clamp,
exposure: ExposureOption::default(),
view_distance: NotNan::from(200),
lighting_display: LightingOption::Smooth,
transparency: TransparencyOption::Volumetric,
show_ui: true,
antialiasing: AntialiasingOption::default(),
use_frustum_culling: true,
debug_info_text: true,
debug_behaviors: false,
debug_chunk_boxes: false,
debug_collision_boxes: false,
debug_light_rays_at_cursor: false,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[non_exhaustive]
pub enum FogOption {
/// No fog: objects will maintain their color and disappear raggedly.
None,
/// Fog starts just before the view distance ends.
Abrupt,
/// Compromise between `Abrupt` and `Physical` options.
Compromise,
/// Almost physically realistic fog of constant density.
Physical,
}
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[non_exhaustive]
pub enum ToneMappingOperator {
/// Limit values above the maximum (or below zero) to lie within that range.
///
/// This is the trivial tone mapping operation, and most “correct” for
/// colors which do lie within the range, but will cause overly bright colors
/// to change hue (as the RGB components are clamped independently).
Clamp,
/// TODO: As currently implemented this is an inadequate placeholder which is
/// overly dark.
Reinhard,
}
impl ToneMappingOperator {
#[inline]
pub fn apply(&self, input: Rgb) -> Rgb {
match self {
ToneMappingOperator::Clamp => input.clamp(),
// From <https://64.github.io/tonemapping/>, this will cut brightness
// too much, but the better versions require a parameter of max scene brightness,
// or more likely for our use case, we'll hook this up to a model of eye
// adaptation to average brightness.
ToneMappingOperator::Reinhard => input * (1.0 + input.luminance()).recip(),
}
}
}
/// “Camera exposure” control: selection of algorithm to control the scaling factor from
/// scene luminance to displayed luminance.
///
/// Note that the exact interpretation of this value also depends on on the chosen
/// [`ToneMappingOperator`].
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[non_exhaustive]
pub enum ExposureOption {
Fixed(NotNan<f32>),
Automatic,
}
impl ExposureOption {
pub(crate) fn initial(&self) -> NotNan<f32> {
match *self {
ExposureOption::Fixed(value) => value,
ExposureOption::Automatic => NotNan::one(),
}
}
}
impl Default for ExposureOption {
fn default() -> Self {
ExposureOption::Fixed(NotNan::one())
}
}
/// How to display light in a [`Space`].
///
/// [`Space`]: crate::space::Space
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[non_exhaustive]
pub enum LightingOption {
/// No lighting: objects will be displayed with their original surface color.
None,
/// Light is taken from the volume immediately above a cube face.
/// Edges between cubes are visible.
Flat,
/// Light varies across surfaces.
Smooth,
}
/// How to render transparent objects; part of a [`GraphicsOptions`].
///
/// Note: There is not yet a consistent interpretation of alpha between the `Surface`
/// and `Volumetric` options; this will probably be changed in the future in favor
/// of the volumetric interpretation.
#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub enum TransparencyOption {
/// Conventional transparent surfaces.
Surface,
/// Accounts for the thickness of material passed through; colors' alpha values are
/// interpreted as the opacity of a unit thickness of the material.
Volumetric,
/// Alpha above or below the given threshold value will be rounded to fully opaque
/// or fully transparent, respectively.
Threshold(NotNan<f32>),
}
impl TransparencyOption {
/// Replace a color's alpha value according to the requested threshold,
/// if any.
#[inline]
pub(crate) fn limit_alpha(&self, color: Rgba) -> Rgba {
match *self {
Self::Threshold(t) => {
if color.alpha() > t {
color.to_rgb().with_alpha_one()
} else {
Rgba::TRANSPARENT
}
}
_ => color,
}
}
#[inline]
#[doc(hidden)] // TODO: make public/documented?
pub fn will_output_alpha(&self) -> bool {
!matches!(self, Self::Threshold(_))
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
#[non_exhaustive]
pub enum AntialiasingOption {
/// Do not apply antialiasing. Every pixel of the rendered image will be the exact
/// color of some part of the world, rather than a combination of adjacent parts.
#[default]
None,
/// If [multisample anti-aliasing](https://en.wikipedia.org/wiki/Multisample_anti-aliasing)
/// or similar functionality is available, allowing relatively cheap antialiasing,
/// then enable it.
IfCheap,
/// Always perform antialiasing, even if it is expensive.
Always,
}
impl AntialiasingOption {
/// True if GPU renderers should enable multisampling
#[doc(hidden)]
pub fn is_msaa(&self) -> bool {
match self {
Self::None => false,
Self::IfCheap => true,
Self::Always => true,
}
}
}