Another project
1use std::path::PathBuf;
2
3use bone_document::{EditOutcome, Sketch, SketchEdit, SketchEntity};
4use bone_render::{
5 Camera2, PixelDiff, PixelDiffThreshold, PixelsPerMm, SketchRenderer, SketchScene, Style,
6 decode_png, encode_png,
7};
8use bone_types::{Length, Point2, Point3, SketchEntityId, SketchPlaneBasis, Tolerance, UnitVec3};
9use uom::si::length::millimeter;
10
11mod common;
12
13use common::{extent_square as extent, make_context};
14
15const GOLDEN: &str = "tests/goldens/arc_circle_zoom100_256.png";
16const UPDATE_ENV: &str = "BONE_UPDATE_ARC_GOLDEN";
17const DIFF_TOLERANCE: f64 = 16.0 / 255.0;
18
19fn golden_path() -> PathBuf {
20 PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(GOLDEN)
21}
22
23fn plane() -> SketchPlaneBasis {
24 let Ok(basis) = SketchPlaneBasis::new(
25 Point3::origin(),
26 UnitVec3::x_axis(),
27 UnitVec3::y_axis(),
28 Tolerance::new(1e-9),
29 ) else {
30 panic!("xy plane basis is orthogonal");
31 };
32 basis
33}
34
35fn add_point(s: Sketch, x: f64, y: f64) -> (Sketch, SketchEntityId) {
36 let Ok((next, EditOutcome::Entity(id))) = s.apply(SketchEdit::AddEntity(SketchEntity::point(
37 Point2::from_mm(x, y),
38 ))) else {
39 panic!("add point");
40 };
41 (next, id)
42}
43
44fn add_circle(s: Sketch, center: SketchEntityId, radius_mm: f64) -> Sketch {
45 let Ok((next, _)) = s.apply(SketchEdit::AddEntity(SketchEntity::circle(
46 center,
47 Length::new::<millimeter>(radius_mm),
48 false,
49 ))) else {
50 panic!("add circle");
51 };
52 next
53}
54
55fn add_arc(
56 s: Sketch,
57 center: SketchEntityId,
58 start: SketchEntityId,
59 end: SketchEntityId,
60) -> Sketch {
61 let Ok((next, _)) = s.apply(SketchEdit::AddEntity(SketchEntity::arc(
62 center, start, end, false,
63 ))) else {
64 panic!("add arc");
65 };
66 next
67}
68
69fn arc_circle_scene() -> SketchScene {
70 let s = Sketch::new(plane());
71 let (s, circle_c) = add_point(s, -0.7, 0.0);
72 let s = add_circle(s, circle_c, 0.5);
73
74 let (s, arc_c) = add_point(s, 0.7, 0.0);
75 let (s, arc_start) = add_point(s, 1.2, 0.0);
76 let (s, arc_end) = add_point(s, 0.7, 0.5);
77 let s = add_arc(s, arc_c, arc_start, arc_end);
78
79 let Ok(scene) = SketchScene::extract(&s) else {
80 panic!("scene extract");
81 };
82 scene
83}
84
85#[test]
86fn arc_circle_at_100x_zoom_matches_golden() {
87 let size = extent(256);
88 let ctx = make_context(size);
89 let mut renderer = SketchRenderer::new(ctx.gpu(), ctx.color_format());
90 let scene = arc_circle_scene();
91 let camera = Camera2::new(size).with_zoom(PixelsPerMm::new(100.0));
92 let style = Style::default();
93
94 let Ok(frame) = renderer.render(&ctx, &scene, camera, &style) else {
95 panic!("SketchRenderer::render failed");
96 };
97
98 let path = golden_path();
99
100 if std::env::var(UPDATE_ENV).is_ok() {
101 let Ok(bytes) = encode_png(&frame) else {
102 panic!("encode_png failed");
103 };
104 if let Some(parent) = path.parent()
105 && let Err(e) = std::fs::create_dir_all(parent)
106 {
107 panic!("create goldens dir {}: {e}", parent.display());
108 }
109 if let Err(e) = std::fs::write(&path, &bytes) {
110 panic!("write golden {}: {e}", path.display());
111 }
112 return;
113 }
114
115 let Ok(bytes) = std::fs::read(&path) else {
116 panic!(
117 "golden missing at {}: rerun with {UPDATE_ENV}=1 to generate",
118 path.display()
119 );
120 };
121 let Ok((golden_extent, golden_rgba)) = decode_png(&bytes) else {
122 panic!("failed to decode golden PNG");
123 };
124 assert_eq!(golden_extent, size, "golden extent drift");
125 let threshold = PixelDiffThreshold::new(DIFF_TOLERANCE);
126 let Ok(report) = PixelDiff::compare(&frame, &golden_rgba, threshold) else {
127 panic!("PixelDiff rejected inputs");
128 };
129 assert!(
130 report.is_clean(),
131 "arc render drifted from golden: {} mismatches, worst {:?}, backend {}",
132 report.over_threshold(),
133 report.worst(),
134 frame.backend(),
135 );
136}