Snapshots show appearances: the CPU renderer colours bodies by their appearance (or imported colour) and faces with looks of their own, as the viewport does
Fixes #4 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT
This commit is contained in:
1 parent
3a416ccb7d
commit
a00153fc99
3 files changed
+49
-6
No files matched your search
@@ -239,7 +239,7 @@ fn icon_png(size: usize) -> Result<Vec<u8>, String> {
|
||||
let px = size as f64 / 256.0;
|
||||
for body in &s.world_state().bodies {
|
||||
let mesh = body.mesh();
|
||||
scene.meshes.push(SceneMesh { mesh: std::sync::Arc::clone(&mesh), color: Rgb(90, 160, 240) });
|
||||
scene.meshes.push(SceneMesh { mesh: std::sync::Arc::clone(&mesh), color: Rgb(90, 160, 240), face_colors: Vec::new() });
|
||||
for (i, e) in mesh.edges.iter().enumerate() {
|
||||
if !mesh.seams.get(i).copied().unwrap_or(false) {
|
||||
scene.lines.push(SceneLine { points: e.clone(), color: Rgb(24, 52, 96), width: (3.0 * px).max(0.8), on_top: false });
|
||||
|
||||
@@ -142,7 +142,17 @@ pub fn scene(s: &Session, cam: &Camera) -> Scene {
|
||||
for body in &st.bodies {
|
||||
let selected = s.selection.iter().any(|x| matches!(x, Sel::Body { name } if *name == body.name));
|
||||
let mesh = body.mesh();
|
||||
sc.meshes.push(SceneMesh { mesh: Arc::clone(&mesh), color: if selected { colors::BODY_SELECTED } else { colors::BODY } });
|
||||
// The body's appearance (or imported colour) and faces with looks of their own, as in
|
||||
// the viewport.
|
||||
let to8 = |x: f32| (x.clamp(0.0, 1.0) * 255.0).round() as u8;
|
||||
let color =
|
||||
if selected { colors::BODY_SELECTED } else { s.doc.body_color(body).map_or(colors::BODY, |[r, g, b]| Rgb(to8(r), to8(g), to8(b))) };
|
||||
let face_colors = if selected {
|
||||
Vec::new()
|
||||
} else {
|
||||
s.doc.face_colors(body).into_iter().map(|(f, l)| (f as u32, Rgb(l.color[0], l.color[1], l.color[2]))).collect()
|
||||
};
|
||||
sc.meshes.push(SceneMesh { mesh: Arc::clone(&mesh), color, face_colors });
|
||||
for (i, e) in mesh.edges.iter().enumerate() {
|
||||
if mesh.seams.get(i).copied().unwrap_or(false) {
|
||||
continue;
|
||||
@@ -236,3 +246,28 @@ fn constructs(s: &Session) -> Vec<(u64, String, solvecraft_doc::construct::Const
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// The CPU renderer (snapshots) shows appearances as the viewport does: a body's colour and
|
||||
/// faces with looks of their own.
|
||||
#[test]
|
||||
fn snapshot_scene_carries_appearances() {
|
||||
let mut s = Session::default();
|
||||
s.execute("solid.box", &json!({"length": 40, "width": 30, "height": 20})).unwrap();
|
||||
s.execute("solid.cylinder", &json!({"diameter": 14, "height": 30, "base": [50, 15, 0], "operation": "new"})).unwrap();
|
||||
s.execute("appearance.assign", &json!({"bodies": ["Body2"], "color": [220, 60, 40]})).unwrap();
|
||||
s.execute("appearance.assign", &json!({"faces": [[20, 15, 20]], "body": "Body1", "color": [40, 170, 80]})).unwrap();
|
||||
let cam = home_camera(&s);
|
||||
let sc = scene(&s, &cam);
|
||||
assert!(sc.meshes.iter().any(|m| m.color == Rgb(220, 60, 40)));
|
||||
assert!(sc.meshes.iter().any(|m| m.face_colors.iter().any(|(_, c)| *c == Rgb(40, 170, 80))));
|
||||
// And the rendered picture has the green top.
|
||||
let img = solvecraft_render::render(&sc, &cam, 200, 140);
|
||||
assert!(img.rgba.chunks(4).any(|p| p[1] > 120 && p[0] < 90 && p[2] < 110), "no green pixels");
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@ impl Rgb {
|
||||
pub struct SceneMesh {
|
||||
pub mesh: Arc<Mesh>,
|
||||
pub color: Rgb,
|
||||
/// Faces in colours of their own: (B-rep face index, colour).
|
||||
pub face_colors: Vec<(u32, Rgb)>,
|
||||
}
|
||||
|
||||
pub struct SceneLine {
|
||||
@@ -102,9 +104,11 @@ pub fn render(scene: &Scene, cam: &Camera, w: usize, h: usize) -> Canvas {
|
||||
let view_dir = cam.back();
|
||||
for sm in &scene.meshes {
|
||||
let m = &sm.mesh;
|
||||
let base = sm.color.f();
|
||||
let body = sm.color.f();
|
||||
let pts: Vec<Option<[f64; 3]>> = m.positions.iter().map(|p| project(*p)).collect();
|
||||
for t in &m.triangles {
|
||||
for (ti, t) in m.triangles.iter().enumerate() {
|
||||
let face = m.tri_face.get(ti).copied();
|
||||
let base = sm.face_colors.iter().find(|(f, _)| Some(*f) == face).map_or(body, |(_, c)| c.f());
|
||||
let (Some(Some(a)), Some(Some(b)), Some(Some(cc))) = (pts.get(t[0] as usize), pts.get(t[1] as usize), pts.get(t[2] as usize)) else {
|
||||
continue;
|
||||
};
|
||||
@@ -248,8 +252,12 @@ mod tests {
|
||||
seams: vec![],
|
||||
edge_faces: vec![],
|
||||
};
|
||||
let scene =
|
||||
Scene { meshes: vec![SceneMesh { mesh: Arc::new(mesh), color: Rgb(200, 30, 30) }], lines: vec![], background: None, radius: 20.0 };
|
||||
let scene = Scene {
|
||||
meshes: vec![SceneMesh { mesh: Arc::new(mesh), color: Rgb(200, 30, 30), face_colors: Vec::new() }],
|
||||
lines: vec![],
|
||||
background: None,
|
||||
radius: 20.0,
|
||||
};
|
||||
let mut cam = Camera { distance: 50.0, ..Default::default() };
|
||||
cam.set_view(StandardView::Front);
|
||||
let c = render(&scene, &cam, 64, 64);
|
||||
|
||||
Reference in new issue
Block a user