Implement native SimpleRenderer reference pipeline (#76)
Some checks failed
Native code generation / deterministic (push) Failing after 2m7s
Imaging and meshing gate / native (push) Failing after 4m9s
JPEG 2000 feature / linux (push) Successful in 2m55s
Native Rust workspace compile / compile (push) Failing after 6m15s
Skia feature / linux (push) Successful in 30m59s
Some checks failed
Native code generation / deterministic (push) Failing after 2m7s
Imaging and meshing gate / native (push) Failing after 4m9s
JPEG 2000 feature / linux (push) Successful in 2m55s
Native Rust workspace compile / compile (push) Failing after 6m15s
Skia feature / linux (push) Successful in 30m59s
This commit is contained in:
665
crates/libremetaverse-rendering-simple/src/simple_renderer.rs
Normal file
665
crates/libremetaverse-rendering-simple/src/simple_renderer.rs
Normal file
@@ -0,0 +1,665 @@
|
||||
//! Deterministic reference renderer built on the checked native prim mesher.
|
||||
|
||||
#![allow(clippy::cast_possible_truncation)] // Checked C# float-to-int twist conversion.
|
||||
#![allow(clippy::missing_errors_doc)] // Public signatures are fixed by the API mapping.
|
||||
#![allow(clippy::must_use_candidate)] // Attributes are not part of the mapped surface.
|
||||
#![allow(clippy::needless_pass_by_value)] // Mapped value parameters are owned.
|
||||
#![allow(clippy::unnecessary_wraps)] // The mapped constructor is fallible in the C# API.
|
||||
#![allow(clippy::unused_self)] // IRendering exposes these operations as instance methods.
|
||||
|
||||
use libremetaverse::rendering::{
|
||||
DetailLevel, Face, FaceMask, FacetedMesh, IRendering, SimpleMesh, Vertex,
|
||||
};
|
||||
use libremetaverse::{MappingType, Primitive, PrimitiveTextureEntryFace};
|
||||
use libremetaverse_imaging::ManagedImage;
|
||||
use libremetaverse_prim_mesher::{
|
||||
PathType, PrimMesh, SculptMesh, SculptMeshSculptType, VertexIndexer,
|
||||
};
|
||||
use libremetaverse_types::compat::Object;
|
||||
use libremetaverse_types::{
|
||||
Error, HoleType, PathCurve, ProfileCurve, SculptType, Vector2, Vector3,
|
||||
};
|
||||
|
||||
const MAX_FACE_VERTICES: usize = 65_536;
|
||||
const MAX_TOTAL_VERTICES: usize = MAX_FACE_VERTICES;
|
||||
const MAX_TOTAL_INDICES: usize = MAX_TOTAL_VERTICES * 6;
|
||||
|
||||
/// Small, deterministic renderer for prim and sculpt geometry.
|
||||
pub struct SimpleRenderer;
|
||||
|
||||
impl SimpleRenderer {
|
||||
pub(crate) fn native_new() -> Result<Self, Error> {
|
||||
Ok(Self)
|
||||
}
|
||||
|
||||
pub(crate) fn native_generate_simple_mesh(
|
||||
&self,
|
||||
prim: Primitive,
|
||||
lod: DetailLevel,
|
||||
) -> Result<SimpleMesh, Error> {
|
||||
let source = prim.id;
|
||||
let faceted = self.native_generate_faceted_mesh(prim, lod)?;
|
||||
flatten_faces(faceted.faces, source)
|
||||
}
|
||||
|
||||
pub(crate) fn native_generate_simple_sculpt_mesh(
|
||||
&self,
|
||||
prim: Primitive,
|
||||
sculpt_texture: ManagedImage,
|
||||
lod: DetailLevel,
|
||||
) -> Result<SimpleMesh, Error> {
|
||||
let source = prim.id;
|
||||
let faceted = self.native_generate_faceted_sculpt_mesh(prim, sculpt_texture, lod)?;
|
||||
flatten_faces(faceted.faces, source)
|
||||
}
|
||||
|
||||
pub(crate) fn native_generate_faceted_mesh(
|
||||
&self,
|
||||
prim: Primitive,
|
||||
lod: DetailLevel,
|
||||
) -> Result<FacetedMesh, Error> {
|
||||
let source = prim.id;
|
||||
let mesh = generate_prim_mesh(&prim, lod, source)?;
|
||||
let indexer = mesh
|
||||
.get_vertex_indexer()
|
||||
.map_err(|_| render_error(source, "index primitive viewer faces"))?
|
||||
.ok_or_else(|| render_error(source, "primitive viewer faces are unavailable"))?;
|
||||
let faces = indexed_faces(&prim, indexer, source)?;
|
||||
Ok(FacetedMesh {
|
||||
faces,
|
||||
skin_data: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn native_generate_faceted_sculpt_mesh(
|
||||
&self,
|
||||
prim: Primitive,
|
||||
sculpt_texture: ManagedImage,
|
||||
lod: DetailLevel,
|
||||
) -> Result<FacetedMesh, Error> {
|
||||
let source = prim.id;
|
||||
let sculpt = prim
|
||||
.sculpt
|
||||
.as_ref()
|
||||
.ok_or_else(|| render_error(source, "primitive has no sculpt metadata"))?;
|
||||
let sculpt_type = match sculpt.type_() {
|
||||
SculptType::Cylinder => SculptMeshSculptType::Cylinder,
|
||||
SculptType::Plane => SculptMeshSculptType::Plane,
|
||||
SculptType::Sphere => SculptMeshSculptType::Sphere,
|
||||
SculptType::Torus => SculptMeshSculptType::Torus,
|
||||
SculptType::Mesh => {
|
||||
return Err(render_error(
|
||||
source,
|
||||
"mesh sculpt requires a decoded mesh asset",
|
||||
));
|
||||
}
|
||||
SculptType::None | SculptType::Invert | SculptType::Mirror => {
|
||||
return Err(render_error(source, "unsupported sculpt topology"));
|
||||
}
|
||||
};
|
||||
let mesher_lod = match lod {
|
||||
DetailLevel::Highest | DetailLevel::High => 32,
|
||||
DetailLevel::Medium => 16,
|
||||
DetailLevel::Low => 8,
|
||||
};
|
||||
let mesh = SculptMesh::new_with_managed_image_sculpt_type_int32_boolean_boolean_boolean(
|
||||
sculpt_texture,
|
||||
sculpt_type,
|
||||
mesher_lod,
|
||||
true,
|
||||
sculpt.mirror(),
|
||||
sculpt.invert(),
|
||||
)
|
||||
.map_err(|_| render_error(source, "generate bounded sculpt geometry"))?;
|
||||
if mesh.coords.len() > MAX_FACE_VERTICES
|
||||
|| mesh.coords.len() != mesh.normals.len()
|
||||
|| mesh.coords.len() != mesh.uvs.len()
|
||||
{
|
||||
return Err(render_error(source, "invalid sculpt vertex domains"));
|
||||
}
|
||||
let vertices = mesh
|
||||
.coords
|
||||
.iter()
|
||||
.zip(&mesh.normals)
|
||||
.zip(&mesh.uvs)
|
||||
.map(|((position, normal), uv)| {
|
||||
checked_vertex(
|
||||
Vector3 {
|
||||
x: position.x,
|
||||
y: position.y,
|
||||
z: position.z,
|
||||
},
|
||||
Vector3 {
|
||||
x: normal.x,
|
||||
y: normal.y,
|
||||
z: normal.z,
|
||||
},
|
||||
Vector2 { x: uv.u, y: uv.v },
|
||||
source,
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let mut indices = Vec::with_capacity(
|
||||
mesh.faces
|
||||
.len()
|
||||
.checked_mul(3)
|
||||
.ok_or_else(|| render_error(source, "sculpt index count overflow"))?,
|
||||
);
|
||||
for face in mesh.faces {
|
||||
push_triangle(
|
||||
&mut indices,
|
||||
face.v1,
|
||||
face.v2,
|
||||
face.v3,
|
||||
vertices.len(),
|
||||
source,
|
||||
)?;
|
||||
}
|
||||
let texture = texture_for_face(&prim, 0, source)?;
|
||||
let face = make_face(0, vertices, indices, texture, source)?;
|
||||
Ok(FacetedMesh {
|
||||
faces: vec![face],
|
||||
skin_data: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn native_transform_tex_coords(
|
||||
&self,
|
||||
vertices: &mut Vec<Vertex>,
|
||||
center: Vector3,
|
||||
te_face: PrimitiveTextureEntryFace,
|
||||
prim_scale: Vector3,
|
||||
) -> Result<(), Error> {
|
||||
if !finite3(center)
|
||||
|| !finite3(prim_scale)
|
||||
|| ![
|
||||
te_face.repeat_u(),
|
||||
te_face.repeat_v(),
|
||||
te_face.offset_u(),
|
||||
te_face.offset_v(),
|
||||
te_face.rotation(),
|
||||
]
|
||||
.into_iter()
|
||||
.all(f32::is_finite)
|
||||
{
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
let cosine = te_face.rotation().cos();
|
||||
let sine = te_face.rotation().sin();
|
||||
for vertex in vertices {
|
||||
if !finite3(vertex.position) || !finite3(vertex.normal) || !finite2(vertex.tex_coord) {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
if te_face.tex_map_type() == MappingType::Planar {
|
||||
let mut binormal;
|
||||
let normal_x = vertex.normal.x;
|
||||
if (-0.5..0.5).contains(&normal_x) {
|
||||
binormal = Vector3::unit_x();
|
||||
if vertex.normal.y > 0.0 {
|
||||
binormal.x = -1.0;
|
||||
}
|
||||
} else {
|
||||
binormal = Vector3::unit_y();
|
||||
if normal_x < 0.0 {
|
||||
binormal.y = -1.0;
|
||||
}
|
||||
}
|
||||
let tangent = cross(binormal, vertex.normal);
|
||||
let scaled = Vector3 {
|
||||
x: vertex.position.x * prim_scale.x,
|
||||
y: vertex.position.y * prim_scale.y,
|
||||
z: vertex.position.z * prim_scale.z,
|
||||
};
|
||||
vertex.tex_coord.x = 0.5 + dot(binormal, scaled) * 2.0;
|
||||
vertex.tex_coord.y = 0.5 - dot(tangent, scaled) * 2.0;
|
||||
}
|
||||
let x = vertex.tex_coord.x - 0.5;
|
||||
let y = vertex.tex_coord.y - 0.5;
|
||||
vertex.tex_coord.x =
|
||||
(x * cosine + y * sine) * te_face.repeat_u() + te_face.offset_u() + 0.5;
|
||||
vertex.tex_coord.y =
|
||||
(-x * sine + y * cosine) * te_face.repeat_v() + te_face.offset_v() + 0.5;
|
||||
if !finite2(vertex.tex_coord) {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl IRendering for SimpleRenderer {
|
||||
fn generate_faceted_mesh(
|
||||
&self,
|
||||
prim: Primitive,
|
||||
lod: DetailLevel,
|
||||
) -> Result<Option<FacetedMesh>, Error> {
|
||||
self.native_generate_faceted_mesh(prim, lod).map(Some)
|
||||
}
|
||||
|
||||
fn generate_faceted_sculpt_mesh(
|
||||
&self,
|
||||
prim: Primitive,
|
||||
sculpt_texture: ManagedImage,
|
||||
lod: DetailLevel,
|
||||
) -> Result<Option<FacetedMesh>, Error> {
|
||||
self.native_generate_faceted_sculpt_mesh(prim, sculpt_texture, lod)
|
||||
.map(Some)
|
||||
}
|
||||
|
||||
fn generate_simple_mesh(
|
||||
&self,
|
||||
prim: Primitive,
|
||||
lod: DetailLevel,
|
||||
) -> Result<Option<SimpleMesh>, Error> {
|
||||
self.native_generate_simple_mesh(prim, lod).map(Some)
|
||||
}
|
||||
|
||||
fn generate_simple_sculpt_mesh(
|
||||
&self,
|
||||
prim: Primitive,
|
||||
sculpt_texture: ManagedImage,
|
||||
lod: DetailLevel,
|
||||
) -> Result<Option<SimpleMesh>, Error> {
|
||||
self.native_generate_simple_sculpt_mesh(prim, sculpt_texture, lod)
|
||||
.map(Some)
|
||||
}
|
||||
|
||||
fn transform_tex_coords(
|
||||
&self,
|
||||
vertices: &mut Vec<Vertex>,
|
||||
center: Vector3,
|
||||
te_face: PrimitiveTextureEntryFace,
|
||||
prim_scale: Vector3,
|
||||
) -> Result<(), Error> {
|
||||
self.native_transform_tex_coords(vertices, center, te_face, prim_scale)
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_prim_mesh(
|
||||
prim: &Primitive,
|
||||
lod: DetailLevel,
|
||||
source: libremetaverse_types::UUID,
|
||||
) -> Result<PrimMesh, Error> {
|
||||
let data = &prim.prim_data;
|
||||
let parameters = [
|
||||
data.profile_begin,
|
||||
data.profile_end,
|
||||
data.profile_hollow,
|
||||
data.path_scale_x,
|
||||
data.path_scale_y,
|
||||
data.path_begin,
|
||||
data.path_end,
|
||||
data.path_shear_x,
|
||||
data.path_shear_y,
|
||||
data.path_radius_offset,
|
||||
data.path_revolutions,
|
||||
data.path_skew,
|
||||
data.path_taper_x,
|
||||
data.path_taper_y,
|
||||
data.path_twist_begin,
|
||||
data.path_twist,
|
||||
];
|
||||
if !parameters.into_iter().all(f32::is_finite) {
|
||||
return Err(render_error(
|
||||
source,
|
||||
"non-finite primitive construction data",
|
||||
));
|
||||
}
|
||||
let (mut sides, mut profile_begin, mut profile_end, sphere_mode) =
|
||||
match data.profile_curve_with_property() {
|
||||
ProfileCurve::Circle => (lod_sides(lod), data.profile_begin, data.profile_end, false),
|
||||
ProfileCurve::EqualTriangle
|
||||
| ProfileCurve::IsoTriangle
|
||||
| ProfileCurve::RightTriangle => (3, data.profile_begin, data.profile_end, false),
|
||||
ProfileCurve::HalfCircle => (
|
||||
lod_sides(lod),
|
||||
data.profile_begin.mul_add(0.5, 0.5),
|
||||
data.profile_end.mul_add(0.5, 0.5),
|
||||
true,
|
||||
),
|
||||
ProfileCurve::Square => (4, data.profile_begin, data.profile_end, false),
|
||||
};
|
||||
if sides < 3 {
|
||||
sides = 3;
|
||||
}
|
||||
profile_begin = profile_begin.clamp(0.0, 1.0);
|
||||
profile_end = profile_end.clamp(0.0, 1.0);
|
||||
let hollow_sides = if data.profile_hole() == HoleType::Circle {
|
||||
lod_sides(lod)
|
||||
} else if data.profile_hole() == HoleType::Triangle {
|
||||
3
|
||||
} else if data.profile_hole() == HoleType::Same {
|
||||
sides
|
||||
} else {
|
||||
4
|
||||
};
|
||||
let mut mesh = PrimMesh::new(
|
||||
sides,
|
||||
profile_begin,
|
||||
profile_end,
|
||||
data.profile_hollow,
|
||||
hollow_sides,
|
||||
)
|
||||
.map_err(|_| render_error(source, "initialize primitive profile"))?;
|
||||
mesh.viewer_mode = true;
|
||||
mesh.sphere_mode = sphere_mode;
|
||||
mesh.hole_size_x = data.path_scale_x;
|
||||
mesh.hole_size_y = data.path_scale_y;
|
||||
mesh.path_cut_begin = data.path_begin;
|
||||
mesh.path_cut_end = data.path_end;
|
||||
mesh.top_shear_x = data.path_shear_x;
|
||||
mesh.top_shear_y = data.path_shear_y;
|
||||
mesh.radius = data.path_radius_offset;
|
||||
mesh.revolutions = data.path_revolutions;
|
||||
mesh.skew = data.path_skew;
|
||||
mesh.steps_per_revolution = lod_sides(lod);
|
||||
let path_type = if matches!(data.path_curve, PathCurve::Line | PathCurve::Flexible) {
|
||||
mesh.taper_x = 1.0 - data.path_scale_x;
|
||||
mesh.taper_y = 1.0 - data.path_scale_y;
|
||||
mesh.twist_begin = checked_twist(data.path_twist_begin, 180.0, source)?;
|
||||
mesh.twist_end = checked_twist(data.path_twist, 180.0, source)?;
|
||||
PathType::Linear
|
||||
} else {
|
||||
mesh.taper_x = data.path_taper_x;
|
||||
mesh.taper_y = data.path_taper_y;
|
||||
mesh.twist_begin = checked_twist(data.path_twist_begin, 360.0, source)?;
|
||||
mesh.twist_end = checked_twist(data.path_twist, 360.0, source)?;
|
||||
PathType::Circular
|
||||
};
|
||||
mesh.extrude(path_type)
|
||||
.map_err(|_| render_error(source, "extrude primitive geometry"))?;
|
||||
if mesh.coords.len() > MAX_TOTAL_VERTICES || mesh.viewer_faces.len() > MAX_TOTAL_INDICES / 3 {
|
||||
return Err(render_error(
|
||||
source,
|
||||
"primitive geometry exceeds renderer bounds",
|
||||
));
|
||||
}
|
||||
Ok(mesh)
|
||||
}
|
||||
|
||||
fn indexed_faces(
|
||||
prim: &Primitive,
|
||||
indexer: VertexIndexer,
|
||||
source: libremetaverse_types::UUID,
|
||||
) -> Result<Vec<Face>, Error> {
|
||||
let count = usize::try_from(indexer.num_prim_faces)
|
||||
.map_err(|_| render_error(source, "invalid primitive face count"))?;
|
||||
if count != indexer.viewer_vertices.len() || count != indexer.viewer_polygons.len() {
|
||||
return Err(render_error(source, "misaligned primitive face domains"));
|
||||
}
|
||||
let mut total_vertices = 0usize;
|
||||
let mut total_indices = 0usize;
|
||||
let mut faces = Vec::with_capacity(count);
|
||||
for face_index in 0..count {
|
||||
let source_vertices = &indexer.viewer_vertices[face_index];
|
||||
let polygons = indexer.viewer_polygons[face_index]
|
||||
.as_ref()
|
||||
.ok_or_else(|| render_error(source, "primitive face has no polygon domain"))?;
|
||||
if source_vertices.is_empty() {
|
||||
if polygons.is_empty() {
|
||||
continue;
|
||||
}
|
||||
return Err(render_error(
|
||||
source,
|
||||
"primitive face polygons have no vertex domain",
|
||||
));
|
||||
}
|
||||
if source_vertices.len() > MAX_FACE_VERTICES {
|
||||
return Err(render_error(
|
||||
source,
|
||||
"primitive face exceeds 16-bit vertex range",
|
||||
));
|
||||
}
|
||||
total_vertices = total_vertices
|
||||
.checked_add(source_vertices.len())
|
||||
.ok_or_else(|| render_error(source, "primitive vertex count overflow"))?;
|
||||
if total_vertices > MAX_TOTAL_VERTICES {
|
||||
return Err(render_error(source, "primitive vertex budget exceeded"));
|
||||
}
|
||||
let vertices = source_vertices
|
||||
.iter()
|
||||
.map(|vertex| {
|
||||
checked_vertex(
|
||||
Vector3 {
|
||||
x: vertex.v.x,
|
||||
y: vertex.v.y,
|
||||
z: vertex.v.z,
|
||||
},
|
||||
Vector3 {
|
||||
x: vertex.n.x,
|
||||
y: vertex.n.y,
|
||||
z: vertex.n.z,
|
||||
},
|
||||
Vector2 {
|
||||
x: vertex.uv.u,
|
||||
y: 1.0 - vertex.uv.v,
|
||||
},
|
||||
source,
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
let mut indices = Vec::with_capacity(
|
||||
polygons
|
||||
.len()
|
||||
.checked_mul(3)
|
||||
.ok_or_else(|| render_error(source, "primitive index count overflow"))?,
|
||||
);
|
||||
for polygon in polygons {
|
||||
if polygon.v1 == polygon.v2 || polygon.v1 == polygon.v3 || polygon.v2 == polygon.v3 {
|
||||
continue;
|
||||
}
|
||||
push_triangle(
|
||||
&mut indices,
|
||||
polygon.v1,
|
||||
polygon.v2,
|
||||
polygon.v3,
|
||||
vertices.len(),
|
||||
source,
|
||||
)?;
|
||||
}
|
||||
total_indices = total_indices
|
||||
.checked_add(indices.len())
|
||||
.ok_or_else(|| render_error(source, "primitive index count overflow"))?;
|
||||
if total_indices > MAX_TOTAL_INDICES {
|
||||
return Err(render_error(source, "primitive index budget exceeded"));
|
||||
}
|
||||
let texture = texture_for_face(prim, face_index, source)?;
|
||||
faces.push(make_face(
|
||||
i32::try_from(face_index)
|
||||
.map_err(|_| render_error(source, "primitive face id overflow"))?,
|
||||
vertices,
|
||||
indices,
|
||||
texture,
|
||||
source,
|
||||
)?);
|
||||
}
|
||||
Ok(faces)
|
||||
}
|
||||
|
||||
fn flatten_faces(
|
||||
faces: Vec<Face>,
|
||||
source: libremetaverse_types::UUID,
|
||||
) -> Result<SimpleMesh, Error> {
|
||||
let mut vertices = Vec::new();
|
||||
let mut indices = Vec::new();
|
||||
for face in faces {
|
||||
let base = vertices.len();
|
||||
let combined = base
|
||||
.checked_add(face.vertices.len())
|
||||
.ok_or_else(|| render_error(source, "simple mesh vertex count overflow"))?;
|
||||
if combined > MAX_TOTAL_VERTICES {
|
||||
return Err(render_error(
|
||||
source,
|
||||
"simple mesh exceeds 16-bit vertex range",
|
||||
));
|
||||
}
|
||||
for index in face.indices {
|
||||
let global = base
|
||||
.checked_add(usize::from(index))
|
||||
.ok_or_else(|| render_error(source, "simple mesh index overflow"))?;
|
||||
if global >= combined {
|
||||
return Err(render_error(source, "simple mesh index is out of range"));
|
||||
}
|
||||
indices.push(
|
||||
u16::try_from(global)
|
||||
.map_err(|_| render_error(source, "simple mesh index exceeds 16 bits"))?,
|
||||
);
|
||||
}
|
||||
vertices.extend(face.vertices);
|
||||
}
|
||||
if indices.len() > MAX_TOTAL_INDICES {
|
||||
return Err(render_error(source, "simple mesh index budget exceeded"));
|
||||
}
|
||||
Ok(SimpleMesh { indices, vertices })
|
||||
}
|
||||
|
||||
fn texture_for_face(
|
||||
prim: &Primitive,
|
||||
index: usize,
|
||||
source: libremetaverse_types::UUID,
|
||||
) -> Result<PrimitiveTextureEntryFace, Error> {
|
||||
let Some(textures) = prim.textures.as_ref() else {
|
||||
return Ok(PrimitiveTextureEntryFace::default());
|
||||
};
|
||||
let index =
|
||||
u32::try_from(index).map_err(|_| render_error(source, "texture face index overflow"))?;
|
||||
textures
|
||||
.get_face(index)
|
||||
.map_err(|_| render_error(source, "texture face index is out of range"))
|
||||
.map(|face| face.cloned().unwrap_or_default())
|
||||
}
|
||||
|
||||
fn make_face(
|
||||
id: i32,
|
||||
vertices: Vec<Vertex>,
|
||||
indices: Vec<u16>,
|
||||
texture_face: PrimitiveTextureEntryFace,
|
||||
source: libremetaverse_types::UUID,
|
||||
) -> Result<Face, Error> {
|
||||
let first = vertices
|
||||
.first()
|
||||
.ok_or_else(|| render_error(source, "render face has no vertices"))?;
|
||||
let mut min = first.position;
|
||||
let mut max = first.position;
|
||||
for vertex in &vertices[1..] {
|
||||
min.x = min.x.min(vertex.position.x);
|
||||
min.y = min.y.min(vertex.position.y);
|
||||
min.z = min.z.min(vertex.position.z);
|
||||
max.x = max.x.max(vertex.position.x);
|
||||
max.y = max.y.max(vertex.position.y);
|
||||
max.z = max.z.max(vertex.position.z);
|
||||
}
|
||||
let center = Vector3 {
|
||||
x: (min.x + max.x) * 0.5,
|
||||
y: (min.y + max.y) * 0.5,
|
||||
z: (min.z + max.z) * 0.5,
|
||||
};
|
||||
Ok(Face {
|
||||
begin_s: 0,
|
||||
begin_t: 0,
|
||||
center,
|
||||
edge: Vec::new(),
|
||||
id,
|
||||
indices,
|
||||
mask: FaceMask::SINGLE,
|
||||
max_extent: max,
|
||||
min_extent: min,
|
||||
normalized_scale: Vector3 {
|
||||
x: max.x - min.x,
|
||||
y: max.y - min.y,
|
||||
z: max.z - min.z,
|
||||
},
|
||||
num_s: 0,
|
||||
num_t: 0,
|
||||
tex_coords1: None,
|
||||
texture_face,
|
||||
user_data: Object::Undefined,
|
||||
vertices,
|
||||
weights: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn checked_vertex(
|
||||
position: Vector3,
|
||||
normal: Vector3,
|
||||
tex_coord: Vector2,
|
||||
source: libremetaverse_types::UUID,
|
||||
) -> Result<Vertex, Error> {
|
||||
if !finite3(position) || !finite3(normal) || !finite2(tex_coord) {
|
||||
return Err(render_error(
|
||||
source,
|
||||
"renderer produced a non-finite vertex",
|
||||
));
|
||||
}
|
||||
Ok(Vertex {
|
||||
position,
|
||||
normal,
|
||||
tex_coord,
|
||||
})
|
||||
}
|
||||
|
||||
fn push_triangle(
|
||||
indices: &mut Vec<u16>,
|
||||
v1: i32,
|
||||
v2: i32,
|
||||
v3: i32,
|
||||
vertex_count: usize,
|
||||
source: libremetaverse_types::UUID,
|
||||
) -> Result<(), Error> {
|
||||
for index in [v1, v2, v3] {
|
||||
let index =
|
||||
usize::try_from(index).map_err(|_| render_error(source, "negative renderer index"))?;
|
||||
if index >= vertex_count {
|
||||
return Err(render_error(source, "renderer index is out of range"));
|
||||
}
|
||||
indices.push(
|
||||
u16::try_from(index)
|
||||
.map_err(|_| render_error(source, "renderer index exceeds 16 bits"))?,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn checked_twist(
|
||||
value: f32,
|
||||
multiplier: f32,
|
||||
source: libremetaverse_types::UUID,
|
||||
) -> Result<i32, Error> {
|
||||
let degrees = value * multiplier;
|
||||
if !degrees.is_finite() || degrees.abs() > 360_000.0 {
|
||||
return Err(render_error(source, "primitive twist is out of range"));
|
||||
}
|
||||
Ok(degrees as i32)
|
||||
}
|
||||
|
||||
const fn lod_sides(lod: DetailLevel) -> i32 {
|
||||
match lod {
|
||||
DetailLevel::Low => 6,
|
||||
DetailLevel::Medium => 12,
|
||||
DetailLevel::High | DetailLevel::Highest => 24,
|
||||
}
|
||||
}
|
||||
|
||||
const fn render_error(source: libremetaverse_types::UUID, context: &'static str) -> Error {
|
||||
Error::Rendering { source, context }
|
||||
}
|
||||
|
||||
const fn finite2(value: Vector2) -> bool {
|
||||
value.x.is_finite() && value.y.is_finite()
|
||||
}
|
||||
|
||||
const fn finite3(value: Vector3) -> bool {
|
||||
value.x.is_finite() && value.y.is_finite() && value.z.is_finite()
|
||||
}
|
||||
|
||||
const fn dot(left: Vector3, right: Vector3) -> f32 {
|
||||
left.x * right.x + left.y * right.y + left.z * right.z
|
||||
}
|
||||
|
||||
const fn cross(left: Vector3, right: Vector3) -> Vector3 {
|
||||
Vector3 {
|
||||
x: left.y * right.z - left.z * right.y,
|
||||
y: left.z * right.x - left.x * right.z,
|
||||
z: left.x * right.y - left.y * right.x,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user