Make bilinear patch splitting a little smarter.

This way it doesn't just keep splitting on the same axis
repeatedly, creating narrow, long patches..
This commit is contained in:
Nathan Vegdahl 2019-08-01 13:49:44 +09:00
parent 95e7d6bdea
commit ecbdf5d609
2 changed files with 99 additions and 42 deletions

View File

@ -3,7 +3,7 @@
use math3d::{Matrix4x4, Normal, Point, Vector};
/// Trait for allowing a type to be linearly interpolated.
pub trait Lerp {
pub trait Lerp: Copy {
fn lerp(self, other: Self, alpha: f32) -> Self;
}
@ -17,7 +17,7 @@ pub fn lerp<T: Lerp>(a: T, b: T, alpha: f32) -> T {
/// Interpolates a slice of data as if each adjecent pair of elements
/// represent a linear segment.
pub fn lerp_slice<T: Lerp + Copy>(s: &[T], alpha: f32) -> T {
pub fn lerp_slice<T: Lerp>(s: &[T], alpha: f32) -> T {
debug_assert!(!s.is_empty());
debug_assert!(alpha >= 0.0);
debug_assert!(alpha <= 1.0);
@ -73,6 +73,33 @@ impl<T: Lerp> Lerp for (T, T) {
}
}
impl<T: Lerp> Lerp for [T; 2] {
fn lerp(self, other: Self, alpha: f32) -> Self {
[self[0].lerp(other[0], alpha), self[1].lerp(other[1], alpha)]
}
}
impl<T: Lerp> Lerp for [T; 3] {
fn lerp(self, other: Self, alpha: f32) -> Self {
[
self[0].lerp(other[0], alpha),
self[1].lerp(other[1], alpha),
self[2].lerp(other[2], alpha),
]
}
}
impl<T: Lerp> Lerp for [T; 4] {
fn lerp(self, other: Self, alpha: f32) -> Self {
[
self[0].lerp(other[0], alpha),
self[1].lerp(other[1], alpha),
self[2].lerp(other[2], alpha),
self[3].lerp(other[3], alpha),
]
}
}
impl Lerp for glam::Vec4 {
fn lerp(self, other: glam::Vec4, alpha: f32) -> glam::Vec4 {
(self * (1.0 - alpha)) + (other * alpha)

View File

@ -1,5 +1,8 @@
use super::{point_order, PointOrder, Splitable, MAX_EDGE_DICE};
use crate::{lerp::lerp, math::Point};
use crate::{
lerp::{lerp, lerp_slice},
math::Point,
};
#[derive(Debug, Copy, Clone)]
pub struct BilinearPatch<'a> {
@ -41,8 +44,8 @@ impl<'a> Splitable for BilinearSubPatch<'a> {
where
F: Fn(Point, Point) -> f32,
{
// Get the points of the sub-patch.
let patch = self.original.control_points[0];
// Get the points of the sub-patch at time 0.5.
let patch = lerp_slice(self.original.control_points, 0.5);
let points = [
bilerp_point(patch, self.clip[0]),
bilerp_point(patch, self.clip[1]),
@ -58,9 +61,38 @@ impl<'a> Splitable for BilinearSubPatch<'a> {
metric(points[3], points[0]),
];
// Do the split, if needed.
for i in 0..4 {
if self.must_split[i] || edge_metric[i] > MAX_EDGE_DICE as f32 {
// Find an edge to split, if any.
let split_edge_index = {
// Find the "longest" edge in terms of the metric.
let (edge_i, m) = edge_metric
.iter()
.enumerate()
.max_by(|a, b| {
if a.1 > b.1 {
std::cmp::Ordering::Greater
} else {
std::cmp::Ordering::Less
}
})
.unwrap();
// Return an edge to split, if a split is needed.
if *m > MAX_EDGE_DICE as f32 {
// Split needed because of over-long edge.
Some(edge_i)
} else {
// Return the the first edge with "must_split" set, if any.
// Otherwise returns `None`.
self.must_split
.iter()
.enumerate()
.find(|a| *a.1)
.map(|a| a.0)
}
};
// Do the split if needed
if let Some(i) = split_edge_index {
let edge_1 = (i, (i + 1) % 4);
let edge_2 = ((i + 2) % 4, (i + 3) % 4);
let new_must_split = {
@ -72,9 +104,8 @@ impl<'a> Splitable for BilinearSubPatch<'a> {
let midpoint_1 = lerp(self.clip[edge_1.0], self.clip[edge_1.1], 0.5);
let midpoint_2 = {
let alpha = if self.must_split[edge_2.0]
|| edge_metric[edge_2.0] > MAX_EDGE_DICE as f32
{
let alpha =
if self.must_split[edge_2.0] || edge_metric[edge_2.0] > MAX_EDGE_DICE as f32 {
0.5
} else {
let edge_2_dice_rate = edge_metric[edge_2.0].ceil();
@ -99,11 +130,10 @@ impl<'a> Splitable for BilinearSubPatch<'a> {
patch_2.clip[edge_1.0] = midpoint_1;
patch_2.clip[edge_2.1] = midpoint_2;
return Some((patch_1, patch_2));
}
}
// No splitting needed to be done.
Some((patch_1, patch_2))
} else {
// No split
None
}
}
}