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
335 lines
11 KiB
Rust
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)
|
|
);
|
|
}
|