Another project
0

Configure Feed

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

feat(document,render): midpoint stuff w/ solver residual and glyph

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

author
Lewis
date (May 9, 2026, 3:40 PM +0300) commit a1a66dd9 parent 338ff00a change-id slrpmxzw
+243 -82
+84
crates/bone-document/src/sketch/mod.rs
··· 269 269 matches!(self.kind_of(a)?, K::Arc | K::Circle) 270 270 && matches!(self.kind_of(b)?, K::Arc | K::Circle) 271 271 } 272 + SketchRelation::Midpoint { point, line } => match self.require_entity(line)? { 273 + SketchEntity::Line(l) => { 274 + self.kind_of(point)? == K::Point && point != l.a() && point != l.b() 275 + } 276 + _ => false, 277 + }, 272 278 SketchRelation::Fix(a) => { 273 279 self.kind_of(a)?; 274 280 true ··· 1339 1345 assert!(matches!( 1340 1346 bad, 1341 1347 Err(SketchEditError::SelfReferencingRelation(_)) 1348 + )); 1349 + } 1350 + 1351 + fn line_with_third_point(s: Sketch, a: SketchEntityId, b: SketchEntityId) -> (Sketch, SketchEntityId, SketchEntityId) { 1352 + let Ok((s, EditOutcome::Entity(line))) = 1353 + s.apply(SketchEdit::AddEntity(SketchEntity::line(a, b, false))) 1354 + else { 1355 + panic!("line"); 1356 + }; 1357 + let Ok((s, EditOutcome::Entity(p))) = s.apply(SketchEdit::AddEntity(SketchEntity::point( 1358 + Point2::from_mm(0.0, 1.0), 1359 + ))) else { 1360 + panic!("midpoint"); 1361 + }; 1362 + (s, line, p) 1363 + } 1364 + 1365 + #[test] 1366 + fn midpoint_relation_accepts_distinct_point() { 1367 + let (s, a, b) = two_points(); 1368 + let (s, line, mid) = line_with_third_point(s, a, b); 1369 + let Ok((s2, _)) = s.apply(SketchEdit::AddRelation(SketchRelation::Midpoint { 1370 + point: mid, 1371 + line, 1372 + })) else { 1373 + panic!("midpoint relation"); 1374 + }; 1375 + assert_eq!(s2.relations().len(), 1); 1376 + } 1377 + 1378 + #[test] 1379 + fn midpoint_relation_rejects_endpoint_a_as_point() { 1380 + let (s, a, b) = two_points(); 1381 + let (s, line, _) = line_with_third_point(s, a, b); 1382 + let bad = s.apply(SketchEdit::AddRelation(SketchRelation::Midpoint { 1383 + point: a, 1384 + line, 1385 + })); 1386 + assert!(matches!( 1387 + bad, 1388 + Err(SketchEditError::InvalidRelationOperands(_)) 1389 + )); 1390 + } 1391 + 1392 + #[test] 1393 + fn midpoint_relation_rejects_endpoint_b_as_point() { 1394 + let (s, a, b) = two_points(); 1395 + let (s, line, _) = line_with_third_point(s, a, b); 1396 + let bad = s.apply(SketchEdit::AddRelation(SketchRelation::Midpoint { 1397 + point: b, 1398 + line, 1399 + })); 1400 + assert!(matches!( 1401 + bad, 1402 + Err(SketchEditError::InvalidRelationOperands(_)) 1403 + )); 1404 + } 1405 + 1406 + #[test] 1407 + fn midpoint_relation_rejects_non_line_target() { 1408 + let (s, a, _) = two_points(); 1409 + let Ok((s, EditOutcome::Entity(circle))) = s.apply(SketchEdit::AddEntity( 1410 + SketchEntity::circle(a, len_mm(1.0), false), 1411 + )) else { 1412 + panic!("circle"); 1413 + }; 1414 + let Ok((s, EditOutcome::Entity(p))) = s.apply(SketchEdit::AddEntity(SketchEntity::point( 1415 + Point2::from_mm(2.0, 0.0), 1416 + ))) else { 1417 + panic!("p"); 1418 + }; 1419 + let bad = s.apply(SketchEdit::AddRelation(SketchRelation::Midpoint { 1420 + point: p, 1421 + line: circle, 1422 + })); 1423 + assert!(matches!( 1424 + bad, 1425 + Err(SketchEditError::InvalidRelationOperands(_)) 1342 1426 )); 1343 1427 } 1344 1428
+6
crates/bone-document/src/sketch/relation.rs
··· 11 11 Tangent(SketchEntityId, SketchEntityId), 12 12 Equal(SketchEntityId, SketchEntityId), 13 13 Concentric(SketchEntityId, SketchEntityId), 14 + Midpoint { 15 + point: SketchEntityId, 16 + line: SketchEntityId, 17 + }, 14 18 Fix(SketchEntityId), 15 19 } 16 20 ··· 24 28 | Self::Tangent(a, b) 25 29 | Self::Equal(a, b) 26 30 | Self::Concentric(a, b) => RelationRefs([Some(a), Some(b)]), 31 + Self::Midpoint { point, line } => RelationRefs([Some(point), Some(line)]), 27 32 Self::Horizontal(a) | Self::Vertical(a) | Self::Fix(a) => RelationRefs([Some(a), None]), 28 33 } 29 34 } ··· 37 42 | Self::Tangent(a, b) 38 43 | Self::Equal(a, b) 39 44 | Self::Concentric(a, b) => Some((a, b)), 45 + Self::Midpoint { point, line } => Some((point, line)), 40 46 Self::Horizontal(_) | Self::Vertical(_) | Self::Fix(_) => None, 41 47 } 42 48 }
+16
crates/bone-document/src/sketch/solve.rs
··· 388 388 SketchRelation::Tangent(a, b) => lower_tangent(sketch, a, b, points, radii), 389 389 SketchRelation::Equal(a, b) => lower_equal(sketch, a, b, points, radii), 390 390 SketchRelation::Concentric(a, b) => lower_concentric(sketch, a, b, points), 391 + SketchRelation::Midpoint { point, line } => lower_midpoint(sketch, point, line, points), 391 392 SketchRelation::Fix(id) => lower_fix(sketch, id, points, radii, parameters), 392 393 } 393 394 } ··· 504 505 _ => Vec::new(), 505 506 }, 506 507 _ => unreachable!("Equal validation guarantees matching-kind operands"), 508 + } 509 + } 510 + 511 + fn lower_midpoint( 512 + sketch: &Sketch, 513 + point_id: SketchEntityId, 514 + line_id: SketchEntityId, 515 + points: &BTreeMap<SketchEntityId, PointHandle>, 516 + ) -> Vec<Residual> { 517 + match ( 518 + points.get(&point_id).copied(), 519 + line_handle(sketch, line_id, points), 520 + ) { 521 + (Some(point), Some(line)) => vec![Residual::MidpointPointLine { point, line }], 522 + _ => Vec::new(), 507 523 } 508 524 } 509 525
+11
crates/bone-document/tests/folder_property.rs
··· 91 91 ai: u8, 92 92 bi: u8, 93 93 }, 94 + Midpoint { 95 + pi: u8, 96 + li: u8, 97 + }, 94 98 Fix { 95 99 ei: u8, 96 100 }, ··· 144 148 (any::<u8>(), any::<u8>()).prop_map(|(ai, bi)| Step::EqualLines { ai, bi }), 145 149 (any::<u8>(), any::<u8>()).prop_map(|(ai, bi)| Step::EqualRounds { ai, bi }), 146 150 (any::<u8>(), any::<u8>()).prop_map(|(ai, bi)| Step::Concentric { ai, bi }), 151 + (any::<u8>(), any::<u8>()).prop_map(|(pi, li)| Step::Midpoint { pi, li }), 147 152 any::<u8>().prop_map(|ei| Step::Fix { ei }), 148 153 (any::<u8>(), any::<u8>(), any::<u16>(), any::<bool>()) 149 154 .prop_map(|(ai, bi, v, driven)| Step::LinearDim { ai, bi, v, driven }), ··· 232 237 | Step::EqualLines { .. } 233 238 | Step::EqualRounds { .. } 234 239 | Step::Concentric { .. } 240 + | Step::Midpoint { .. } 235 241 | Step::Fix { .. } => resolve_relation(s, step), 236 242 Step::LinearDim { .. } 237 243 | Step::RadiusDim { .. } ··· 334 340 Step::Concentric { ai, bi } => { 335 341 let (a, b) = pick_two_distinct(&rounds(s), ai, bi)?; 336 342 SketchRelation::Concentric(a, b) 343 + } 344 + Step::Midpoint { pi, li } => { 345 + let point = pick(&entities_of_kind(s, SketchEntityKind::Point), pi)?; 346 + let line = pick(&entities_of_kind(s, SketchEntityKind::Line), li)?; 347 + SketchRelation::Midpoint { point, line } 337 348 } 338 349 Step::Fix { ei } => SketchRelation::Fix(pick(s.entity_order(), ei)?), 339 350 _ => unreachable!("caller routes only relations here"),
+36 -59
crates/bone-document/tests/folder_roundtrip.rs
··· 4 4 from_str, load, save, to_string, 5 5 }; 6 6 use bone_types::{ 7 - Angle, DocumentId, Length, Point2, Point3, SketchId, SketchPlaneBasis, Tolerance, UnitVec3, 8 - degree, millimeter, 7 + Angle, DocumentId, Length, Point2, Point3, SketchEntityId, SketchId, SketchPlaneBasis, 8 + Tolerance, UnitVec3, degree, millimeter, 9 9 }; 10 10 use slotmap::{Key, KeyData}; 11 11 use tempfile::{TempDir, tempdir}; ··· 103 103 assert_eq!(loaded.parameters().get("width"), Some(10.0)); 104 104 } 105 105 106 - #[test] 107 - fn rich_sketch_with_every_variant_roundtrips() { 108 - let dir = ok_dir(); 109 - let folder = DocumentFolder::new(dir.path().join("rich.bone")); 110 - 111 - let mut s = Sketch::new(plane()); 112 - let Ok((s_next, EditOutcome::Entity(p0))) = s.apply(SketchEdit::AddEntity( 113 - SketchEntity::point(Point2::from_mm(0.0, 0.0)), 114 - )) else { 115 - panic!("p0"); 116 - }; 117 - s = s_next; 118 - let Ok((s_next, EditOutcome::Entity(p1))) = s.apply(SketchEdit::AddEntity( 119 - SketchEntity::point(Point2::from_mm(10.0, 0.0)), 120 - )) else { 121 - panic!("p1"); 122 - }; 123 - s = s_next; 124 - let Ok((s_next, EditOutcome::Entity(p2))) = s.apply(SketchEdit::AddEntity( 125 - SketchEntity::point(Point2::from_mm(5.0, 6.0)), 126 - )) else { 127 - panic!("p2"); 106 + fn add_entity_returning_id(s: Sketch, entity: SketchEntity) -> (Sketch, SketchEntityId) { 107 + let Ok((next, EditOutcome::Entity(id))) = s.apply(SketchEdit::AddEntity(entity)) else { 108 + panic!("add entity {entity:?}"); 128 109 }; 129 - s = s_next; 110 + (next, id) 111 + } 130 112 131 - let Ok((s_next, EditOutcome::Entity(line))) = 132 - s.apply(SketchEdit::AddEntity(SketchEntity::line(p0, p1, false))) 133 - else { 134 - panic!("line"); 135 - }; 136 - s = s_next; 137 - let Ok((s_next, EditOutcome::Entity(line2))) = 138 - s.apply(SketchEdit::AddEntity(SketchEntity::line(p0, p2, true))) 139 - else { 140 - panic!("line2"); 141 - }; 142 - s = s_next; 143 - let Ok((s_next, EditOutcome::Entity(arc))) = 144 - s.apply(SketchEdit::AddEntity(SketchEntity::arc(p0, p1, p2, false))) 145 - else { 146 - panic!("arc"); 147 - }; 148 - s = s_next; 149 - let Ok((s_next, EditOutcome::Entity(circle))) = s.apply(SketchEdit::AddEntity( 150 - SketchEntity::circle(p0, mm(3.0), false), 151 - )) else { 152 - panic!("circle"); 153 - }; 154 - s = s_next; 155 - 156 - for relation in [ 113 + fn rich_sketch_with_every_variant() -> Sketch { 114 + let s = Sketch::new(plane()); 115 + let (s, p0) = add_entity_returning_id(s, SketchEntity::point(Point2::from_mm(0.0, 0.0))); 116 + let (s, p1) = add_entity_returning_id(s, SketchEntity::point(Point2::from_mm(10.0, 0.0))); 117 + let (s, p2) = add_entity_returning_id(s, SketchEntity::point(Point2::from_mm(5.0, 6.0))); 118 + let (s, line) = add_entity_returning_id(s, SketchEntity::line(p0, p1, false)); 119 + let (s, line2) = add_entity_returning_id(s, SketchEntity::line(p0, p2, true)); 120 + let (s, arc) = add_entity_returning_id(s, SketchEntity::arc(p0, p1, p2, false)); 121 + let (s, circle) = add_entity_returning_id(s, SketchEntity::circle(p0, mm(3.0), false)); 122 + let relations = [ 157 123 SketchRelation::Coincident(p0, line), 158 124 SketchRelation::Horizontal(line), 159 125 SketchRelation::Vertical(line2), ··· 162 128 SketchRelation::Tangent(line, circle), 163 129 SketchRelation::Equal(line, line2), 164 130 SketchRelation::Concentric(arc, circle), 131 + SketchRelation::Midpoint { 132 + point: p2, 133 + line, 134 + }, 165 135 SketchRelation::Fix(p0), 166 - ] { 136 + ]; 137 + let s = relations.into_iter().fold(s, |s, relation| { 167 138 let Ok((next, _)) = s.apply(SketchEdit::AddRelation(relation)) else { 168 139 panic!("relation {relation:?}"); 169 140 }; 170 - s = next; 171 - } 172 - 173 - for dim in [ 141 + next 142 + }); 143 + let dimensions = [ 174 144 SketchDimension::Linear { 175 145 a: p0, 176 146 b: p1, ··· 193 163 value: deg(45.0), 194 164 kind: bone_document::DimensionKind::Driving, 195 165 }, 196 - ] { 166 + ]; 167 + dimensions.into_iter().fold(s, |s, dim| { 197 168 let Ok((next, _)) = s.apply(SketchEdit::AddDimension(dim)) else { 198 169 panic!("dim {dim:?}"); 199 170 }; 200 - s = next; 201 - } 171 + next 172 + }) 173 + } 202 174 175 + #[test] 176 + fn rich_sketch_with_every_variant_roundtrips() { 177 + let dir = ok_dir(); 178 + let folder = DocumentFolder::new(dir.path().join("rich.bone")); 179 + let s = rich_sketch_with_every_variant(); 203 180 let mut doc = Document::new(document_id(1), "rich".to_owned()); 204 181 doc.insert_sketch(sketch_id(1), "Rich".to_owned(), s.clone()); 205 182
+4
crates/bone-document/tests/folder_snapshots.rs
··· 135 135 SketchRelation::Tangent(line_ids[0], circle_id), 136 136 SketchRelation::Equal(line_ids[0], line_ids[2]), 137 137 SketchRelation::Concentric(arc_id, circle_id), 138 + SketchRelation::Midpoint { 139 + point: p2, 140 + line: line_ids[0], 141 + }, 138 142 SketchRelation::Fix(p0), 139 143 ]; 140 144 let with_relations = relations.into_iter().fold(with_circle, add_relation);
+15
crates/bone-document/tests/snapshots/folder_snapshots__sketch_file.snap
··· 247 247 )), 248 248 version: 1, 249 249 ), SerdeSlot( 250 + value: Midpoint( 251 + point: SerKey( 252 + idx: 3, 253 + version: 1, 254 + ), 255 + line: SerKey( 256 + idx: 5, 257 + version: 1, 258 + ), 259 + ), 260 + version: 1, 261 + ), SerdeSlot( 250 262 value: Fix(SerKey( 251 263 idx: 1, 252 264 version: 1, ··· 279 291 version: 1, 280 292 ), SerKey( 281 293 idx: 9, 294 + version: 1, 295 + ), SerKey( 296 + idx: 10, 282 297 version: 1, 283 298 )], 284 299 dimensions: [SerdeSlot(
crates/bone-render/assets/relation_glyphs.png

This is a binary file and will not be displayed.

+17 -10
crates/bone-render/src/pipelines/glyph.rs
··· 1 1 use wgpu::util::DeviceExt; 2 2 3 + use crate::RenderTargets; 3 4 use crate::camera::Camera2; 4 5 use crate::gpu::{Gpu, PICK_FORMAT}; 5 6 use crate::scene::{SceneRelationGlyph, SketchScene}; 6 7 use crate::snapshot::{GlyphStyle, Style, decode_png}; 7 8 8 - pub const ATLAS_SIDE: u32 = 96; 9 + pub const ATLAS_SIDE: u32 = 128; 9 10 pub const TILE_SIDE: u32 = 32; 10 - pub const GRID_COLS: u32 = 3; 11 - pub const GRID_ROWS: u32 = 3; 12 - pub const GLYPH_COUNT: u32 = 9; 11 + pub const GRID_COLS: u32 = 4; 12 + pub const GRID_ROWS: u32 = 4; 13 + pub const GLYPH_COUNT: u32 = 10; 13 14 14 15 const COMMITTED_ATLAS: &[u8] = include_bytes!("../../assets/relation_glyphs.png"); 15 16 ··· 89 90 pub fn draw( 90 91 &self, 91 92 encoder: &mut wgpu::CommandEncoder, 92 - color_view: &wgpu::TextureView, 93 - pick_view: &wgpu::TextureView, 93 + targets: RenderTargets<'_>, 94 94 camera: Camera2, 95 95 style: &Style, 96 96 scene: &SketchScene, ··· 117 117 label: Some("bone-render:glyph-pass"), 118 118 color_attachments: &[ 119 119 Some(wgpu::RenderPassColorAttachment { 120 - view: color_view, 120 + view: targets.color, 121 121 resolve_target: None, 122 122 depth_slice: None, 123 123 ops: wgpu::Operations { ··· 126 126 }, 127 127 }), 128 128 Some(wgpu::RenderPassColorAttachment { 129 - view: pick_view, 129 + view: targets.pick, 130 130 resolve_target: None, 131 131 depth_slice: None, 132 132 ops: wgpu::Operations { ··· 405 405 ring(x, y, 16.0, 16.0, 4.0, 1.8), 406 406 ), 407 407 RelationGlyphKind::Fix => fix_anchor(x, y), 408 + RelationGlyphKind::Midpoint => combine( 409 + segment(x, y, 6.0, 16.0, 26.0, 16.0, 1.8), 410 + segment(x, y, 16.0, 11.0, 16.0, 21.0, 1.8), 411 + ), 408 412 } 409 413 } 410 414 ··· 490 494 ); 491 495 } 492 496 497 + const _: () = assert!(GLYPH_COUNT <= GRID_COLS * GRID_ROWS); 498 + 493 499 #[test] 494 - fn atlas_is_square_and_ninefold() { 500 + fn atlas_extent_matches_grid() { 495 501 let rgba = build_atlas_rgba(); 496 502 assert_eq!(rgba.len(), (ATLAS_SIDE * ATLAS_SIDE * 4) as usize); 497 - assert_eq!(GLYPH_COUNT, GRID_COLS * GRID_ROWS); 503 + assert_eq!(ATLAS_SIDE, TILE_SIDE * GRID_COLS); 504 + assert_eq!(ATLAS_SIDE, TILE_SIDE * GRID_ROWS); 498 505 } 499 506 500 507 #[test]
+5 -5
crates/bone-render/src/pipelines/glyph.wgsl
··· 38 38 vec2<f32>(1.0, 1.0), 39 39 ); 40 40 41 - const GRID_DIM: f32 = 3.0; 42 - const TILE_UV: f32 = 1.0 / 3.0; 43 - const HALF_TEXEL: f32 = 0.5 / 96.0; 41 + const GRID_DIM: f32 = 4.0; 42 + const TILE_UV: f32 = 1.0 / 4.0; 43 + const HALF_TEXEL: f32 = 0.5 / 128.0; 44 44 const INNER_UV: f32 = TILE_UV - 2.0 * HALF_TEXEL; 45 45 46 46 @vertex ··· 53 53 let world_mm = center_mm + corner_signed * half_mm; 54 54 let clip = u.clip_from_world * vec4<f32>(world_mm, 0.0, 1.0); 55 55 56 - let col = f32(inst.tile_index % 3u); 57 - let row = f32(inst.tile_index / 3u); 56 + let col = f32(inst.tile_index % 4u); 57 + let row = f32(inst.tile_index / 4u); 58 58 let local_u = corner.x; 59 59 let local_v = 1.0 - corner.y; 60 60 let u0 = col * TILE_UV + HALF_TEXEL;
+5 -1
crates/bone-render/src/scene.rs
··· 148 148 Equal = 6, 149 149 Concentric = 7, 150 150 Fix = 8, 151 + Midpoint = 9, 151 152 } 152 153 153 154 impl RelationGlyphKind { ··· 168 169 6 => Some(Self::Equal), 169 170 7 => Some(Self::Concentric), 170 171 8 => Some(Self::Fix), 172 + 9 => Some(Self::Midpoint), 171 173 _ => None, 172 174 } 173 175 } ··· 183 185 SketchRelation::Tangent(_, _) => Self::Tangent, 184 186 SketchRelation::Equal(_, _) => Self::Equal, 185 187 SketchRelation::Concentric(_, _) => Self::Concentric, 188 + SketchRelation::Midpoint { .. } => Self::Midpoint, 186 189 SketchRelation::Fix(_) => Self::Fix, 187 190 } 188 191 } 189 192 190 193 #[must_use] 191 - pub const fn all() -> [Self; 9] { 194 + pub const fn all() -> [Self; 10] { 192 195 [ 193 196 Self::Coincident, 194 197 Self::Horizontal, ··· 199 202 Self::Equal, 200 203 Self::Concentric, 201 204 Self::Fix, 205 + Self::Midpoint, 202 206 ] 203 207 } 204 208 }
crates/bone-render/tests/goldens/relations_256.png

