Files
MetaCrate/tests/compat/tests/rendering_shims.rs
Chili Palmer 611db567a0
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
Implement native SimpleRenderer reference pipeline (#76)
2026-08-10 21:50:59 +00:00

335 lines
11 KiB
Rust

#![allow(clippy::float_cmp, clippy::too_many_lines)]
use libremetaverse::rendering::{DetailLevel, IRendering, SimpleMesh, Vertex};
use libremetaverse::{
MappingType, Primitive, PrimitiveConstructionData, PrimitiveSculptData, PrimitiveTextureEntry,
PrimitiveTextureEntryFace,
};
use libremetaverse_imaging::{ManagedImage, ManagedImageImageChannels};
use libremetaverse_prim_mesher::PrimMesh;
use libremetaverse_rendering_mesh_foundry::MeshFoundry;
use libremetaverse_rendering_simple::SimpleRenderer;
use libremetaverse_types::{
Error, HoleType, PCode, PathCurve, ProfileCurve, SculptType, UUID, Vector2, Vector3,
};
fn uuid(value: &str) -> UUID {
UUID::new_with_string(value.into()).expect("fixture UUID")
}
fn primitive(profile: ProfileCurve, path: PathCurve, path_scale_y: f32) -> Primitive {
let mut primitive = Primitive::new_with_constructor().expect("Primitive constructor");
let mut data =
PrimitiveConstructionData::new_with_constructor().expect("ConstructionData constructor");
data.p_code = PCode::Prim;
data.path_curve = path;
data.set_profile_curve_with_property(profile);
data.path_scale_x = 1.0;
data.path_scale_y = path_scale_y;
data.path_begin = 0.0;
data.path_end = 1.0;
data.profile_begin = 0.0;
data.profile_end = 1.0;
data.profile_hollow = 0.0;
data.set_profile_hole(HoleType::Same);
data.path_revolutions = 1.0;
primitive.prim_data = data;
primitive
}
fn box_primitive() -> Primitive {
primitive(ProfileCurve::Square, PathCurve::Line, 1.0)
}
fn mesh_hash(mesh: &SimpleMesh) -> u64 {
let mut hash = 0xcbf2_9ce4_8422_2325_u64;
let mut mix = |value: u32| {
hash ^= u64::from(value);
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
};
mix(u32::try_from(mesh.vertices.len()).expect("fixture vertex count"));
mix(u32::try_from(mesh.indices.len()).expect("fixture index count"));
for vertex in &mesh.vertices {
for value in [
vertex.position.x,
vertex.position.y,
vertex.position.z,
vertex.normal.x,
vertex.normal.y,
vertex.normal.z,
vertex.tex_coord.x,
vertex.tex_coord.y,
] {
mix(value.to_bits());
}
}
for index in &mesh.indices {
mix(u32::from(*index));
}
hash
}
fn assert_valid_mesh(mesh: &SimpleMesh) {
assert!(!mesh.vertices.is_empty());
assert!(!mesh.indices.is_empty());
assert_eq!(mesh.indices.len() % 3, 0);
for index in &mesh.indices {
assert!(usize::from(*index) < mesh.vertices.len());
}
for vertex in &mesh.vertices {
for value in [
vertex.position.x,
vertex.position.y,
vertex.position.z,
vertex.normal.x,
vertex.normal.y,
vertex.normal.z,
vertex.tex_coord.x,
vertex.tex_coord.y,
] {
assert!(value.is_finite());
}
}
}
fn sculpt_image() -> ManagedImage {
let mut image = ManagedImage::new(8, 8, ManagedImageImageChannels::COLOR)
.expect("sculpt image constructor");
for index in 0_u8..64 {
image.red[usize::from(index)] = index.wrapping_mul(3);
image.green[usize::from(index)] = index.wrapping_mul(5);
image.blue[usize::from(index)] = index.wrapping_mul(7);
}
image
}
fn vertex(x: f32, y: f32, normal: Vector3, position: Vector3) -> Vertex {
Vertex {
position,
normal,
tex_coord: Vector2 { x, y },
}
}
#[test]
fn mesh_and_renderer_calls_compile_with_typed_elements() {
let _: fn(i32, f32, f32, f32, i32) -> Result<PrimMesh, libremetaverse_prim_mesher::Error> =
PrimMesh::new;
let _: fn(
&SimpleRenderer,
Primitive,
DetailLevel,
) -> Result<SimpleMesh, libremetaverse_rendering_simple::Error> =
SimpleRenderer::generate_simple_mesh;
let _: fn(
&MeshFoundry,
Primitive,
DetailLevel,
) -> Result<Option<SimpleMesh>, libremetaverse_rendering_mesh_foundry::Error> =
MeshFoundry::generate_simple_mesh;
}
#[test]
fn simple_renderer_box_pipeline_is_deterministic_and_matches_golden_mesh() {
let renderer = SimpleRenderer::new().expect("SimpleRenderer constructor");
let faceted = renderer
.generate_faceted_mesh(box_primitive(), DetailLevel::High)
.expect("faceted box");
assert_eq!(faceted.faces.len(), 6);
assert_eq!(
faceted
.faces
.iter()
.map(|face| face.vertices.len())
.sum::<usize>(),
24
);
assert_eq!(
faceted
.faces
.iter()
.map(|face| face.indices.len())
.sum::<usize>(),
36
);
for face in &faceted.faces {
assert!(face.min_extent.x <= face.max_extent.x);
assert!(face.min_extent.y <= face.max_extent.y);
assert!(face.min_extent.z <= face.max_extent.z);
}
let first = renderer
.generate_simple_mesh(box_primitive(), DetailLevel::High)
.expect("first simple box");
let second = renderer
.generate_simple_mesh(box_primitive(), DetailLevel::High)
.expect("second simple box");
assert_valid_mesh(&first);
assert_eq!(first.vertices.len(), 24);
assert_eq!(first.indices.len(), 36);
assert_eq!(mesh_hash(&first), mesh_hash(&second));
assert_eq!(mesh_hash(&first), 0x3d2b_10e3_7da0_3d2e);
}
#[test]
fn simple_renderer_maps_default_and_per_face_texture_metadata() {
let default_texture = uuid("11111111-1111-1111-1111-111111111111");
let override_texture = uuid("22222222-2222-2222-2222-222222222222");
let material = uuid("33333333-3333-3333-3333-333333333333");
let render_material = uuid("44444444-4444-4444-4444-444444444444");
let mut textures =
PrimitiveTextureEntry::new_with_uuid(default_texture).expect("texture entry constructor");
let face = textures.create_face(0).expect("face override");
face.set_texture_id(override_texture);
face.set_material_id(material);
face.set_render_material_id(render_material);
let mut prim = box_primitive();
prim.textures = Some(textures);
let mesh = SimpleRenderer::new()
.expect("renderer")
.generate_faceted_mesh(prim, DetailLevel::Medium)
.expect("textured box");
assert_eq!(mesh.faces[0].texture_face.texture_id(), override_texture);
assert_eq!(mesh.faces[0].texture_face.material_id(), material);
assert_eq!(
mesh.faces[0].texture_face.render_material_id(),
render_material
);
assert_eq!(mesh.faces[1].texture_face.texture_id(), default_texture);
}
#[test]
fn simple_renderer_transforms_default_and_planar_texture_coordinates() {
let renderer = SimpleRenderer::new().expect("renderer");
let mut face = PrimitiveTextureEntryFace::new(None).expect("texture face");
face.set_repeat_u(2.0);
face.set_repeat_v(3.0);
face.set_offset_u(0.1);
face.set_offset_v(-0.2);
face.set_rotation(0.0);
face.set_tex_map_type(MappingType::Default);
let mut vertices = vec![vertex(0.25, 0.75, Vector3::unit_z(), Vector3::zero())];
renderer
.transform_tex_coords(
&mut vertices,
Vector3::zero(),
face,
Vector3 {
x: 1.0,
y: 1.0,
z: 1.0,
},
)
.expect("default texture transform");
assert!((vertices[0].tex_coord.x - 0.1).abs() < 1.0e-6);
assert!((vertices[0].tex_coord.y - 1.05).abs() < 1.0e-6);
let mut planar = PrimitiveTextureEntryFace::new(None).expect("planar face");
planar.set_tex_map_type(MappingType::Planar);
let mut vertices = vec![vertex(
99.0,
99.0,
Vector3::unit_z(),
Vector3 {
x: 0.25,
y: -0.25,
z: 0.5,
},
)];
renderer
.transform_tex_coords(
&mut vertices,
Vector3::zero(),
planar,
Vector3 {
x: 2.0,
y: 4.0,
z: 1.0,
},
)
.expect("planar texture transform");
assert!((vertices[0].tex_coord.x - 1.5).abs() < 1.0e-6);
assert!((vertices[0].tex_coord.y - -1.5).abs() < 1.0e-6);
}
#[test]
fn simple_renderer_generates_deterministic_sculpt_geometry() {
let mut prim = box_primitive();
let mut sculpt = PrimitiveSculptData::new_with_constructor().expect("sculpt metadata");
sculpt.set_type_(SculptType::Plane);
prim.sculpt = Some(sculpt);
let renderer = SimpleRenderer::new().expect("renderer");
let first = renderer
.generate_simple_sculpt_mesh(prim.clone(), sculpt_image(), DetailLevel::Low)
.expect("first sculpt mesh");
let second = renderer
.generate_simple_sculpt_mesh(prim, sculpt_image(), DetailLevel::Low)
.expect("second sculpt mesh");
assert_valid_mesh(&first);
assert_eq!(mesh_hash(&first), mesh_hash(&second));
}
#[test]
fn simple_renderer_trait_dispatch_returns_geometry() {
let renderer = SimpleRenderer::new().expect("renderer");
let mesh = IRendering::generate_simple_mesh(&renderer, box_primitive(), DetailLevel::Low)
.expect("trait call")
.expect("renderer returned geometry");
assert_valid_mesh(&mesh);
}
#[test]
fn simple_renderer_reports_source_context_for_invalid_and_mesh_sculpt_inputs() {
let renderer = SimpleRenderer::new().expect("renderer");
let source = uuid("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee");
let mut invalid = box_primitive();
invalid.id = source;
invalid.prim_data.path_end = f32::NAN;
assert!(matches!(
renderer.generate_simple_mesh(invalid, DetailLevel::High),
Err(Error::Rendering {
source: actual,
context: "non-finite primitive construction data"
}) if actual == source
));
let mut mesh_sculpt = box_primitive();
mesh_sculpt.id = source;
let mut sculpt = PrimitiveSculptData::new_with_constructor().expect("sculpt metadata");
sculpt.set_type_(SculptType::Mesh);
mesh_sculpt.sculpt = Some(sculpt);
assert!(matches!(
renderer.generate_simple_sculpt_mesh(
mesh_sculpt,
sculpt_image(),
DetailLevel::High,
),
Err(Error::Rendering {
source: actual,
context: "mesh sculpt requires a decoded mesh asset"
}) if actual == source
));
}
#[test]
fn simple_renderer_rejects_non_finite_texture_inputs() {
let renderer = SimpleRenderer::new().expect("renderer");
let mut face = PrimitiveTextureEntryFace::new(None).expect("texture face");
face.set_repeat_u(f32::INFINITY);
let mut vertices = vec![vertex(0.0, 0.0, Vector3::unit_z(), Vector3::zero())];
assert_eq!(
renderer.transform_tex_coords(
&mut vertices,
Vector3::zero(),
face,
Vector3 {
x: 1.0,
y: 1.0,
z: 1.0
},
),
Err(Error::Argument)
);
}