1pub(super) use super::optics::{DepthOfField, MotionBlur};
8use super::profile_numeric::{finite_clamp, lerp, sanitize_bands, unit};
9use super::{BackdropStyle, DepthCue, DisplayTransform, LightingEnvironment};
10use serde::{Deserialize, Serialize};
11
12#[path = "profile_resolve.rs"]
13mod resolve;
14
15#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize)]
18pub struct IllustrationStyle {
19 pub silhouette_strength: f32,
21 pub cavity_strength: f32,
23 pub depth_cue_strength: f32,
25 #[serde(default)]
27 pub posterize_levels: f32,
28 #[serde(default)]
31 pub motion_persistence: f32,
32 #[serde(default)]
34 pub outline_width: f32,
35}
36
37#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
39pub struct BloomStyle {
40 pub threshold: f32,
42 pub intensity: f32,
44 pub radius: f32,
46}
47
48impl BloomStyle {
49 #[must_use]
51 pub const fn cinematic() -> Self {
52 Self {
53 threshold: 1.7,
54 intensity: 0.26,
55 radius: 2.0,
56 }
57 }
58
59 fn sanitize(self) -> Self {
60 Self {
61 threshold: finite_clamp(self.threshold, 0.0, 64.0, 1.05),
62 intensity: unit(self.intensity),
63 radius: finite_clamp(self.radius, 0.0, 8.0, 2.0),
64 }
65 }
66
67 fn blend(self, other: Self, weight: f32) -> Self {
68 let weight = unit(weight);
69 Self {
70 threshold: lerp(self.threshold, other.threshold, weight),
71 intensity: lerp(self.intensity, other.intensity, weight),
72 radius: lerp(self.radius, other.radius, weight),
73 }
74 .sanitize()
75 }
76
77 pub(crate) fn packed(self) -> [f32; 4] {
78 let style = self.sanitize();
79 [style.threshold, style.intensity, style.radius, 0.0]
80 }
81}
82
83impl IllustrationStyle {
84 #[must_use]
86 pub const fn publication() -> Self {
87 Self {
88 silhouette_strength: 0.65,
89 cavity_strength: 0.35,
90 depth_cue_strength: 0.15,
91 posterize_levels: 0.0,
92 motion_persistence: 0.0,
93 outline_width: 0.0,
94 }
95 }
96
97 fn sanitize(self) -> Self {
98 Self {
99 silhouette_strength: unit(self.silhouette_strength),
100 cavity_strength: unit(self.cavity_strength),
101 depth_cue_strength: unit(self.depth_cue_strength),
102 posterize_levels: sanitize_bands(self.posterize_levels),
103 motion_persistence: unit(self.motion_persistence),
104 outline_width: finite_clamp(self.outline_width, 0.0, 8.0, 0.0),
105 }
106 }
107
108 fn blend(self, other: Self, weight: f32) -> Self {
109 let weight = unit(weight);
110 Self {
111 silhouette_strength: lerp(self.silhouette_strength, other.silhouette_strength, weight),
112 cavity_strength: lerp(self.cavity_strength, other.cavity_strength, weight),
113 depth_cue_strength: lerp(self.depth_cue_strength, other.depth_cue_strength, weight),
114 posterize_levels: lerp(self.posterize_levels, other.posterize_levels, weight),
115 motion_persistence: lerp(self.motion_persistence, other.motion_persistence, weight),
116 outline_width: lerp(self.outline_width, other.outline_width, weight),
117 }
118 }
119
120 pub(crate) fn packed(self, focus_distance: f32) -> [f32; 4] {
121 let style = self.sanitize();
122 [
123 style.silhouette_strength,
124 style.cavity_strength,
125 style.depth_cue_strength,
126 if focus_distance.is_finite() {
127 focus_distance.max(1.0e-3)
128 } else {
129 1.0
130 },
131 ]
132 }
133
134 pub(crate) fn npr_packed(self) -> [f32; 4] {
137 let s = self.sanitize();
138 [
139 s.posterize_levels,
140 s.motion_persistence,
141 s.outline_width,
142 0.0,
143 ]
144 }
145}
146
147#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize)]
155pub struct AntiAliasingStyle {
156 pub edge_smoothing: bool,
161}
162
163impl AntiAliasingStyle {
164 #[must_use]
167 pub const fn smoothed() -> Self {
168 Self {
169 edge_smoothing: true,
170 }
171 }
172
173 #[must_use]
176 pub const fn none() -> Self {
177 Self {
178 edge_smoothing: false,
179 }
180 }
181
182 fn sanitize(self) -> Self {
183 self
184 }
185}
186
187#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
192#[non_exhaustive]
193pub enum PresentationEffect {
194 Illustration(IllustrationStyle),
196 DepthCue(DepthCue),
198 DepthOfField(DepthOfField),
200 MotionBlur(MotionBlur),
202 Backdrop(BackdropStyle),
204 Lighting(LightingEnvironment),
206 Display(DisplayTransform),
208 Bloom(BloomStyle),
210 AntiAliasing(AntiAliasingStyle),
212}
213
214#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
216pub struct EffectLayer {
217 pub priority: i16,
220 pub weight: f32,
222 pub effect: PresentationEffect,
224}
225
226impl EffectLayer {
227 #[must_use]
229 pub const fn new(effect: PresentationEffect) -> Self {
230 Self {
231 priority: 0,
232 weight: 1.0,
233 effect,
234 }
235 }
236
237 #[must_use]
239 pub const fn with_weight(mut self, weight: f32) -> Self {
240 self.weight = weight;
241 self
242 }
243
244 #[must_use]
246 pub const fn with_priority(mut self, priority: i16) -> Self {
247 self.priority = priority;
248 self
249 }
250}
251
252#[derive(Clone, PartialEq, Debug, Default, Serialize, Deserialize)]
254pub struct RenderProfile {
255 layers: Vec<EffectLayer>,
256}
257
258impl RenderProfile {
259 #[must_use]
262 pub const fn inspection() -> Self {
263 Self { layers: Vec::new() }
264 }
265
266 #[must_use]
268 pub fn illustrative() -> Self {
269 Self::inspection().with_effect(PresentationEffect::Illustration(
270 IllustrationStyle::publication(),
271 ))
272 }
273
274 #[must_use]
278 pub fn cinematic() -> Self {
279 Self::inspection()
280 .with_effect(PresentationEffect::Illustration(IllustrationStyle {
281 silhouette_strength: 0.5,
282 cavity_strength: 0.4,
283 depth_cue_strength: 0.28,
284 posterize_levels: 0.0,
285 motion_persistence: 0.0,
286 outline_width: 0.0,
287 }))
288 .with_effect(PresentationEffect::Lighting(
289 LightingEnvironment::documentary(),
290 ))
291 .with_effect(PresentationEffect::Display(DisplayTransform::cinematic()))
292 .with_effect(PresentationEffect::Bloom(BloomStyle::cinematic()))
293 .with_effect(PresentationEffect::DepthOfField(DepthOfField::cinematic()))
294 .with_effect(PresentationEffect::MotionBlur(MotionBlur::cinematic()))
295 }
296
297 #[must_use]
299 pub fn with_effect(self, effect: PresentationEffect) -> Self {
300 self.with_layer(EffectLayer::new(effect))
301 }
302
303 #[must_use]
305 pub fn with_layer(mut self, layer: EffectLayer) -> Self {
306 self.layers.push(layer);
307 self
308 }
309
310 #[must_use]
312 pub fn layers(&self) -> &[EffectLayer] {
313 &self.layers
314 }
315}
316
317#[derive(Clone, Copy, PartialEq, Debug, Default, Serialize, Deserialize)]
319pub struct ResolvedRenderPlan {
320 illustration: IllustrationStyle,
321 depth_cue: DepthCue,
322 depth_of_field: Option<DepthOfField>,
323 motion_blur: Option<MotionBlur>,
324 bloom: Option<BloomStyle>,
325 backdrop: BackdropStyle,
326 lighting: LightingEnvironment,
327 display: DisplayTransform,
328 antialias: Option<AntiAliasingStyle>,
329}
330
331impl ResolvedRenderPlan {
332 #[must_use]
334 pub const fn illustration(&self) -> IllustrationStyle {
335 self.illustration
336 }
337 #[must_use]
339 pub const fn depth_cue(&self) -> DepthCue {
340 self.depth_cue
341 }
342 #[must_use]
344 pub const fn depth_of_field(&self) -> Option<DepthOfField> {
345 self.depth_of_field
346 }
347
348 #[must_use]
350 pub const fn motion_blur(&self) -> Option<MotionBlur> {
351 self.motion_blur
352 }
353
354 #[must_use]
356 pub const fn backdrop(&self) -> BackdropStyle {
357 self.backdrop
358 }
359
360 #[must_use]
362 pub const fn lighting(&self) -> LightingEnvironment {
363 self.lighting
364 }
365
366 #[must_use]
368 pub const fn display(&self) -> DisplayTransform {
369 self.display
370 }
371
372 #[must_use]
374 pub const fn bloom(&self) -> Option<BloomStyle> {
375 self.bloom
376 }
377
378 #[must_use]
381 pub const fn antialias(&self) -> Option<AntiAliasingStyle> {
382 self.antialias
383 }
384
385 pub(crate) fn packed_presentation(self, has_translucency: bool) -> [[f32; 4]; 6] {
386 super::backdrop::pack(
387 self.backdrop,
388 self.display,
389 has_translucency,
390 match self.bloom {
391 Some(style) => style.packed(),
392 None => [0.0; 4],
393 },
394 )
395 }
396
397 pub(crate) fn packed_lighting(self) -> [[f32; 4]; 8] {
398 self.lighting.packed()
399 }
400
401 pub(crate) fn packed_depth_cue(self) -> [f32; 4] {
402 self.depth_cue.packed()
403 }
404
405 pub(crate) fn optics(self, focus_distance: f32) -> [f32; 4] {
406 match self.depth_of_field {
407 Some(settings) => settings.packed(focus_distance),
408 None => [focus_distance.max(1.0e-3), 0.0, 0.0, 0.0],
409 }
410 }
411}
412#[cfg(test)]
413#[path = "profile_tests.rs"]
414mod tests;