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 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
use core::{iter, mem};
use hashbrown::HashMap;
use num::traits::Float;
pub use vek::{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 fn aabb_to_points<T: Float>(bounds: Aabb<T>) -> [Vec3<T>; 8] {
[
Vec3::new(bounds.min.x, bounds.min.y, bounds.min.z),
Vec3::new(bounds.max.x, bounds.min.y, bounds.min.z),
Vec3::new(bounds.max.x, bounds.max.y, bounds.min.z),
Vec3::new(bounds.min.x, bounds.max.y, bounds.min.z),
Vec3::new(bounds.min.x, bounds.min.y, bounds.max.z),
Vec3::new(bounds.max.x, bounds.min.y, bounds.max.z),
Vec3::new(bounds.max.x, bounds.max.y, bounds.max.z),
Vec3::new(bounds.min.x, bounds.max.y, bounds.max.z),
]
}
/// Each Vec4 <a, b, c, -d> should be interpreted as reprenting plane
/// equation
///
/// a(x - x0) + b(y - y0) + c(z - z0) = 0, i.e.
/// ax + by + cz - (a * x0 + b * y0 + c * z0) = 0, i.e.
/// ax + by + cz = (a * x0 + b * y0 + c * z0), i.e.
/// (letting d = a * x0 + b * y0 + c * z0)
/// ax + by + cz = d
///
/// where d is the distance of the plane from the origin.
pub fn aabb_to_planes<T: Float>(bounds: Aabb<T>) -> [Vec4<T>; 6] {
let zero = T::zero();
let one = T::one();
let bounds = bounds.map(|e| e.abs());
[
// bottom
Vec4::new(zero, -one, zero, -bounds.min.y),
// top
Vec4::new(zero, one, zero, -bounds.max.y),
// left
Vec4::new(-one, zero, zero, -bounds.min.x),
// right
Vec4::new(one, zero, zero, -bounds.max.x),
// near
Vec4::new(zero, zero, -one, -bounds.min.z),
// far
Vec4::new(zero, zero, one, -bounds.max.z),
]
}
pub fn mat_mul_points<T: Float + MulAdd<T, T, Output = T>>(
mat: Mat4<T>,
pts: &mut [Vec3<T>],
mut do_p: impl FnMut(Vec4<T>) -> Vec3<T>,
) {
pts.iter_mut().for_each(|p| {
*p = do_p(mat * Vec4::from_point(*p));
});
}
/// NOTE: Expects points computed from aabb_to_points.
pub fn calc_view_frust_object<T: Float>(pts: &[Vec3<T>; 8]) -> Vec<Vec<Vec3<T>>> {
vec![
// near (CCW)
vec![pts[0], pts[1], pts[2], pts[3]],
// far (CCW)
vec![pts[7], pts[6], pts[5], pts[4]],
// left (CCW)
vec![pts[0], pts[3], pts[7], pts[4]],
// right (CCW)
vec![pts[1], pts[5], pts[6], pts[2]],
// bottom (CCW)
vec![pts[4], pts[5], pts[1], pts[0]],
// top (CCW)
vec![pts[6], pts[7], pts[3], pts[2]],
]
}
pub fn calc_view_frustum_world_coord<T: Float + MulAdd<T, T, Output = T>>(
inv_proj_view: Mat4<T>,
) -> [Vec3<T>; 8] {
let mut world_pts = aabb_to_points(Aabb {
min: Vec3::new(-T::one(), -T::one(), T::zero()),
max: Vec3::one(),
});
mat_mul_points(inv_proj_view, &mut world_pts, |p| Vec3::from(p) / p.w);
world_pts
}
pub fn point_plane_distance<T: Float>(point: Vec3<T>, norm_dist: Vec4<T>) -> T {
norm_dist.dot(Vec4::from_point(point))
}
pub fn point_before_plane<T: Float>(point: Vec3<T>, plane: Vec4<T>) -> bool {
point_plane_distance(point, plane) > T::zero()
}
/// Returns true if and only if the final point in the polygon (i.e. the
/// first point added to the new polygon) is outside the clipping plane
/// (this implies that the polygon must be non-degenerate).
pub fn clip_points_by_plane<T: Float + MulAdd<T, T, Output = T> + core::fmt::Debug>(
points: &mut Vec<Vec3<T>>,
plane: Vec4<T>,
intersection_points: &mut Vec<Vec3<T>>,
) -> bool {
if points.len() < 3 {
return false;
}
// NOTE: Guaranteed to succeed since points.len() > 3.
let mut current_point = points[points.len() - 1];
let intersect_plane_edge = |a, b| {
let diff: Vec3<_> = b - a;
let t = plane.dot(Vec4::from_direction(diff));
if t == T::zero() {
None
} else {
let t = -(plane.dot(Vec4::from_point(a)) / t);
if t < T::zero() || T::one() < t {
None
} else {
Some(diff * t + a)
}
}
};
let last_is_outside = point_before_plane(current_point, plane);
let mut is_outside = last_is_outside;
let mut old_points = Vec::with_capacity((3 * points.len()) / 2);
mem::swap(&mut old_points, points);
old_points.into_iter().for_each(|point| {
let prev_point = mem::replace(&mut current_point, point);
let before_plane = point_before_plane(current_point, plane);
let prev_is_outside = mem::replace(&mut is_outside, before_plane);
if !prev_is_outside {
// Push previous point.
points.push(prev_point);
}
if prev_is_outside != is_outside {
if let Some(intersection_point) = intersect_plane_edge(prev_point, current_point) {
// Push intersection point.
intersection_points.push(intersection_point);
points.push(intersection_point);
}
}
});
last_is_outside
}
fn append_intersection_points<T: Float + core::fmt::Debug>(
polys: &mut Vec<Vec<Vec3<T>>>,
intersection_points: Vec<Vec3<T>>,
tolerance: T,
) {
// NOTE: We use decoded versions of each polygon, with rounded entries.
//
// The line segments in intersection_points are consistently ordered as follows:
// each segment represents the intersection of the cutting plane with the
// polygon from which the segment came. The polygon can thus be split into
// two parts: the part "inside" the new surface (below the plane), and the
// part "outside" it (above the plane). Thus, when oriented
// with the polygon normal pointing into the camera, and the cutting plane as
// the x axis, with the "outside" part on top and the "inside" part on the
// bottom, there is a leftmost point (the point going from outside to
// inside, counterclockwise) and a rightmost point (the point going from
// inside to outside, counterclockwise). Our consistent ordering guarantees
// that the leftmost point comes before the rightmost point in each line
// segment.
//
// Why does this help us? To see that, consider the polygon adjacent to the
// considered polygon which also has the same right intersection point (we
// know there will be exactly one of these, because we have a solid
// structure and are only considering polygons that intersect the plane
// exactly two times; this means that we are ignoring polygons that intersect
// the plane at just one point, which means the two polygons must share a
// point, not be coplanar, and both intersect the plane; no theorem here,
// but I believe there can provably be at most one such instance given that
// we have at least three polygons with such a line segment).
//
// Now, for the adjacent polygon, repeat the above process. If the intersection
// point shared by the polygons is on the right in both cases, then we can
// see that the polygon's normal must be facing in the opposite direction of
// the original polygon despite being adjacent. But this
// should be impossible for a closed object! The same applies to the leftmost
// point.
//
// What is the practical upshot of all this? It means that we can consistently
// hash each line segment by its first point, which we can look up using the
// second point of a previous line segment. This will produce a chain of
// entries terminating in the original segment we looked up. As an added
// bonus, by going from leftmost point to leftmost point, we also ensure that
// we produce a polygon whose face is oriented counterclockwise around its
// normal; this can be seen by following the right-hand rule (TODO: provide
// more rigorous proof).
let tol = tolerance.recip();
let make_key = move |point: Vec3<T>| {
// We use floating points rounded to tolerance in order to make our HashMap
// lookups work. Otherwise we'd have to use a sorted structure, like a
// btree, which wouldn't be the end of the world but would have
// theoretically worse complexity.
//
// NOTE: Definitely non-ideal that we panic if the rounded value can't fit in an
// i64...
//
// TODO: If necessary, let the caller specify how to hash these keys, since in
// cases where we know the kind of floating point we're using we can
// just cast to bits or something.
point.map(|e| {
(e * tol)
.round()
.to_i64()
.expect("We don't currently try to handle floats that won't fit in an i64.")
})
};
let mut lines_iter = intersection_points.chunks_exact(2).filter_map(|uv| {
let u_key = make_key(uv[0]);
let v = uv[1];
// NOTE: The reason we need to make sure this doesn't happen is that it's
// otherwise possible for two points to hash to the same value due to
// epsilon being too low. Because of the ordering mentioned previously,
// we know we should *eventually* find a pair of points starting with
// make_key(u) and ending with a different make_key(v) in such cases, so
// we just discard all the other pairs (treating them as points rather
// than lines).
(u_key != make_key(v)).then_some((u_key, v))
});
if let Some((last_key, first)) = lines_iter.next() {
let lines = lines_iter.collect::<HashMap<_, _>>();
if lines.len() < 2 {
// You need at least 3 sides for a polygon
return;
}
// NOTE: Guaranteed to terminate, provided we have no cycles besides the one
// that touches every point (which should be the case given how these
// points were generated).
let poly_iter = iter::successors(Some(first), |&cur| lines.get(&make_key(cur)).copied());
let poly: Vec<_> = poly_iter.collect();
// We have to check to make sure we really went through the whole cycle.
// TODO: Consider adaptively decreasing precision until we can make the cycle
// happen.
if poly.last().copied().map(make_key) == Some(last_key) {
// Push the new polygon onto the object.
polys.push(poly);
}
}
}
pub fn clip_object_by_plane<T: Float + MulAdd<T, T, Output = T> + core::fmt::Debug>(
polys: &mut Vec<Vec<Vec3<T>>>,
plane: Vec4<T>,
tolerance: T,
) {
let mut intersection_points = Vec::new();
polys.retain_mut(|points| {
let len = intersection_points.len();
let outside_first = clip_points_by_plane(points, plane, &mut intersection_points);
// Only remember intersections that are not coplanar with this side; i.e. those
// that have segment length 2.
if len + 2 != intersection_points.len() {
intersection_points.truncate(len);
} else if !outside_first {
// Order the two intersection points consistently, so that, when considered
// counterclockwise:
// - the first point goes from the exterior of the polygon (above the cutting
// plane) to its interior.
// - the second point goes from the interior of the polygon (below the cutting
// plane) to its exterior.
// the second is always going
//
// This allows us to uniquely map each line segment to an "owning" point (the
// one going from outside to inside), which happens to also point
// the segment in a counterclockwise direction around the new
// polygon normal composed of all the lines we clipped.
intersection_points.swap(len, len + 1);
}
// Remove polygon if it was clipped away
!points.is_empty()
});
// Add a polygon of all intersection points with the plane to close out the
// object.
append_intersection_points(polys, intersection_points, tolerance);
}
pub fn clip_object_by_aabb<T: Float + MulAdd<T, T, Output = T> + core::fmt::Debug>(
polys: &mut Vec<Vec<Vec3<T>>>,
bounds: Aabb<T>,
tolerance: T,
) {
let planes = aabb_to_planes(bounds);
planes.iter().for_each(|&plane| {
clip_object_by_plane(polys, plane, tolerance);
});
}
/// Return value is 'Some(segment)' if line segment intersects the current
/// test plane. Otherwise 'None' is returned in which case the line
/// segment is entirely clipped.
pub fn clip_test<T: Float + core::fmt::Debug>(p: T, q: T, (u1, u2): (T, T)) -> Option<(T, T)> {
if p == T::zero() {
if q >= T::zero() { Some((u1, u2)) } else { None }
} else {
let r = q / p;
if p < T::zero() {
if r > u2 {
None
} else {
Some((if r > u1 { r } else { u1 }, u2))
}
} else if r < u1 {
None
} else {
Some((u1, if r < u2 { r } else { u2 }))
}
}
}
pub fn intersection_line_aabb<T: Float + MulAdd<T, T, Output = T> + core::fmt::Debug>(
p: Vec3<T>,
dir: Vec3<T>,
bounds: Aabb<T>,
) -> Option<Vec3<T>> {
clip_test(-dir.z, p.z - bounds.min.z, (T::zero(), T::infinity()))
.and_then(|t| clip_test(dir.z, bounds.max.z - p.z, t))
.and_then(|t| clip_test(-dir.y, p.y - bounds.min.y, t))
.and_then(|t| clip_test(dir.y, bounds.max.y - p.y, t))
.and_then(|t| clip_test(-dir.x, p.x - bounds.min.x, t))
.and_then(|t| clip_test(dir.x, bounds.max.x - p.x, t))
.and_then(|(t1, t2)| {
if T::zero() <= t2 {
Some(dir.mul_add(Vec3::broadcast(t2), p))
} else if T::zero() <= t1 {
Some(dir.mul_add(Vec3::broadcast(t1), p))
} else {
None
}
})
}
pub fn include_object_light_volume<
T: Float + MulAdd<T, T, Output = T> + core::fmt::Debug,
I: Iterator<Item = Vec3<T>>,
>(
obj: I,
light_dir: Vec3<T>,
bounds: Aabb<T>,
) -> impl Iterator<Item = Vec3<T>> {
obj.flat_map(move |pt| iter::once(pt).chain(intersection_line_aabb(pt, -light_dir, bounds)))
}
// NOTE: Currently specialized to skip extending to the end of the light ray,
// since our light ray is already infinite. Correct code is commented out
// below.
pub fn calc_focused_light_volume_points<T: Float + MulAdd<T, T, Output = T> + core::fmt::Debug>(
inv_proj_view: Mat4<T>,
_light_dir: Vec3<T>,
scene_bounding_box: Aabb<T>,
tolerance: T,
) -> impl Iterator<Item = Vec3<T>> {
let world_pts = calc_view_frustum_world_coord(inv_proj_view);
let mut world_frust_object = calc_view_frust_object(&world_pts);
clip_object_by_aabb(&mut world_frust_object, scene_bounding_box, tolerance);
world_frust_object.into_iter().flat_map(|e| e.into_iter())
/* include_object_light_volume(
world_frust_object.into_iter().flat_map(|e| e.into_iter()),
light_dir,
scene_bounding_box,
) */
}
/// Computes an axis aligned bounding box that contains the provided points when
/// transformed into the coordinate space specificed by `mat`.
///
/// "psr" stands for "Potential shadow receivers" since this function is used to
/// get an Aabb containing the potential points (which represent the extents of
/// the volumes of potential things that will be shadowed) that will be
/// shadowed.
///
/// NOTE: Will not yield useful results if pts is empty!
pub fn fit_psr<
T: Float + MulAdd<T, T, Output = T>,
I: Iterator<Item = Vec3<T>>,
F: FnMut(Vec4<T>) -> Vec4<T>,
>(
mat: Mat4<T>,
pts: I,
mut do_p: F,
) -> Aabb<T> {
let mut min = Vec4::broadcast(T::infinity());
let mut max = Vec4::broadcast(T::neg_infinity());
pts.map(|p| do_p(mat * Vec4::<T>::from_point(p)))
.for_each(|p| {
min = Vec4::partial_min(min, p);
max = Vec4::partial_max(max, p);
});
Aabb {
min: min.xyz(),
max: max.xyz(),
}
}