Skip to main content

loam_render/raymarch/scene/
primitive.rs

1//! Boxes use chart coordinates; halfspaces require Space::is_chart_flat.
2//! Only geodesic spheres have the same distance contract across curved spaces.
3
4use glam::Vec3;
5use loam_math::{Space, WgslSpace};
6use loam_shape::Shape;
7
8use super::literal::wgsl_f32;
9use super::SENTINEL_DISTANCE;
10
11pub trait Primitive {
12    fn to_wgsl<S: WgslSpace>(&self, space: &S, name: &str) -> String;
13
14    fn eval<S: Space<Point = Vec3, Vector = Vec3>>(&self, space: &S, p: Vec3) -> f32;
15}
16
17impl Primitive for Shape {
18    fn to_wgsl<S: WgslSpace>(&self, space: &S, name: &str) -> String {
19        match self {
20            Shape::Sphere { center, radius } => format!(
21                "fn {name}(p: vec3<f32>) -> f32 {{\n\
22                 \treturn loam_distance(p, vec3<f32>({cx}, {cy}, {cz})) - ({r});\n\
23                 }}\n",
24                name = name,
25                cx = wgsl_f32(center.x),
26                cy = wgsl_f32(center.y),
27                cz = wgsl_f32(center.z),
28                r = wgsl_f32(*radius),
29            ),
30            Shape::Box3 { half_extents } => format!(
31                "fn {name}(p: vec3<f32>) -> f32 {{\n\
32                 \tlet b = vec3<f32>({hx}, {hy}, {hz});\n\
33                 \tlet q = abs(p) - b;\n\
34                 \treturn length(max(q, vec3<f32>(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0);\n\
35                 }}\n",
36                name = name,
37                hx = wgsl_f32(half_extents.x),
38                hy = wgsl_f32(half_extents.y),
39                hz = wgsl_f32(half_extents.z),
40            ),
41            Shape::HalfSpace { normal, offset } if space.is_chart_flat() => format!(
42                "fn {name}(p: vec3<f32>) -> f32 {{\n\
43                 \treturn dot(p, vec3<f32>({nx}, {ny}, {nz})) - ({d});\n\
44                 }}\n",
45                name = name,
46                nx = wgsl_f32(normal.x),
47                ny = wgsl_f32(normal.y),
48                nz = wgsl_f32(normal.z),
49                d = wgsl_f32(*offset),
50            ),
51            Shape::HalfSpace { .. }
52            | Shape::HalfSpace4D { .. }
53            | Shape::Polygon2D { .. }
54            | Shape::ConvexPolytope3D { .. }
55            | Shape::ConvexPolytope4D { .. }
56            | Shape::HyperSphere4D { .. } => {
57                format!("fn {name}(_p: vec3<f32>) -> f32 {{\n\treturn {SENTINEL_DISTANCE:e};\n}}\n",)
58            }
59        }
60    }
61
62    fn eval<S: Space<Point = Vec3, Vector = Vec3>>(&self, space: &S, p: Vec3) -> f32 {
63        match self {
64            Shape::Sphere { center, radius } => space.distance(p, *center) - *radius,
65
66            Shape::Box3 { half_extents } => {
67                let q = p.abs() - *half_extents;
68                q.max(Vec3::ZERO).length() + q.x.max(q.y.max(q.z)).min(0.0)
69            }
70
71            Shape::HalfSpace { normal, offset } if space.is_chart_flat() => {
72                p.dot(*normal) - *offset
73            }
74
75            Shape::HalfSpace { .. }
76            | Shape::HalfSpace4D { .. }
77            | Shape::Polygon2D { .. }
78            | Shape::ConvexPolytope3D { .. }
79            | Shape::ConvexPolytope4D { .. }
80            | Shape::HyperSphere4D { .. } => SENTINEL_DISTANCE,
81        }
82    }
83}