Another project
0

Configure Feed

Select the types of activity you want to include in your feed.

test(render): cube cam golden

Lewis: May this revision serve well! <lu5a@proton.me>

author
Lewis
date (Jun 5, 2026, 7:26 PM +0300) commit 78f1eb90 parent 35cf22df change-id ryrztylu
+147
+147
crates/bone-render/tests/cube_camera.rs
··· 1 + use std::path::PathBuf; 2 + 3 + use bone_kernel::{ 4 + BrepSolid, Curve2Kind, ExtrudeDirection, ExtrudeEndCondition, ExtrudeFeature, ExtrudeProfile, 5 + ExtrudeSense, Line2, MergeResult, ProfileEdge, ProfileLoop, evaluate_extrude, 6 + }; 7 + use bone_render::{ 8 + PixelDiff, PixelDiffThreshold, SolidRenderer, SolidScene, Style, decode_png, encode_png, 9 + frame_isometric, 10 + }; 11 + use bone_types::{ 12 + AngleTolerance, ChordHeightTolerance, FeatureId, Length, Plane3, Point2, Point3, 13 + PositiveLength, SketchEntityId, SketchId, Tolerance, UnitVec3, millimeter, 14 + }; 15 + use slotmap::{Key, SlotMap}; 16 + 17 + mod common; 18 + 19 + use common::{extent_square as extent, make_context}; 20 + 21 + const GOLDEN: &str = "tests/goldens/cube_iso_256.png"; 22 + const UPDATE_ENV: &str = "BONE_UPDATE_CUBE_ISO_GOLDEN"; 23 + const DIFF_TOLERANCE: f64 = 16.0 / 255.0; 24 + const TOLERANCE: Tolerance = Tolerance::new(1.0e-9); 25 + 26 + fn golden_path() -> PathBuf { 27 + PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(GOLDEN) 28 + } 29 + 30 + fn unit_cube() -> BrepSolid { 31 + let mut entities: SlotMap<SketchEntityId, ()> = SlotMap::with_key(); 32 + let mut features: SlotMap<FeatureId, ()> = SlotMap::with_key(); 33 + let Ok(plane) = Plane3::new( 34 + Point3::origin(), 35 + UnitVec3::x_axis(), 36 + UnitVec3::y_axis(), 37 + TOLERANCE, 38 + ) else { 39 + panic!("x and y axes are orthonormal"); 40 + }; 41 + let corners = [ 42 + Point2::from_mm(0.0, 0.0), 43 + Point2::from_mm(1.0, 0.0), 44 + Point2::from_mm(1.0, 1.0), 45 + Point2::from_mm(0.0, 1.0), 46 + ]; 47 + let edges = (0..4) 48 + .map(|index| { 49 + let start = corners[index]; 50 + let end = corners[(index + 1) % 4]; 51 + let Ok(segment) = Line2::new(start, end, TOLERANCE) else { 52 + panic!("rectangle endpoints are distinct"); 53 + }; 54 + ProfileEdge::new( 55 + Curve2Kind::Line(segment), 56 + entities.insert(()), 57 + entities.insert(()), 58 + ) 59 + }) 60 + .collect(); 61 + let profile = ExtrudeProfile::new(plane, vec![ProfileLoop::Open(edges)]); 62 + let Ok(depth) = PositiveLength::new(Length::new::<millimeter>(1.0)) else { 63 + panic!("1 mm is a positive length"); 64 + }; 65 + let feature = ExtrudeFeature { 66 + sketch: SketchId::null(), 67 + direction: ExtrudeDirection::Normal { 68 + sense: ExtrudeSense::Forward, 69 + }, 70 + end_condition: ExtrudeEndCondition::Blind { depth }, 71 + draft: None, 72 + thin_wall: None, 73 + merge_result: MergeResult::Merge, 74 + }; 75 + let Ok(solid) = evaluate_extrude(features.insert(()), &profile, &feature) else { 76 + panic!("the unit square extrudes into a cube"); 77 + }; 78 + solid 79 + } 80 + 81 + #[test] 82 + fn cube_iso_matches_golden() { 83 + let size = extent(256); 84 + let ctx = make_context(size); 85 + let mut renderer = SolidRenderer::new(ctx.gpu(), ctx.color_format()); 86 + let solid = unit_cube(); 87 + let Ok(mesh) = solid.tessellate( 88 + ChordHeightTolerance::from_mm(0.05), 89 + AngleTolerance::from_radians(0.2), 90 + ) else { 91 + panic!("the cube tessellates"); 92 + }; 93 + let scene = SolidScene::from_mesh(&mesh); 94 + let Some(aabb) = solid.bounding_box() else { 95 + panic!("the cube has a bounding box"); 96 + }; 97 + let Ok(camera) = frame_isometric(aabb, size) else { 98 + panic!("the cube frames isometrically"); 99 + }; 100 + let style = Style::default(); 101 + 102 + let Ok(frame) = renderer.render(&ctx, &scene, camera, &style) else { 103 + panic!("SolidRenderer::render failed"); 104 + }; 105 + 106 + let path = golden_path(); 107 + 108 + if std::env::var(UPDATE_ENV).is_ok() { 109 + let Ok(bytes) = encode_png(&frame) else { 110 + panic!("encode_png failed"); 111 + }; 112 + if let Some(parent) = path.parent() { 113 + assert!( 114 + std::fs::create_dir_all(parent).is_ok(), 115 + "failed to create goldens dir" 116 + ); 117 + } 118 + assert!( 119 + std::fs::write(&path, &bytes).is_ok(), 120 + "failed to write golden {}", 121 + path.display() 122 + ); 123 + return; 124 + } 125 + 126 + let Ok(bytes) = std::fs::read(&path) else { 127 + panic!( 128 + "golden missing at {}: rerun with {UPDATE_ENV}=1 to generate", 129 + path.display() 130 + ); 131 + }; 132 + let Ok((golden_extent, golden_rgba)) = decode_png(&bytes) else { 133 + panic!("failed to decode golden PNG"); 134 + }; 135 + assert_eq!(golden_extent, size, "golden extent drift"); 136 + let threshold = PixelDiffThreshold::new(DIFF_TOLERANCE); 137 + let Ok(report) = PixelDiff::compare(&frame, &golden_rgba, threshold) else { 138 + panic!("PixelDiff rejected inputs"); 139 + }; 140 + assert!( 141 + report.is_clean(), 142 + "cube render drifted from golden: {} mismatches, worst {:?}, backend {}", 143 + report.over_threshold(), 144 + report.worst(), 145 + frame.backend(), 146 + ); 147 + }
crates/bone-render/tests/goldens/cube_iso_256.png

This is a binary file and will not be displayed.