#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use crate::assets::AssetId;
use crate::features::{FinitePoint3, FiniteVector3};
use crate::ids::{BodyId, FaceId};
use crate::math::{Point3, Vector3};
use crate::provenance::SourceObjectAssociation;
use crate::scalar::NonNegativeReal;
crate::ids::id_type!(
TessellationId, compose, into_string
);
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TessellationError {
#[non_exhaustive]
Admission(String),
EditRefused(String),
}
impl std::fmt::Display for TessellationError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Admission(message) | Self::EditRefused(message) => formatter.write_str(message),
}
}
}
impl std::error::Error for TessellationError {}
fn tessellation_error(message: impl Into<String>) -> TessellationError {
TessellationError::Admission(message.into())
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct ShadedVertex<P = Point3, N = Vector3> {
pub position: P,
pub normal: N,
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct ShadedTriangle<N = Vector3> {
pub corners: [u32; 3],
pub normals: [N; 3],
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct Strip<V>(Vec<V>);
impl<V> Strip<V> {
#[must_use]
pub fn new(vertices: Vec<V>) -> Option<Self> {
(vertices.len() >= 3).then_some(Self(vertices))
}
#[must_use]
pub fn vertices(&self) -> &[V] {
&self.0
}
pub fn vertices_mut(&mut self) -> &mut [V] {
&mut self.0
}
#[must_use]
pub fn triangle_count(&self) -> usize {
self.0.len() - 2
}
}
impl<'de, V: Deserialize<'de>> Deserialize<'de> for Strip<V> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Self::new(Vec::<V>::deserialize(deserializer)?)
.ok_or_else(|| serde::de::Error::custom("tessellation strip spans no triangle"))
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(transparent)]
pub struct Strips<V>(Vec<Strip<V>>);
impl<V> Strips<V> {
#[must_use]
pub fn new(strips: Vec<Strip<V>>) -> Option<Self> {
if strips.is_empty() {
return None;
}
let span = strips.iter().try_fold(0u64, |total, strip| {
total.checked_add(cadmpeg_core::decode::u64_from_index(strip.vertices().len()))
})?;
u32::try_from(span).is_ok().then_some(Self(strips))
}
#[must_use]
pub fn as_slice(&self) -> &[Strip<V>] {
&self.0
}
pub fn vertices_mut(&mut self) -> impl Iterator<Item = &mut V> {
self.0.iter_mut().flat_map(Strip::vertices_mut)
}
fn try_map<W, E>(self, mut map: impl FnMut(V) -> Result<W, E>) -> Result<Strips<W>, E> {
self.0
.into_iter()
.map(|strip| {
strip
.0
.into_iter()
.map(&mut map)
.collect::<Result<Vec<_>, E>>()
.map(Strip)
})
.collect::<Result<Vec<_>, E>>()
.map(Strips)
}
}
impl<V> Strips<V> {
#[must_use]
pub fn from_spans(vertices: Vec<V>, spans: &[u32]) -> Option<Self> {
let mut remaining = vertices.into_iter();
let mut strips = Vec::new();
for span in spans {
let span = usize::try_from(*span).ok()?;
let run: Vec<V> = remaining.by_ref().take(span).collect();
if run.len() != span {
return None;
}
strips.push(Strip::new(run)?);
}
if remaining.next().is_some() {
return None;
}
Self::new(strips)
}
}
impl<'de, V: Deserialize<'de>> Deserialize<'de> for Strips<V> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
Self::new(Vec::<Strip<V>>::deserialize(deserializer)?)
.ok_or_else(|| serde::de::Error::custom("tessellation strip set is empty"))
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum TessellationMesh<P = Point3, N = Vector3> {
List {
vertices: Vec<P>,
triangles: Vec<[u32; 3]>,
},
ShadedList {
vertices: Vec<ShadedVertex<P, N>>,
triangles: Vec<[u32; 3]>,
},
CornerShadedList {
vertices: Vec<P>,
triangles: Vec<ShadedTriangle<N>>,
},
Strips {
strips: Strips<P>,
},
ShadedStrips {
strips: Strips<ShadedVertex<P, N>>,
},
}
fn strip_triangles<V>(strips: &Strips<V>) -> Vec<[u32; 3]> {
let mut triangles = Vec::new();
let mut base: u32 = 0;
for strip in strips.as_slice() {
let mut index: u32 = 0;
for _ in 0..strip.triangle_count() {
let a = base + index;
triangles.push(if index.is_multiple_of(2) {
[a, a + 1, a + 2]
} else {
[a, a + 2, a + 1]
});
index += 1;
}
base += index + 2;
}
triangles
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum TessellationLaneError {
#[error("{vertices} vertex/vertices against {normals} vertex normal(s)")]
VertexNormalLane {
vertices: usize,
normals: usize,
},
#[error("{corners} triangle corner(s) against {normals} corner normal(s)")]
CornerNormalLane {
corners: usize,
normals: usize,
},
#[error("{triangles} triangle(s) name more corners than an index can count")]
CornerCountOverflow {
triangles: usize,
},
#[error("{spans} strip span(s) do not cut the vertex lane")]
Strips {
spans: usize,
},
}
impl From<TessellationLaneError> for cadmpeg_core::CodecError {
fn from(error: TessellationLaneError) -> Self {
Self::Malformed(error.to_string())
}
}
impl TessellationMesh {
pub fn from_strip_lanes(
positions: Vec<Point3>,
normals: Option<Vec<Vector3>>,
spans: &[u32],
) -> Result<Self, TessellationLaneError> {
let Some(normals) = normals else {
let strips = Strips::from_spans(positions, spans)
.ok_or(TessellationLaneError::Strips { spans: spans.len() })?;
return Ok(Self::Strips { strips });
};
if normals.len() != positions.len() {
return Err(TessellationLaneError::VertexNormalLane {
vertices: positions.len(),
normals: normals.len(),
});
}
let rows = positions
.into_iter()
.zip(normals)
.map(|(position, normal)| ShadedVertex { position, normal })
.collect();
let strips = Strips::from_spans(rows, spans)
.ok_or(TessellationLaneError::Strips { spans: spans.len() })?;
Ok(Self::ShadedStrips { strips })
}
pub fn from_list_lanes(
positions: Vec<Point3>,
triangles: Vec<[u32; 3]>,
normals: Option<Vec<Vector3>>,
) -> Result<Self, TessellationLaneError> {
Self::pair_list_lanes(positions, triangles, normals)
}
}
impl<P, N> TessellationMesh<P, N> {
fn pair_list_lanes(
positions: Vec<P>,
triangles: Vec<[u32; 3]>,
normals: Option<Vec<N>>,
) -> Result<Self, TessellationLaneError> {
let Some(normals) = normals else {
return Ok(Self::List {
vertices: positions,
triangles,
});
};
if normals.len() != positions.len() {
return Err(TessellationLaneError::VertexNormalLane {
vertices: positions.len(),
normals: normals.len(),
});
}
Ok(Self::ShadedList {
vertices: positions
.into_iter()
.zip(normals)
.map(|(position, normal)| ShadedVertex { position, normal })
.collect(),
triangles,
})
}
}
impl<P: Copy, N: Copy> TessellationMesh<P, N> {
#[must_use]
pub fn vertices(&self) -> Vec<P> {
match self {
Self::List { vertices, .. } | Self::CornerShadedList { vertices, .. } => {
vertices.clone()
}
Self::ShadedList { vertices, .. } => {
vertices.iter().map(|vertex| vertex.position).collect()
}
Self::Strips { strips } => strips
.as_slice()
.iter()
.flat_map(|strip| strip.vertices().iter().copied())
.collect(),
Self::ShadedStrips { strips } => strips
.as_slice()
.iter()
.flat_map(|strip| strip.vertices().iter().map(|vertex| vertex.position))
.collect(),
}
}
#[must_use]
pub fn vertex_count(&self) -> usize {
match self {
Self::List { vertices, .. } | Self::CornerShadedList { vertices, .. } => vertices.len(),
Self::ShadedList { vertices, .. } => vertices.len(),
Self::Strips { strips } => strips
.as_slice()
.iter()
.map(|strip| strip.vertices().len())
.sum(),
Self::ShadedStrips { strips } => strips
.as_slice()
.iter()
.map(|strip| strip.vertices().len())
.sum(),
}
}
#[must_use]
pub fn triangles(&self) -> Vec<[u32; 3]> {
match self {
Self::List { triangles, .. } | Self::ShadedList { triangles, .. } => triangles.clone(),
Self::CornerShadedList { triangles, .. } => {
triangles.iter().map(|triangle| triangle.corners).collect()
}
Self::Strips { strips } => strip_triangles(strips),
Self::ShadedStrips { strips } => strip_triangles(strips),
}
}
#[must_use]
pub fn triangle_count(&self) -> usize {
match self {
Self::List { triangles, .. } | Self::ShadedList { triangles, .. } => triangles.len(),
Self::CornerShadedList { triangles, .. } => triangles.len(),
Self::Strips { strips } => strips.as_slice().iter().map(Strip::triangle_count).sum(),
Self::ShadedStrips { strips } => {
strips.as_slice().iter().map(Strip::triangle_count).sum()
}
}
}
#[must_use]
pub fn vertex_normals(&self) -> Vec<N> {
match self {
Self::List { .. } | Self::CornerShadedList { .. } | Self::Strips { .. } => Vec::new(),
Self::ShadedList { vertices, .. } => {
vertices.iter().map(|vertex| vertex.normal).collect()
}
Self::ShadedStrips { strips } => strips
.as_slice()
.iter()
.flat_map(|strip| strip.vertices().iter().map(|vertex| vertex.normal))
.collect(),
}
}
#[must_use]
pub fn corner_normals(&self) -> Vec<N> {
match self {
Self::List { .. }
| Self::ShadedList { .. }
| Self::Strips { .. }
| Self::ShadedStrips { .. } => Vec::new(),
Self::CornerShadedList { triangles, .. } => triangles
.iter()
.flat_map(|triangle| triangle.normals)
.collect(),
}
}
#[must_use]
pub fn strip_lengths(&self) -> Vec<u32> {
let spans = |lengths: Vec<usize>| {
lengths
.into_iter()
.filter_map(|length| u32::try_from(length).ok())
.collect()
};
match self {
Self::List { .. } | Self::ShadedList { .. } | Self::CornerShadedList { .. } => {
Vec::new()
}
Self::Strips { strips } => spans(
strips
.as_slice()
.iter()
.map(|strip| strip.vertices().len())
.collect(),
),
Self::ShadedStrips { strips } => spans(
strips
.as_slice()
.iter()
.map(|strip| strip.vertices().len())
.collect(),
),
}
}
fn apply_positions(
&mut self,
mut edit: impl FnMut(&mut P) -> Result<(), TessellationError>,
) -> Result<(), TessellationError> {
match self {
Self::List { vertices, .. } | Self::CornerShadedList { vertices, .. } => {
for vertex in vertices.iter_mut() {
edit(vertex)?;
}
}
Self::ShadedList { vertices, .. } => {
for vertex in vertices.iter_mut() {
edit(&mut vertex.position)?;
}
}
Self::Strips { strips } => {
for vertex in strips.vertices_mut() {
edit(vertex)?;
}
}
Self::ShadedStrips { strips } => {
for vertex in strips.vertices_mut() {
edit(&mut vertex.position)?;
}
}
}
Ok(())
}
fn apply_normals(
&mut self,
mut edit: impl FnMut(&mut N) -> Result<(), TessellationError>,
) -> Result<bool, TessellationError> {
match self {
Self::List { .. } | Self::Strips { .. } => return Ok(false),
Self::ShadedList { vertices, .. } => {
for vertex in vertices.iter_mut() {
edit(&mut vertex.normal)?;
}
}
Self::CornerShadedList { triangles, .. } => {
for triangle in triangles.iter_mut() {
for normal in &mut triangle.normals {
edit(normal)?;
}
}
}
Self::ShadedStrips { strips } => {
for vertex in strips.vertices_mut() {
edit(&mut vertex.normal)?;
}
}
}
Ok(true)
}
}
impl<P, N> TessellationMesh<P, N> {
pub fn from_corner_lanes(
positions: Vec<P>,
triangles: Vec<[u32; 3]>,
corner_normals: Option<Vec<N>>,
) -> Result<Self, TessellationLaneError> {
let Some(corner_normals) = corner_normals else {
return Ok(Self::List {
vertices: positions,
triangles,
});
};
let Some(corner_count) = triangles.len().checked_mul(3) else {
return Err(TessellationLaneError::CornerCountOverflow {
triangles: triangles.len(),
});
};
if corner_normals.len() != corner_count {
return Err(TessellationLaneError::CornerNormalLane {
corners: corner_count,
normals: corner_normals.len(),
});
}
let mut normals = corner_normals.into_iter();
let rows = triangles
.into_iter()
.map(|corners| {
let triple: Vec<N> = normals.by_ref().take(3).collect();
let normals = <[N; 3]>::try_from(triple).map_err(|short: Vec<_>| {
TessellationLaneError::CornerNormalLane {
corners: corner_count,
normals: short.len(),
}
})?;
Ok(ShadedTriangle { corners, normals })
})
.collect::<Result<Vec<_>, TessellationLaneError>>()?;
Ok(Self::CornerShadedList {
vertices: positions,
triangles: rows,
})
}
fn try_map_points<Q, E>(
self,
mut point: impl FnMut(P) -> Result<Q, E>,
) -> Result<TessellationMesh<Q, N>, E> {
Ok(match self {
Self::List {
vertices,
triangles,
} => TessellationMesh::List {
vertices: vertices.into_iter().map(point).collect::<Result<_, E>>()?,
triangles,
},
Self::ShadedList {
vertices,
triangles,
} => TessellationMesh::ShadedList {
vertices: vertices
.into_iter()
.map(|vertex| {
Ok(ShadedVertex {
position: point(vertex.position)?,
normal: vertex.normal,
})
})
.collect::<Result<_, E>>()?,
triangles,
},
Self::CornerShadedList {
vertices,
triangles,
} => TessellationMesh::CornerShadedList {
vertices: vertices.into_iter().map(point).collect::<Result<_, E>>()?,
triangles,
},
Self::Strips { strips } => TessellationMesh::Strips {
strips: strips.try_map(point)?,
},
Self::ShadedStrips { strips } => TessellationMesh::ShadedStrips {
strips: strips.try_map(|vertex| {
Ok(ShadedVertex {
position: point(vertex.position)?,
normal: vertex.normal,
})
})?,
},
})
}
fn try_map_normals<M, E>(
self,
mut normal: impl FnMut(N) -> Result<M, E>,
) -> Result<TessellationMesh<P, M>, E> {
Ok(match self {
Self::List {
vertices,
triangles,
} => TessellationMesh::List {
vertices,
triangles,
},
Self::ShadedList {
vertices,
triangles,
} => TessellationMesh::ShadedList {
vertices: vertices
.into_iter()
.map(|vertex| {
Ok(ShadedVertex {
position: vertex.position,
normal: normal(vertex.normal)?,
})
})
.collect::<Result<_, E>>()?,
triangles,
},
Self::CornerShadedList {
vertices,
triangles,
} => TessellationMesh::CornerShadedList {
vertices,
triangles: triangles
.into_iter()
.map(|triangle| {
let [first, second, third] = triangle.normals;
Ok(ShadedTriangle {
corners: triangle.corners,
normals: [normal(first)?, normal(second)?, normal(third)?],
})
})
.collect::<Result<_, E>>()?,
},
Self::Strips { strips } => TessellationMesh::Strips { strips },
Self::ShadedStrips { strips } => TessellationMesh::ShadedStrips {
strips: strips.try_map(|vertex| {
Ok(ShadedVertex {
position: vertex.position,
normal: normal(vertex.normal)?,
})
})?,
},
})
}
}
impl TessellationMesh<FinitePoint3, FiniteVector3> {
pub fn from_checked_list_lanes(
positions: Vec<FinitePoint3>,
triangles: Vec<[u32; 3]>,
normals: Option<Vec<FiniteVector3>>,
) -> Result<Self, TessellationLaneError> {
Self::pair_list_lanes(positions, triangles, normals)
}
#[must_use]
pub fn into_raw(self) -> TessellationMesh {
let Ok(positions) =
self.try_map_points(|point| Ok::<_, std::convert::Infallible>(point.get()));
let Ok(raw) =
positions.try_map_normals(|normal| Ok::<_, std::convert::Infallible>(normal.get()));
raw
}
}
fn admit_vertex(point: Point3) -> Result<FinitePoint3, TessellationError> {
FinitePoint3::new(point)
.ok_or_else(|| tessellation_error("vertices contain a non-finite coordinate"))
}
fn admit_normal(normal: Vector3) -> Result<FiniteVector3, TessellationError> {
FiniteVector3::new(normal)
.ok_or_else(|| tessellation_error("normals contain a non-finite coordinate"))
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "domain", rename_all = "snake_case")]
#[serde(deny_unknown_fields)]
pub enum ChannelAddressing {
Vertex {},
Corner {
indices: Vec<u32>,
},
Triangle {
indices: Vec<u32>,
},
}
impl ChannelAddressing {
#[must_use]
pub fn indices(&self) -> &[u32] {
match self {
Self::Vertex {} => &[],
Self::Corner { indices } | Self::Triangle { indices } => indices,
}
}
}
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct TessellationWire {
id: TessellationId,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_body"
)]
body: Option<BodyId>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
faces: Vec<FaceId>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_chordal_deflection"
)]
chordal_deflection: Option<f64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_source_object"
)]
source_object: Option<SourceObjectAssociation>,
mesh: TessellationMesh,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
feature_edges: Vec<[u32; 2]>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
triangle_groups: Vec<TessellationTriangleGroup>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
texture_assignments: Vec<TessellationTextureAssignment>,
#[serde(default)]
channels: Vec<TessellationChannel>,
}
#[derive(Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
struct TessellationChannelWire {
addressing: ChannelAddressing,
item_size: u32,
kind: u32,
flags: u32,
#[serde(with = "crate::bytes")]
#[cfg_attr(feature = "schema", schemars(with = "String"))]
data: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(try_from = "TessellationWire", into = "TessellationWire")]
pub struct Tessellation {
pub id: TessellationId,
pub body: Option<BodyId>,
pub faces: Vec<FaceId>,
chordal_deflection: Option<NonNegativeReal>,
pub source_object: Option<SourceObjectAssociation>,
mesh: TessellationMesh<FinitePoint3, FiniteVector3>,
feature_edges: Vec<[u32; 2]>,
triangle_groups: Vec<TessellationTriangleGroup>,
texture_assignments: Vec<TessellationTextureAssignment>,
channels: Vec<TessellationChannel>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct TessellationTriangleGroup {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_source_id"
)]
pub source_id: Option<String>,
pub triangles: Vec<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(deny_unknown_fields)]
pub struct TessellationTextureAssignment {
#[serde(
default,
skip_serializing_if = "Option::is_none",
deserialize_with = "deserialize_source_id"
)]
pub source_id: Option<String>,
pub texture: AssetId,
pub triangles: Vec<u32>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(try_from = "TessellationChannelWire", into = "TessellationChannelWire")]
pub struct TessellationChannel {
addressing: ChannelAddressing,
item_size: u32,
kind: u32,
flags: u32,
data: Vec<u8>,
count: u32,
}
fn require_triangle_indices(
vertex_count: usize,
triangles: &[[u32; 3]],
) -> Result<(), TessellationError> {
if triangles
.iter()
.flatten()
.any(|index| cadmpeg_core::decode::index_from_u32(*index) >= vertex_count)
{
return Err(tessellation_error(
"contains an out-of-range tessellation index",
));
}
Ok(())
}
fn require_channel_indices(
triangle_count: usize,
channels: &[TessellationChannel],
) -> Result<(), TessellationError> {
let corner_count = triangle_count
.checked_mul(3)
.ok_or_else(|| tessellation_error("tessellation corner count overflows usize"))?;
for channel in channels {
let expected = match channel.addressing() {
ChannelAddressing::Vertex {} => 0,
ChannelAddressing::Corner { .. } => corner_count,
ChannelAddressing::Triangle { .. } => triangle_count,
};
if channel.indices().len() != expected
|| channel
.indices()
.iter()
.any(|index| *index >= channel.count())
{
return Err(tessellation_error(
"contains invalid tessellation channel indices",
));
}
}
Ok(())
}
fn require_feature_edges(
vertex_count: usize,
feature_edges: &[[u32; 2]],
) -> Result<(), TessellationError> {
if feature_edges.iter().any(|edge| {
edge[0] >= edge[1] || usize::try_from(edge[1]).map_or(true, |index| index >= vertex_count)
}) || feature_edges.windows(2).any(|edges| edges[0] >= edges[1])
{
return Err(tessellation_error(
"contains an invalid tessellation feature edge",
));
}
Ok(())
}
fn require_triangle_groups(
triangle_count: usize,
triangle_groups: &[TessellationTriangleGroup],
) -> Result<(), TessellationError> {
if triangle_groups.is_empty() {
return Ok(());
}
let mut memberships = std::iter::repeat_with(|| false)
.take(triangle_count)
.collect::<Vec<_>>();
let mut source_ids = std::collections::BTreeSet::new();
let valid = triangle_groups.iter().all(|group| {
!group.triangles.is_empty()
&& group
.triangles
.windows(2)
.all(|ordinals| ordinals[0] < ordinals[1])
&& group.triangles.iter().all(|ordinal| {
usize::try_from(*ordinal)
.ok()
.and_then(|ordinal| memberships.get_mut(ordinal))
.is_some_and(|visited| {
if *visited {
return false;
}
*visited = true;
true
})
})
&& group.source_id.as_ref().is_none_or(|source_id| {
!source_id.is_empty() && source_ids.insert(source_id.as_str())
})
}) && memberships.iter().all(|visited| *visited);
if !valid {
return Err(tessellation_error(
"contains an invalid tessellation triangle-group partition",
));
}
Ok(())
}
fn require_texture_assignments(
triangle_count: usize,
texture_assignments: &[TessellationTextureAssignment],
) -> Result<(), TessellationError> {
if texture_assignments.is_empty() {
return Ok(());
}
let mut memberships = std::iter::repeat_with(|| false)
.take(triangle_count)
.collect::<Vec<_>>();
let mut source_ids = std::collections::BTreeSet::new();
let mut anonymous_textures = std::collections::BTreeSet::new();
let valid = texture_assignments.iter().all(|assignment| {
!assignment.triangles.is_empty()
&& match assignment.source_id.as_deref() {
Some(source_id) => !source_id.is_empty() && source_ids.insert(source_id),
None => anonymous_textures.insert(&assignment.texture),
}
&& assignment
.triangles
.windows(2)
.all(|ordinals| ordinals[0] < ordinals[1])
&& assignment.triangles.iter().all(|ordinal| {
usize::try_from(*ordinal)
.ok()
.and_then(|ordinal| memberships.get_mut(ordinal))
.is_some_and(|visited| {
if *visited {
return false;
}
*visited = true;
true
})
})
});
if !valid {
return Err(tessellation_error(
"contains invalid tessellation texture assignments",
));
}
Ok(())
}
impl Tessellation {
pub fn new(
id: TessellationId,
mesh: TessellationMesh,
channels: Vec<TessellationChannel>,
) -> Result<Self, TessellationError> {
let mesh = mesh
.try_map_points(admit_vertex)?
.try_map_normals(admit_normal)?;
Self::from_parts(id, mesh, channels)
}
pub fn from_parts(
id: TessellationId,
mesh: TessellationMesh<FinitePoint3, FiniteVector3>,
channels: Vec<TessellationChannel>,
) -> Result<Self, TessellationError> {
let triangle_count = match &mesh {
TessellationMesh::Strips { .. } | TessellationMesh::ShadedStrips { .. } => {
mesh.triangle_count()
}
_ => {
let triangles = mesh.triangles();
require_triangle_indices(mesh.vertex_count(), &triangles)?;
triangles.len()
}
};
require_channel_indices(triangle_count, &channels)?;
Ok(Self {
id,
body: None,
faces: Vec::new(),
chordal_deflection: None,
source_object: None,
mesh,
feature_edges: Vec::new(),
triangle_groups: Vec::new(),
texture_assignments: Vec::new(),
channels,
})
}
pub fn with_mesh(
self,
mesh: TessellationMesh<FinitePoint3, FiniteVector3>,
channels: Vec<TessellationChannel>,
) -> Result<Self, TessellationError> {
let triangles = mesh.triangles();
require_triangle_indices(mesh.vertex_count(), &triangles)?;
require_channel_indices(triangles.len(), &channels)?;
require_feature_edges(mesh.vertex_count(), &self.feature_edges)?;
require_triangle_groups(triangles.len(), &self.triangle_groups)?;
require_texture_assignments(triangles.len(), &self.texture_assignments)?;
Ok(Self {
mesh,
channels,
..self
})
}
#[must_use]
pub const fn mesh(&self) -> &TessellationMesh<FinitePoint3, FiniteVector3> {
&self.mesh
}
#[must_use]
pub fn vertices(&self) -> Vec<FinitePoint3> {
self.mesh.vertices()
}
#[must_use]
pub fn vertex_count(&self) -> usize {
self.mesh.vertex_count()
}
pub fn edit_vertices(
&mut self,
edit: impl FnMut(&mut Point3) -> Result<(), TessellationError>,
) -> Result<(), TessellationError> {
let Ok(mut mesh) = self
.mesh
.clone()
.try_map_points(|point| Ok::<_, std::convert::Infallible>(point.get()));
mesh.apply_positions(edit)?;
self.mesh = mesh.try_map_points(admit_vertex)?;
Ok(())
}
#[must_use]
pub fn triangles(&self) -> Vec<[u32; 3]> {
self.mesh.triangles()
}
#[must_use]
pub fn triangle_count(&self) -> usize {
self.mesh.triangle_count()
}
#[must_use]
pub fn strip_lengths(&self) -> Vec<u32> {
self.mesh.strip_lengths()
}
#[must_use]
pub fn vertex_normals(&self) -> Vec<FiniteVector3> {
self.mesh.vertex_normals()
}
pub fn edit_normals(
&mut self,
edit: impl FnMut(&mut Vector3) -> Result<(), TessellationError>,
) -> Result<(), TessellationError> {
let Ok(mut mesh) = self
.mesh
.clone()
.try_map_normals(|normal| Ok::<_, std::convert::Infallible>(normal.get()));
if !mesh.apply_normals(edit)? {
return Err(tessellation_error("mesh has no shading normals to edit"));
}
self.mesh = mesh.try_map_normals(admit_normal)?;
Ok(())
}
#[must_use]
pub fn per_corner_normals(&self) -> Vec<FiniteVector3> {
self.mesh.corner_normals()
}
#[must_use]
pub fn channels(&self) -> &[TessellationChannel] {
&self.channels
}
#[must_use]
pub fn feature_edges(&self) -> &[[u32; 2]] {
&self.feature_edges
}
#[must_use]
pub fn triangle_groups(&self) -> &[TessellationTriangleGroup] {
&self.triangle_groups
}
#[must_use]
pub fn texture_assignments(&self) -> &[TessellationTextureAssignment] {
&self.texture_assignments
}
#[must_use]
pub fn with_body(mut self, body: Option<BodyId>) -> Self {
self.body = body;
self
}
#[must_use]
pub fn with_faces(mut self, faces: Vec<FaceId>) -> Self {
self.faces = faces;
self
}
#[must_use]
pub const fn chordal_deflection(&self) -> Option<NonNegativeReal> {
self.chordal_deflection
}
pub fn set_chordal_deflection(
&mut self,
chordal_deflection: Option<f64>,
) -> Result<(), TessellationError> {
self.chordal_deflection = chordal_deflection
.map(|value| {
NonNegativeReal::new(value).ok_or_else(|| {
tessellation_error("chordal_deflection must be finite and non-negative")
})
})
.transpose()?;
Ok(())
}
pub fn scale_chordal_deflection(
&mut self,
scale: crate::scalar::PositiveReal,
) -> Result<(), TessellationError> {
if let Some(value) = self.chordal_deflection {
self.chordal_deflection = Some(value.scaled(scale).ok_or_else(|| {
tessellation_error("chordal_deflection must be finite and non-negative")
})?);
}
Ok(())
}
pub fn with_chordal_deflection(
mut self,
chordal_deflection: Option<f64>,
) -> Result<Self, TessellationError> {
self.set_chordal_deflection(chordal_deflection)?;
Ok(self)
}
#[must_use]
pub fn with_admitted_chordal_deflection(
mut self,
chordal_deflection: Option<NonNegativeReal>,
) -> Self {
self.chordal_deflection = chordal_deflection;
self
}
#[must_use]
pub fn with_source_object(mut self, source_object: Option<SourceObjectAssociation>) -> Self {
self.source_object = source_object;
self
}
pub fn with_feature_edges(
mut self,
feature_edges: Vec<[u32; 2]>,
) -> Result<Self, TessellationError> {
require_feature_edges(self.mesh.vertex_count(), &feature_edges)?;
self.feature_edges = feature_edges;
Ok(self)
}
pub fn with_triangle_groups(
mut self,
triangle_groups: Vec<TessellationTriangleGroup>,
) -> Result<Self, TessellationError> {
require_triangle_groups(self.mesh.triangle_count(), &triangle_groups)?;
self.triangle_groups = triangle_groups;
Ok(self)
}
pub fn with_texture_assignments(
mut self,
texture_assignments: Vec<TessellationTextureAssignment>,
) -> Result<Self, TessellationError> {
require_texture_assignments(self.mesh.triangle_count(), &texture_assignments)?;
self.texture_assignments = texture_assignments;
Ok(self)
}
}
impl TessellationChannel {
pub fn new(
addressing: ChannelAddressing,
item_size: u32,
kind: u32,
flags: u32,
data: Vec<u8>,
) -> Result<Self, TessellationError> {
let item_size_usize = usize::try_from(item_size)
.map_err(|_| tessellation_error("tessellation channel item size overflows usize"))?;
if item_size_usize == 0 {
if !data.is_empty() {
return Err(tessellation_error(
"contains a malformed tessellation channel",
));
}
} else if !data.len().is_multiple_of(item_size_usize) {
return Err(tessellation_error(
"contains a malformed tessellation channel",
));
}
let element_count = match std::num::NonZeroUsize::new(item_size_usize) {
None => 0,
Some(item_size) => data.len() / item_size,
};
let count = u32::try_from(element_count)
.map_err(|_| tessellation_error("tessellation channel count overflows u32"))?;
if addressing.indices().iter().any(|index| *index >= count) {
return Err(tessellation_error(
"contains invalid tessellation channel indices",
));
}
Ok(Self {
addressing,
item_size,
kind,
flags,
data,
count,
})
}
#[must_use]
pub fn addressing(&self) -> &ChannelAddressing {
&self.addressing
}
#[must_use]
pub const fn item_size(&self) -> u32 {
self.item_size
}
#[must_use]
pub const fn kind(&self) -> u32 {
self.kind
}
#[must_use]
pub const fn flags(&self) -> u32 {
self.flags
}
#[must_use]
pub const fn count(&self) -> u32 {
self.count
}
#[must_use]
pub fn data(&self) -> &[u8] {
&self.data
}
#[must_use]
pub fn indices(&self) -> &[u32] {
self.addressing.indices()
}
}
#[cfg(test)]
impl From<Tessellation> for TessellationWire {
fn from(tessellation: Tessellation) -> Self {
Self {
id: tessellation.id,
body: tessellation.body,
faces: tessellation.faces,
chordal_deflection: tessellation.chordal_deflection.map(NonNegativeReal::get),
source_object: tessellation.source_object,
mesh: tessellation.mesh.into_raw(),
feature_edges: tessellation.feature_edges,
triangle_groups: tessellation.triangle_groups,
texture_assignments: tessellation.texture_assignments,
channels: tessellation.channels,
}
}
}
impl TryFrom<TessellationWire> for Tessellation {
type Error = TessellationError;
fn try_from(wire: TessellationWire) -> Result<Self, Self::Error> {
let mut mesh = Self {
body: wire.body,
faces: wire.faces,
source_object: wire.source_object,
..Self::new(wire.id, wire.mesh, wire.channels)?
};
mesh.set_chordal_deflection(wire.chordal_deflection)?;
mesh = mesh.with_feature_edges(wire.feature_edges)?;
mesh = mesh.with_triangle_groups(wire.triangle_groups)?;
mesh.with_texture_assignments(wire.texture_assignments)
}
}
impl TryFrom<TessellationChannelWire> for TessellationChannel {
type Error = TessellationError;
fn try_from(wire: TessellationChannelWire) -> Result<Self, Self::Error> {
Self::new(
wire.addressing,
wire.item_size,
wire.kind,
wire.flags,
wire.data,
)
}
}
#[cfg(feature = "schema")]
impl JsonSchema for Tessellation {
fn schema_name() -> std::borrow::Cow<'static, str> {
"Tessellation".into()
}
fn schema_id() -> std::borrow::Cow<'static, str> {
concat!(module_path!(), "::Tessellation").into()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
TessellationWire::json_schema(generator)
}
}
#[cfg(feature = "schema")]
impl JsonSchema for TessellationChannel {
fn schema_name() -> std::borrow::Cow<'static, str> {
"TessellationChannel".into()
}
fn schema_id() -> std::borrow::Cow<'static, str> {
concat!(module_path!(), "::TessellationChannel").into()
}
fn json_schema(generator: &mut schemars::SchemaGenerator) -> schemars::Schema {
TessellationChannelWire::json_schema(generator)
}
}
#[cfg(test)]
mod tests;
cadmpeg_core::named_optional_field!(deserialize_body, BodyId, "body");
cadmpeg_core::named_optional_field!(deserialize_chordal_deflection, f64, "chordal_deflection");
cadmpeg_core::named_optional_field!(
deserialize_source_object,
SourceObjectAssociation,
"source_object"
);
cadmpeg_core::named_optional_field!(deserialize_source_id, String, "source_id");
mod identity_rewrite;
mod serialization;