This is a binary file and will not be displayed.

+14 -2
crates/bone-render/tests/relations.rs
··· 131 131 let (s, fp) = add_point(s, 4.5, -3.0); 132 132 let s = add_relation(s, SketchRelation::Fix(fp)); 133 133 134 + let (s, mp_a) = add_point(s, 2.0, -3.0); 135 + let (s, mp_b) = add_point(s, 5.0, -3.0); 136 + let (s, mp_line) = add_line(s, mp_a, mp_b); 137 + let (s, mp_point) = add_point(s, 3.5, -3.0); 138 + let s = add_relation( 139 + s, 140 + SketchRelation::Midpoint { 141 + point: mp_point, 142 + line: mp_line, 143 + }, 144 + ); 145 + 134 146 let Ok(scene) = SketchScene::extract(&s) else { 135 147 panic!("scene extract"); 136 148 }; 137 149 assert_eq!( 138 150 scene.relations().len(), 139 151 RelationGlyphKind::all().len(), 140 - "expected nine-kind coverage in relation scene", 152 + "expected one scene glyph per relation kind", 141 153 ); 142 154 scene 143 155 } ··· 157 169 } 158 170 159 171 #[test] 160 - fn nine_relation_kinds_match_golden() { 172 + fn relation_kinds_match_golden() { 161 173 let size = extent(256); 162 174 let ctx = make_context(size); 163 175 let scene = one_per_kind_scene();
+26 -5
crates/bone-solver/src/residual.rs
··· 60 60 point: PointHandle, 61 61 curve: CurveRadius, 62 62 }, 63 + MidpointPointLine { 64 + point: PointHandle, 65 + line: LineHandle, 66 + }, 63 67 EqualLength(LineHandle, LineHandle), 64 68 EqualRadius(CurveRadius, CurveRadius), 65 69 LinearDistance { ··· 84 88 #[must_use] 85 89 pub fn rows(&self) -> usize { 86 90 match self { 87 - Self::CoincidentPointPoint(..) => 2, 91 + Self::CoincidentPointPoint(..) | Self::MidpointPointLine { .. } => 2, 88 92 _ => 1, 89 93 } 90 94 } ··· 107 111 Self::CoincidentPointPoint(p, q) => { 108 112 point_params(p).into_iter().chain(point_params(q)).collect() 109 113 } 110 - Self::CoincidentPointLine { point, line } => point_params(point) 111 - .into_iter() 112 - .chain(line_params(line)) 113 - .collect(), 114 + Self::CoincidentPointLine { point, line } | Self::MidpointPointLine { point, line } => { 115 + point_params(point) 116 + .into_iter() 117 + .chain(line_params(line)) 118 + .collect() 119 + } 114 120 Self::CoincidentPointCurve { point, curve } => point_params(point) 115 121 .into_iter() 116 122 .chain(curve_params(curve)) ··· 175 181 let ey = get(params, point.y) - get(params, line.a.y); 176 182 out[0] = ex * dy - ey * dx; 177 183 } 184 + Self::MidpointPointLine { point, line } => { 185 + out[0] = 186 + get(params, point.x) - 0.5 * (get(params, line.a.x) + get(params, line.b.x)); 187 + out[1] = 188 + get(params, point.y) - 0.5 * (get(params, line.a.y) + get(params, line.b.y)); 189 + } 178 190 Self::CoincidentPointCurve { point, curve } => { 179 191 let cx = get(params, curve.center().x); 180 192 let cy = get(params, curve.center().y); ··· 238 250 } 239 251 Self::CoincidentPointLine { point, line } => { 240 252 jacobian_coincident_point_line(params, row, point, line, sink); 253 + } 254 + Self::MidpointPointLine { point, line } => { 255 + let row2 = row.next(); 256 + sink.push((row, point.x, 1.0)); 257 + sink.push((row, line.a.x, -0.5)); 258 + sink.push((row, line.b.x, -0.5)); 259 + sink.push((row2, point.y, 1.0)); 260 + sink.push((row2, line.a.y, -0.5)); 261 + sink.push((row2, line.b.y, -0.5)); 241 262 } 242 263 Self::CoincidentPointCurve { point, curve } => { 243 264 jacobian_coincident_point_curve(params, row, point, curve, sink);
+4
crates/bone-solver/src/system.rs
··· 234 234 point: point(p), 235 235 curve: curve(c), 236 236 }, 237 + Residual::MidpointPointLine { point: p, line: l } => Residual::MidpointPointLine { 238 + point: point(p), 239 + line: line(l), 240 + }, 237 241 Residual::EqualLength(a, b) => Residual::EqualLength(line(a), line(b)), 238 242 Residual::EqualRadius(a, b) => Residual::EqualRadius(curve(a), curve(b)), 239 243 Residual::LinearDistance { a, b, value_mm } => Residual::LinearDistance {