veloren_voxygen/scene/
math.rs

1use core::{iter, mem};
2use hashbrown::HashMap;
3use num::traits::Float;
4/*
5pub use vek::{geom::repr_simd::*, mat::repr_simd::column_major::Mat4, ops::*, vec::repr_simd::*};
6*/
7pub use vek::{geom::repr_c::*, mat::repr_c::column_major::Mat4, ops::*, vec::repr_c::*};
8
9pub fn aabb_to_points<T: Float>(bounds: Aabb<T>) -> [Vec3<T>; 8] {
10    [
11        Vec3::new(bounds.min.x, bounds.min.y, bounds.min.z),
12        Vec3::new(bounds.max.x, bounds.min.y, bounds.min.z),
13        Vec3::new(bounds.max.x, bounds.max.y, bounds.min.z),
14        Vec3::new(bounds.min.x, bounds.max.y, bounds.min.z),
15        Vec3::new(bounds.min.x, bounds.min.y, bounds.max.z),
16        Vec3::new(bounds.max.x, bounds.min.y, bounds.max.z),
17        Vec3::new(bounds.max.x, bounds.max.y, bounds.max.z),
18        Vec3::new(bounds.min.x, bounds.max.y, bounds.max.z),
19    ]
20}
21
22/// Each Vec4 <a, b, c, -d> should be interpreted as reprenting plane
23/// equation
24///
25/// a(x - x0) + b(y - y0) + c(z - z0) = 0, i.e.
26/// ax + by + cz - (a * x0 + b * y0 + c * z0) = 0, i.e.
27/// ax + by + cz = (a * x0 + b * y0 + c * z0), i.e.
28/// (letting d = a * x0 + b * y0 + c * z0)
29/// ax + by + cz = d
30///
31/// where d is the distance of the plane from the origin.
32pub fn aabb_to_planes<T: Float>(bounds: Aabb<T>) -> [Vec4<T>; 6] {
33    let zero = T::zero();
34    let one = T::one();
35    let bounds = bounds.map(|e| e.abs());
36    [
37        // bottom
38        Vec4::new(zero, -one, zero, -bounds.min.y),
39        // top
40        Vec4::new(zero, one, zero, -bounds.max.y),
41        // left
42        Vec4::new(-one, zero, zero, -bounds.min.x),
43        // right
44        Vec4::new(one, zero, zero, -bounds.max.x),
45        // near
46        Vec4::new(zero, zero, -one, -bounds.min.z),
47        // far
48        Vec4::new(zero, zero, one, -bounds.max.z),
49    ]
50}
51
52pub fn mat_mul_points<T: Float + MulAdd<T, T, Output = T>>(
53    mat: Mat4<T>,
54    pts: &mut [Vec3<T>],
55    mut do_p: impl FnMut(Vec4<T>) -> Vec3<T>,
56) {
57    pts.iter_mut().for_each(|p| {
58        *p = do_p(mat * Vec4::from_point(*p));
59    });
60}
61
62/// NOTE: Expects points computed from aabb_to_points.
63pub fn calc_view_frust_object<T: Float>(pts: &[Vec3<T>; 8]) -> Vec<Vec<Vec3<T>>> {
64    vec![
65        // near (CCW)
66        vec![pts[0], pts[1], pts[2], pts[3]],
67        // far (CCW)
68        vec![pts[7], pts[6], pts[5], pts[4]],
69        // left (CCW)
70        vec![pts[0], pts[3], pts[7], pts[4]],
71        // right (CCW)
72        vec![pts[1], pts[5], pts[6], pts[2]],
73        // bottom (CCW)
74        vec![pts[4], pts[5], pts[1], pts[0]],
75        // top (CCW)
76        vec![pts[6], pts[7], pts[3], pts[2]],
77    ]
78}
79
80pub fn calc_view_frustum_world_coord<T: Float + MulAdd<T, T, Output = T>>(
81    inv_proj_view: Mat4<T>,
82) -> [Vec3<T>; 8] {
83    let mut world_pts = aabb_to_points(Aabb {
84        min: Vec3::new(-T::one(), -T::one(), T::zero()),
85        max: Vec3::one(),
86    });
87    mat_mul_points(inv_proj_view, &mut world_pts, |p| Vec3::from(p) / p.w);
88    world_pts
89}
90
91pub fn point_plane_distance<T: Float>(point: Vec3<T>, norm_dist: Vec4<T>) -> T {
92    norm_dist.dot(Vec4::from_point(point))
93}
94
95pub fn point_before_plane<T: Float>(point: Vec3<T>, plane: Vec4<T>) -> bool {
96    point_plane_distance(point, plane) > T::zero()
97}
98
99/// Returns true if and only if the final point in the polygon (i.e. the
100/// first point added to the new polygon) is outside the clipping plane
101/// (this implies that the polygon must be non-degenerate).
102pub fn clip_points_by_plane<T: Float + MulAdd<T, T, Output = T> + core::fmt::Debug>(
103    points: &mut Vec<Vec3<T>>,
104    plane: Vec4<T>,
105    intersection_points: &mut Vec<Vec3<T>>,
106) -> bool {
107    if points.len() < 3 {
108        return false;
109    }
110    // NOTE: Guaranteed to succeed since points.len() > 3.
111    let mut current_point = points[points.len() - 1];
112    let intersect_plane_edge = |a, b| {
113        let diff: Vec3<_> = b - a;
114        let t = plane.dot(Vec4::from_direction(diff));
115        if t == T::zero() {
116            None
117        } else {
118            let t = -(plane.dot(Vec4::from_point(a)) / t);
119            if t < T::zero() || T::one() < t {
120                None
121            } else {
122                Some(diff * t + a)
123            }
124        }
125    };
126    let last_is_outside = point_before_plane(current_point, plane);
127    let mut is_outside = last_is_outside;
128    let mut old_points = Vec::with_capacity((3 * points.len()) / 2);
129    mem::swap(&mut old_points, points);
130    old_points.into_iter().for_each(|point| {
131        let prev_point = mem::replace(&mut current_point, point);
132        let before_plane = point_before_plane(current_point, plane);
133        let prev_is_outside = mem::replace(&mut is_outside, before_plane);
134        if !prev_is_outside {
135            // Push previous point.
136            points.push(prev_point);
137        }
138        if prev_is_outside != is_outside {
139            if let Some(intersection_point) = intersect_plane_edge(prev_point, current_point) {
140                // Push intersection point.
141                intersection_points.push(intersection_point);
142                points.push(intersection_point);
143            }
144        }
145    });
146    last_is_outside
147}
148
149fn append_intersection_points<T: Float + core::fmt::Debug>(
150    polys: &mut Vec<Vec<Vec3<T>>>,
151    intersection_points: Vec<Vec3<T>>,
152    tolerance: T,
153) {
154    // NOTE: We use decoded versions of each polygon, with rounded entries.
155    //
156    // The line segments in intersection_points are consistently ordered as follows:
157    // each segment represents the intersection of the cutting plane with the
158    // polygon from which the segment came.  The polygon can thus be split into
159    // two parts: the part "inside" the new surface (below the plane), and the
160    // part "outside" it (above the plane).  Thus, when oriented
161    // with the polygon normal pointing into the camera, and the cutting plane as
162    // the x axis, with the "outside" part on top and the "inside" part on the
163    // bottom, there is a leftmost point (the point going from outside to
164    // inside, counterclockwise) and a rightmost point (the point going from
165    // inside to outside, counterclockwise).  Our consistent ordering guarantees
166    // that the leftmost point comes before the rightmost point in each line
167    // segment.
168    //
169    // Why does this help us?  To see that, consider the polygon adjacent to the
170    // considered polygon which also has the same right intersection point (we
171    // know there will be exactly one of these, because we have a solid
172    // structure and are only considering polygons that intersect the plane
173    // exactly two times; this means that we are ignoring polygons that intersect
174    // the plane at just one point, which means the two polygons must share a
175    // point, not be coplanar, and both intersect the plane; no theorem here,
176    // but I believe there can provably be at most one such instance given that
177    // we have at least three polygons with such a line segment).
178    //
179    // Now, for the adjacent polygon, repeat the above process.  If the intersection
180    // point shared by the polygons is on the right in both cases, then we can
181    // see that the polygon's normal must be facing in the opposite direction of
182    // the original polygon despite being adjacent.  But this
183    // should be impossible for a closed object!  The same applies to the leftmost
184    // point.
185    //
186    // What is the practical upshot of all this?  It means that we can consistently
187    // hash each line segment by its first point, which we can look up using the
188    // second point of a previous line segment.  This will produce a chain of
189    // entries terminating in the original segment we looked up.  As an added
190    // bonus, by going from leftmost point to leftmost point, we also ensure that
191    // we produce a polygon whose face is oriented counterclockwise around its
192    // normal; this can be seen by following the right-hand rule (TODO: provide
193    // more rigorous proof).
194    let tol = tolerance.recip();
195    let make_key = move |point: Vec3<T>| {
196        // We use floating points rounded to tolerance in order to make our HashMap
197        // lookups work. Otherwise we'd have to use a sorted structure, like a
198        // btree, which wouldn't be the end of the world but would have
199        // theoretically worse complexity.
200        //
201        // NOTE: Definitely non-ideal that we panic if the rounded value can't fit in an
202        // i64...
203        //
204        // TODO: If necessary, let the caller specify how to hash these keys, since in
205        // cases where we know the kind of floating point we're using we can
206        // just cast to bits or something.
207        point.map(|e| {
208            (e * tol)
209                .round()
210                .to_i64()
211                .expect("We don't currently try to handle floats that won't fit in an i64.")
212        })
213    };
214    let mut lines_iter = intersection_points.chunks_exact(2).filter_map(|uv| {
215        let u_key = make_key(uv[0]);
216        let v = uv[1];
217        // NOTE: The reason we need to make sure this doesn't happen is that it's
218        // otherwise possible for two points to hash to the same value due to
219        // epsilon being too low. Because of the ordering mentioned previously,
220        // we know we should *eventually* find a pair of points starting with
221        // make_key(u) and ending with a different make_key(v) in such cases, so
222        // we just discard all the other pairs (treating them as points rather
223        // than lines).
224        (u_key != make_key(v)).then_some((u_key, v))
225    });
226
227    if let Some((last_key, first)) = lines_iter.next() {
228        let lines = lines_iter.collect::<HashMap<_, _>>();
229        if lines.len() < 2 {
230            // You need at least 3 sides for a polygon
231            return;
232        }
233        // NOTE: Guaranteed to terminate, provided we have no cycles besides the one
234        // that touches every point (which should be the case given how these
235        // points were generated).
236        let poly_iter = iter::successors(Some(first), |&cur| lines.get(&make_key(cur)).copied());
237        let poly: Vec<_> = poly_iter.collect();
238        // We have to check to make sure we really went through the whole cycle.
239        // TODO: Consider adaptively decreasing precision until we can make the cycle
240        // happen.
241        if poly.last().copied().map(make_key) == Some(last_key) {
242            // Push the new polygon onto the object.
243            polys.push(poly);
244        }
245    }
246}
247
248pub fn clip_object_by_plane<T: Float + MulAdd<T, T, Output = T> + core::fmt::Debug>(
249    polys: &mut Vec<Vec<Vec3<T>>>,
250    plane: Vec4<T>,
251    tolerance: T,
252) {
253    let mut intersection_points = Vec::new();
254    polys.retain_mut(|points| {
255        let len = intersection_points.len();
256        let outside_first = clip_points_by_plane(points, plane, &mut intersection_points);
257        // Only remember intersections that are not coplanar with this side; i.e. those
258        // that have segment length 2.
259        if len + 2 != intersection_points.len() {
260            intersection_points.truncate(len);
261        } else if !outside_first {
262            // Order the two intersection points consistently, so that, when considered
263            // counterclockwise:
264            // - the first point goes from the exterior of the polygon (above the cutting
265            //   plane) to its interior.
266            // - the second point goes from the interior of the polygon (below the cutting
267            //   plane) to its exterior.
268            // the second is always going
269            //
270            // This allows us to uniquely map each line segment to an "owning" point (the
271            // one going from outside to inside), which happens to also point
272            // the segment in a counterclockwise direction around the new
273            // polygon normal composed of all the lines we clipped.
274            intersection_points.swap(len, len + 1);
275        }
276        // Remove polygon if it was clipped away
277        !points.is_empty()
278    });
279    // Add a polygon of all intersection points with the plane to close out the
280    // object.
281    append_intersection_points(polys, intersection_points, tolerance);
282}
283
284pub fn clip_object_by_aabb<T: Float + MulAdd<T, T, Output = T> + core::fmt::Debug>(
285    polys: &mut Vec<Vec<Vec3<T>>>,
286    bounds: Aabb<T>,
287    tolerance: T,
288) {
289    let planes = aabb_to_planes(bounds);
290    planes.iter().for_each(|&plane| {
291        clip_object_by_plane(polys, plane, tolerance);
292    });
293}
294
295/// Return value is 'Some(segment)' if line segment intersects the current
296/// test plane.  Otherwise 'None' is returned in which case the line
297/// segment is entirely clipped.
298pub fn clip_test<T: Float + core::fmt::Debug>(p: T, q: T, (u1, u2): (T, T)) -> Option<(T, T)> {
299    if p == T::zero() {
300        if q >= T::zero() { Some((u1, u2)) } else { None }
301    } else {
302        let r = q / p;
303        if p < T::zero() {
304            if r > u2 {
305                None
306            } else {
307                Some((if r > u1 { r } else { u1 }, u2))
308            }
309        } else if r < u1 {
310            None
311        } else {
312            Some((u1, if r < u2 { r } else { u2 }))
313        }
314    }
315}
316
317pub fn intersection_line_aabb<T: Float + MulAdd<T, T, Output = T> + core::fmt::Debug>(
318    p: Vec3<T>,
319    dir: Vec3<T>,
320    bounds: Aabb<T>,
321) -> Option<Vec3<T>> {
322    clip_test(-dir.z, p.z - bounds.min.z, (T::zero(), T::infinity()))
323        .and_then(|t| clip_test(dir.z, bounds.max.z - p.z, t))
324        .and_then(|t| clip_test(-dir.y, p.y - bounds.min.y, t))
325        .and_then(|t| clip_test(dir.y, bounds.max.y - p.y, t))
326        .and_then(|t| clip_test(-dir.x, p.x - bounds.min.x, t))
327        .and_then(|t| clip_test(dir.x, bounds.max.x - p.x, t))
328        .and_then(|(t1, t2)| {
329            if T::zero() <= t2 {
330                Some(dir.mul_add(Vec3::broadcast(t2), p))
331            } else if T::zero() <= t1 {
332                Some(dir.mul_add(Vec3::broadcast(t1), p))
333            } else {
334                None
335            }
336        })
337}
338
339pub fn include_object_light_volume<
340    T: Float + MulAdd<T, T, Output = T> + core::fmt::Debug,
341    I: Iterator<Item = Vec3<T>>,
342>(
343    obj: I,
344    light_dir: Vec3<T>,
345    bounds: Aabb<T>,
346) -> impl Iterator<Item = Vec3<T>> {
347    obj.flat_map(move |pt| iter::once(pt).chain(intersection_line_aabb(pt, -light_dir, bounds)))
348}
349
350// NOTE: Currently specialized to skip extending to the end of the light ray,
351// since our light ray is already infinite.  Correct code is commented out
352// below.
353pub fn calc_focused_light_volume_points<T: Float + MulAdd<T, T, Output = T> + core::fmt::Debug>(
354    inv_proj_view: Mat4<T>,
355    _light_dir: Vec3<T>,
356    scene_bounding_box: Aabb<T>,
357    tolerance: T,
358) -> impl Iterator<Item = Vec3<T>> {
359    let world_pts = calc_view_frustum_world_coord(inv_proj_view);
360    let mut world_frust_object = calc_view_frust_object(&world_pts);
361    clip_object_by_aabb(&mut world_frust_object, scene_bounding_box, tolerance);
362    world_frust_object.into_iter().flat_map(|e| e.into_iter())
363    /* include_object_light_volume(
364        world_frust_object.into_iter().flat_map(|e| e.into_iter()),
365        light_dir,
366        scene_bounding_box,
367    ) */
368}
369
370/// Computes an axis aligned bounding box that contains the provided points when
371/// transformed into the coordinate space specificed by `mat`.
372///
373/// "psr" stands for "Potential shadow receivers" since this function is used to
374/// get an Aabb containing the potential points (which represent the extents of
375/// the volumes of potential things that will be shadowed) that will be
376/// shadowed.
377///
378/// NOTE: Will not yield useful results if pts is empty!
379pub fn fit_psr<
380    T: Float + MulAdd<T, T, Output = T>,
381    I: Iterator<Item = Vec3<T>>,
382    F: FnMut(Vec4<T>) -> Vec4<T>,
383>(
384    mat: Mat4<T>,
385    pts: I,
386    mut do_p: F,
387) -> Aabb<T> {
388    let mut min = Vec4::broadcast(T::infinity());
389    let mut max = Vec4::broadcast(T::neg_infinity());
390    pts.map(|p| do_p(mat * Vec4::<T>::from_point(p)))
391        .for_each(|p| {
392            min = Vec4::partial_min(min, p);
393            max = Vec4::partial_max(max, p);
394        });
395    Aabb {
396        min: min.xyz(),
397        max: max.xyz(),
398    }
399}