Iterate over partially-disjoint unions of primitive bounds, improving CSG rasterization performance in the presence of sparse/disjoint unions.

This commit is contained in:
Avi Weinstock 2022-02-13 23:45:58 -05:00
parent 3373985a5f
commit aab552afc5
2 changed files with 191 additions and 77 deletions

View File

@ -82,6 +82,69 @@ pub enum Primitive {
Repeat(Id<Primitive>, Vec3<i32>, i32), Repeat(Id<Primitive>, Vec3<i32>, i32),
} }
impl std::fmt::Debug for Primitive {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Primitive::Empty => f.debug_tuple("Empty").finish(),
Primitive::Aabb(aabb) => f.debug_tuple("Aabb").field(&aabb).finish(),
Primitive::Pyramid { aabb, inset } => {
f.debug_tuple("Pyramid").field(&aabb).field(&inset).finish()
},
Primitive::Ramp { aabb, inset, dir } => {
f.debug_tuple("Ramp").field(&aabb).field(&inset).field(&dir).finish()
},
Primitive::Gable { aabb, inset, dir } => {
f.debug_tuple("Gable").field(&aabb).field(&inset).field(&dir).finish()
},
Primitive::Cylinder(aabb) => f.debug_tuple("Cylinder").field(&aabb).finish(),
Primitive::Cone(aabb) => f.debug_tuple("Cone").field(&aabb).finish(),
Primitive::Sphere(aabb) => f.debug_tuple("Sphere").field(&aabb).finish(),
Primitive::Superquadric { aabb, degree } => f
.debug_tuple("Superquadric")
.field(&aabb)
.field(&degree)
.finish(),
Primitive::Plane(aabr, origin, gradient) => f
.debug_tuple("Plane")
.field(&aabr)
.field(&origin)
.field(&gradient)
.finish(),
Primitive::Segment { segment, radius } => f
.debug_tuple("Segment")
.field(&segment)
.field(&radius)
.finish(),
Primitive::SegmentPrism {
segment,
radius,
height,
} => f
.debug_tuple("SegmentPrism")
.field(&segment)
.field(&radius)
.field(&height)
.finish(),
Primitive::Sampling(prim, _) => f.debug_tuple("Sampling").field(&prim).finish(),
Primitive::Prefab(prefab) => f.debug_tuple("Prefab").field(&prefab).finish(),
Primitive::Intersect(a, b) => f.debug_tuple("Intersect").field(&a).field(&b).finish(),
Primitive::Union(a, b) => f.debug_tuple("Union").field(&a).field(&b).finish(),
Primitive::Without(a, b) => f.debug_tuple("Without").field(&a).field(&b).finish(),
Primitive::Rotate(a, mat) => f.debug_tuple("Rotate").field(&a).field(&mat).finish(),
Primitive::Translate(a, vec) => {
f.debug_tuple("Translate").field(&a).field(&vec).finish()
},
Primitive::Scale(a, vec) => f.debug_tuple("Scale").field(&a).field(&vec).finish(),
Primitive::Repeat(a, offset, n) => f
.debug_tuple("Repeat")
.field(&a)
.field(&offset)
.field(&n)
.finish(),
}
}
}
impl Primitive { impl Primitive {
pub fn intersect(a: impl Into<Id<Primitive>>, b: impl Into<Id<Primitive>>) -> Self { pub fn intersect(a: impl Into<Id<Primitive>>, b: impl Into<Id<Primitive>>) -> Self {
Self::Intersect(a.into(), b.into()) Self::Intersect(a.into(), b.into())
@ -387,7 +450,7 @@ impl Fill {
} }
} }
fn get_bounds_inner(&self, tree: &Store<Primitive>, prim: Id<Primitive>) -> Option<Aabb<i32>> { fn get_bounds_inner(&self, tree: &Store<Primitive>, prim: Id<Primitive>) -> Vec<Aabb<i32>> {
fn or_zip_with<T, F: FnOnce(T, T) -> T>(a: Option<T>, b: Option<T>, f: F) -> Option<T> { fn or_zip_with<T, F: FnOnce(T, T) -> T>(a: Option<T>, b: Option<T>, f: F) -> Option<T> {
match (a, b) { match (a, b) {
(Some(a), Some(b)) => Some(f(a, b)), (Some(a), Some(b)) => Some(f(a, b)),
@ -396,16 +459,16 @@ impl Fill {
} }
} }
Some(match &tree[prim] { match &tree[prim] {
Primitive::Empty => return None, Primitive::Empty => vec![],
Primitive::Aabb(aabb) => *aabb, Primitive::Aabb(aabb) => vec![*aabb],
Primitive::Pyramid { aabb, .. } => *aabb, Primitive::Pyramid { aabb, .. } => vec![*aabb],
Primitive::Gable { aabb, .. } => *aabb, Primitive::Gable { aabb, .. } => vec![*aabb],
Primitive::Ramp { aabb, .. } => *aabb, Primitive::Ramp { aabb, .. } => vec![*aabb],
Primitive::Cylinder(aabb) => *aabb, Primitive::Cylinder(aabb) => vec![*aabb],
Primitive::Cone(aabb) => *aabb, Primitive::Cone(aabb) => vec![*aabb],
Primitive::Sphere(aabb) => *aabb, Primitive::Sphere(aabb) => vec![*aabb],
Primitive::Superquadric { aabb, .. } => *aabb, Primitive::Superquadric { aabb, .. } => vec![*aabb],
Primitive::Plane(aabr, origin, gradient) => { Primitive::Plane(aabr, origin, gradient) => {
let half_size = aabr.half_size().reduce_max(); let half_size = aabr.half_size().reduce_max();
let longest_dist = ((aabr.center() - origin.xy()).map(|x| x.abs()) let longest_dist = ((aabr.center() - origin.xy()).map(|x| x.abs())
@ -421,7 +484,7 @@ impl Fill {
min: aabr.min.with_z(origin.z + z.reduce_min().min(0)), min: aabr.min.with_z(origin.z + z.reduce_min().min(0)),
max: aabr.max.with_z(origin.z + z.reduce_max().max(0)), max: aabr.max.with_z(origin.z + z.reduce_max().max(0)),
}; };
aabb.made_valid() vec![aabb.made_valid()]
}, },
Primitive::Segment { segment, radius } => { Primitive::Segment { segment, radius } => {
let aabb = Aabb { let aabb = Aabb {
@ -429,10 +492,10 @@ impl Fill {
max: segment.end, max: segment.end,
} }
.made_valid(); .made_valid();
Aabb { vec![Aabb {
min: (aabb.min - *radius).floor().as_(), min: (aabb.min - *radius).floor().as_(),
max: (aabb.max + *radius).ceil().as_(), max: (aabb.max + *radius).ceil().as_(),
} }]
}, },
Primitive::SegmentPrism { Primitive::SegmentPrism {
segment, segment,
@ -452,58 +515,108 @@ impl Fill {
let xy = (aabb.max.xy() + *radius).ceil(); let xy = (aabb.max.xy() + *radius).ceil();
xy.with_z((aabb.max.z + *height).ceil()).as_() xy.with_z((aabb.max.z + *height).ceil()).as_()
}; };
Aabb { min, max } vec![Aabb { min, max }]
}, },
Primitive::Sampling(a, _) => self.get_bounds_inner(tree, *a)?, Primitive::Sampling(a, _) => self.get_bounds_inner(tree, *a),
Primitive::Prefab(p) => p.get_bounds(), Primitive::Prefab(p) => vec![p.get_bounds()],
Primitive::Intersect(a, b) => or_zip_with( Primitive::Intersect(a, b) => or_zip_with(
self.get_bounds_inner(tree, *a), self.get_bounds_opt(tree, *a),
self.get_bounds_inner(tree, *b), self.get_bounds_opt(tree, *b),
|a, b| a.intersection(b), |a, b| a.intersection(b),
)?, ).into_iter().collect(),
Primitive::Union(a, b) => or_zip_with(
self.get_bounds_inner(tree, *a), Primitive::Union(a, b) => {
self.get_bounds_inner(tree, *b), fn jaccard(x: Aabb<i32>, y: Aabb<i32>) -> f32 {
|a, b| a.union(b), let s_intersection = x.intersection(y).size().as_::<f32>().magnitude();
)?, let s_union = x.union(y).size().as_::<f32>().magnitude();
Primitive::Without(a, _) => self.get_bounds_inner(tree, *a)?, s_intersection / s_union
Primitive::Rotate(prim, mat) => { }
let aabb = self.get_bounds_inner(tree, *prim)?; let mut inputs = Vec::new();
let extent = *mat * Vec3::from(aabb.size()); inputs.extend(self.get_bounds_inner(tree, *a));
let new_aabb: Aabb<i32> = Aabb { inputs.extend(self.get_bounds_inner(tree, *b));
min: aabb.min, let mut results = Vec::new();
max: aabb.min + extent, if let Some(aabb) = inputs.pop() {
}; results.push(aabb);
new_aabb.made_valid() for a in &inputs {
let best = results.iter().enumerate().max_by_key(|(_, b)| (jaccard(*a, **b) * 1000.0) as usize);
match best {
Some((i, b)) if jaccard(*a, *b) > 0.3 => {
let mut aabb = results.swap_remove(i);
aabb = aabb.union(*a);
results.push(aabb);
},
_ => results.push(*a),
}
}
results
} else {
results
}
}, },
Primitive::Translate(prim, vec) => { Primitive::Without(a, _) => self.get_bounds_inner(tree, *a),
let aabb = self.get_bounds_inner(tree, *prim)?; Primitive::Rotate(prim, mat) => self
Aabb { .get_bounds_inner(tree, *prim)
.into_iter()
.map(|aabb| {
let extent = *mat * Vec3::from(aabb.size());
let new_aabb: Aabb<i32> = Aabb {
min: aabb.min,
max: aabb.min + extent,
};
new_aabb.made_valid()
})
.collect(),
Primitive::Translate(prim, vec) => self
.get_bounds_inner(tree, *prim)
.into_iter()
.map(|aabb| Aabb {
min: aabb.min.map2(*vec, i32::saturating_add), min: aabb.min.map2(*vec, i32::saturating_add),
max: aabb.max.map2(*vec, i32::saturating_add), max: aabb.max.map2(*vec, i32::saturating_add),
} })
}, .collect(),
Primitive::Scale(prim, vec) => { Primitive::Scale(prim, vec) => self
let aabb = self.get_bounds_inner(tree, *prim)?; .get_bounds_inner(tree, *prim)
let center = aabb.center(); .into_iter()
Aabb { .map(|aabb| {
min: center + ((aabb.min - center).as_::<f32>() * vec).as_::<i32>(), let center = aabb.center();
max: center + ((aabb.max - center).as_::<f32>() * vec).as_::<i32>(), Aabb {
} min: center + ((aabb.min - center).as_::<f32>() * vec).as_::<i32>(),
}, max: center + ((aabb.max - center).as_::<f32>() * vec).as_::<i32>(),
Primitive::Repeat(prim, offset, count) => { }
let aabb = self.get_bounds_inner(tree, *prim)?; })
Aabb { .collect(),
Primitive::Repeat(prim, offset, count) => self
.get_bounds_inner(tree, *prim)
.into_iter()
.map(|aabb| Aabb {
min: aabb.min.map2(aabb.min + offset * count, |a, b| a.min(b)), min: aabb.min.map2(aabb.min + offset * count, |a, b| a.min(b)),
max: aabb.max.map2(aabb.max + offset * count, |a, b| a.max(b)), max: aabb.max.map2(aabb.max + offset * count, |a, b| a.max(b)),
} })
}, .collect(),
}) }
}
pub fn get_bounds_disjoint(
&self,
tree: &Store<Primitive>,
prim: Id<Primitive>,
) -> Vec<Aabb<i32>> {
self.get_bounds_inner(tree, prim)
}
pub fn get_bounds_opt(
&self,
tree: &Store<Primitive>,
prim: Id<Primitive>,
) -> Option<Aabb<i32>> {
self.get_bounds_inner(tree, prim)
.into_iter()
.reduce(|a, b| a.union(b))
} }
pub fn get_bounds(&self, tree: &Store<Primitive>, prim: Id<Primitive>) -> Aabb<i32> { pub fn get_bounds(&self, tree: &Store<Primitive>, prim: Id<Primitive>) -> Aabb<i32> {
self.get_bounds_inner(tree, prim) self.get_bounds_opt(tree, prim)
.unwrap_or_else(|| Aabb::new_empty(Vec3::zero())) .unwrap_or(Aabb::new_empty(Vec3::zero()))
} }
} }

View File

@ -983,31 +983,32 @@ impl Site {
}; };
for (prim, fill) in fills { for (prim, fill) in fills {
let mut aabb = fill.get_bounds(&prim_tree, prim); for mut aabb in fill.get_bounds_disjoint(&prim_tree, prim) {
aabb.min = Vec2::max(aabb.min.xy(), chunk_aabr.min).with_z(aabb.min.z); aabb.min = Vec2::max(aabb.min.xy(), chunk_aabr.min).with_z(aabb.min.z);
aabb.max = Vec2::min(aabb.max.xy(), chunk_aabr.max).with_z(aabb.max.z); aabb.max = Vec2::min(aabb.max.xy(), chunk_aabr.max).with_z(aabb.max.z);
for x in aabb.min.x..aabb.max.x { for x in aabb.min.x..aabb.max.x {
for y in aabb.min.y..aabb.max.y { for y in aabb.min.y..aabb.max.y {
let col_tile = self.wpos_tile(Vec2::new(x, y)); let col_tile = self.wpos_tile(Vec2::new(x, y));
if if
/* col_tile.is_building() && */ /* col_tile.is_building() && */
col_tile col_tile
.plot .plot
.and_then(|p| self.plots[p].z_range()) .and_then(|p| self.plots[p].z_range())
.zip(self.plots[plot].z_range()) .zip(self.plots[plot].z_range())
.map_or(false, |(a, b)| a.end > b.end) .map_or(false, |(a, b)| a.end > b.end)
{ {
continue; continue;
} }
for z in aabb.min.z..aabb.max.z { for z in aabb.min.z..aabb.max.z {
let pos = Vec3::new(x, y, z); let pos = Vec3::new(x, y, z);
canvas.map(pos, |block| { canvas.map(pos, |block| {
fill.sample_at(&prim_tree, prim, pos, &info, block) fill.sample_at(&prim_tree, prim, pos, &info, block)
.unwrap_or(block) .unwrap_or(block)
}); });
}
} }
} }
} }