//! Integration contracts for the data consumed by the future rendering pipeline. use libremetaverse_imaging::{ManagedImage, ManagedImageImageChannels}; use libremetaverse_prim_mesher::{ PathType, PrimMesh, SculptMesh, SculptMeshSculptType, ViewerPolygon, ViewerVertex, }; fn assert_rendering_vertices(vertices: &[ViewerVertex], polygons: &[ViewerPolygon]) { assert!(vertices.len() <= usize::from(u16::MAX) + 1); for vertex in vertices { assert!(vertex.v.x.is_finite() && vertex.v.y.is_finite() && vertex.v.z.is_finite()); assert!(vertex.n.x.is_finite() && vertex.n.y.is_finite() && vertex.n.z.is_finite()); assert!(vertex.uv.u.is_finite() && vertex.uv.v.is_finite()); } for polygon in polygons { for index in [polygon.v1, polygon.v2, polygon.v3] { let index = usize::try_from(index).expect("non-negative viewer index"); assert!(index < vertices.len()); u16::try_from(index).expect("MeshFoundry-facing index fits u16"); } } } #[test] fn prim_mesh_viewer_groups_are_rendering_ready() { let mut mesh = PrimMesh::new(24, 0.05, 0.95, 0.25, 12).expect("checked profile"); mesh.viewer_mode = true; mesh.calc_vertex_normals = true; mesh.path_cut_begin = 0.03; mesh.path_cut_end = 0.96; mesh.twist_begin = -30; mesh.twist_end = 120; mesh.taper_x = 0.1; mesh.skew = 0.15; mesh.revolutions = 1.25; mesh.steps_per_revolution = 48; mesh.extrude(PathType::Circular) .expect("checked viewer extrusion"); let indexer = mesh .get_vertex_indexer() .expect("checked viewer indexing") .expect("viewer mode produces an indexer"); assert_eq!(indexer.num_prim_faces, mesh.num_prim_faces); assert_eq!(indexer.viewer_vertices.len(), indexer.viewer_polygons.len()); for (vertices, polygons) in indexer .viewer_vertices .iter() .zip(indexer.viewer_polygons.iter()) { assert_rendering_vertices( vertices, polygons .as_deref() .expect("allocated prim-face polygon group"), ); } } #[test] fn sculpt_lods_and_topologies_keep_rendering_indices_and_attributes_aligned() { let mut image = ManagedImage::new(128, 128, ManagedImageImageChannels::COLOR) .expect("bounded sculpt fixture"); for index in 0..image.red.len() { image.red[index] = u8::try_from(index & 0xff).expect("masked sample"); image.green[index] = u8::try_from((index / 128) & 0xff).expect("masked sample"); image.blue[index] = u8::try_from((index * 17) & 0xff).expect("masked sample"); } for lod in [8, 16, 32] { for topology in [ SculptMeshSculptType::Plane, SculptMeshSculptType::Sphere, SculptMeshSculptType::Torus, SculptMeshSculptType::Cylinder, ] { let mesh = SculptMesh::new_with_managed_image_sculpt_type_int32_boolean_boolean_boolean( image.clone().expect("independent image fixture"), topology, lod, true, true, false, ) .expect("checked sculpt mesh"); assert_eq!(mesh.coords.len(), mesh.normals.len()); assert_eq!(mesh.coords.len(), mesh.uvs.len()); assert_eq!(mesh.faces.len(), mesh.viewer_faces.len()); assert!(mesh.coords.len() <= usize::from(u16::MAX) + 1); for face in &mesh.faces { for index in [face.v1, face.v2, face.v3] { let index = usize::try_from(index).expect("non-negative sculpt index"); assert!(index < mesh.coords.len()); u16::try_from(index).expect("MeshFoundry-facing sculpt index fits u16"); } } for viewer in &mesh.viewer_faces { for coord in [viewer.v1, viewer.v2, viewer.v3] { assert!(coord.x.is_finite() && coord.y.is_finite() && coord.z.is_finite()); } for normal in [viewer.n1, viewer.n2, viewer.n3] { assert!(normal.x.is_finite() && normal.y.is_finite() && normal.z.is_finite()); } for uv in [viewer.uv1, viewer.uv2, viewer.uv3] { assert!(uv.u.is_finite() && uv.v.is_finite()); } } } } }