1use crate::types::*;
2use crate::rga::StrokeId;
3use std::collections::HashMap;
4
5#[derive(Debug, Clone, Copy)]
10pub struct StrokePoint {
11 pub x: f32,
12 pub y: f32,
13 pub pressure: f32,
15}
16
17impl StrokePoint {
18 #[inline]
19 pub fn new(x: f32, y: f32, pressure: f32) -> Self {
20 Self { x, y, pressure }
21 }
22
23 #[inline]
24 pub fn basic(x: f32, y: f32) -> Self {
25 Self { x, y, pressure: 1.0 }
26 }
27}
28
29#[derive(Debug, Clone, Copy)]
37pub struct Aabb {
38 pub min_x: f32,
39 pub min_y: f32,
40 pub max_x: f32,
41 pub max_y: f32,
42}
43
44impl Aabb {
45 pub const INFINITE: Aabb = Aabb {
48 min_x: f32::NEG_INFINITY,
49 min_y: f32::NEG_INFINITY,
50 max_x: f32::INFINITY,
51 max_y: f32::INFINITY,
52 };
53
54 pub fn from_points(points: &[StrokePoint]) -> Self {
57 if points.is_empty() {
58 return Self::INFINITE;
59 }
60 let mut min_x = f32::INFINITY;
61 let mut min_y = f32::INFINITY;
62 let mut max_x = f32::NEG_INFINITY;
63 let mut max_y = f32::NEG_INFINITY;
64 for p in points {
65 if p.x < min_x { min_x = p.x; }
66 if p.y < min_y { min_y = p.y; }
67 if p.x > max_x { max_x = p.x; }
68 if p.y > max_y { max_y = p.y; }
69 }
70 Aabb { min_x, min_y, max_x, max_y }
71 }
72
73 #[inline]
75 pub fn intersects(&self, other: &Aabb) -> bool {
76 self.min_x <= other.max_x
77 && self.max_x >= other.min_x
78 && self.min_y <= other.max_y
79 && self.max_y >= other.min_y
80 }
81
82 #[inline]
85 pub fn expanded(self, padding: f32) -> Aabb {
86 Aabb {
87 min_x: self.min_x - padding,
88 min_y: self.min_y - padding,
89 max_x: self.max_x + padding,
90 max_y: self.max_y + padding,
91 }
92 }
93
94 pub fn transform(&self, t: &Transform2D) -> Aabb {
98 let corners = [
99 (self.min_x, self.min_y),
100 (self.max_x, self.min_y),
101 (self.min_x, self.max_y),
102 (self.max_x, self.max_y),
103 ];
104 let mut min_x = f32::INFINITY;
105 let mut min_y = f32::INFINITY;
106 let mut max_x = f32::NEG_INFINITY;
107 let mut max_y = f32::NEG_INFINITY;
108 for (x, y) in corners {
109 let tx = t.a * x + t.c * y + t.tx;
110 let ty = t.b * x + t.d * y + t.ty;
111 if tx < min_x { min_x = tx; }
112 if ty < min_y { min_y = ty; }
113 if tx > max_x { max_x = tx; }
114 if ty > max_y { max_y = ty; }
115 }
116 Aabb { min_x, min_y, max_x, max_y }
117 }
118}
119
120#[inline]
124fn perp_dist(p: &StrokePoint, a: &StrokePoint, b: &StrokePoint) -> f32 {
125 let dx = b.x - a.x;
126 let dy = b.y - a.y;
127 let len_sq = dx * dx + dy * dy;
128 if len_sq < 1e-10 {
129 let ex = p.x - a.x;
131 let ey = p.y - a.y;
132 return (ex * ex + ey * ey).sqrt();
133 }
134 let cross = (dx * (a.y - p.y) - dy * (a.x - p.x)).abs();
136 cross / len_sq.sqrt()
137}
138
139fn rdp_indices(points: &[StrokePoint], epsilon: f32) -> Vec<usize> {
145 let n = points.len();
146 if n <= 2 {
147 return (0..n).collect();
148 }
149
150 let mut keep = vec![false; n];
151 keep[0] = true;
152 keep[n - 1] = true;
153
154 let mut stack: Vec<(usize, usize)> = Vec::with_capacity(64);
156 stack.push((0, n - 1));
157
158 while let Some((start, end)) = stack.pop() {
159 if end <= start + 1 {
160 continue;
161 }
162 let a = &points[start];
163 let b = &points[end];
164 let mut max_dist = 0.0f32;
165 let mut max_idx = start;
166
167 for (idx, p) in points.iter().enumerate().take(end).skip(start + 1) {
168 let d = perp_dist(p, a, b);
169 if d > max_dist {
170 max_dist = d;
171 max_idx = idx;
172 }
173 }
174
175 if max_dist > epsilon {
176 keep[max_idx] = true;
177 stack.push((start, max_idx));
178 stack.push((max_idx, end));
179 }
180 }
182
183 keep.iter()
184 .enumerate()
185 .filter_map(|(i, &k)| if k { Some(i) } else { None })
186 .collect()
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq)]
193#[repr(u8)]
194pub enum ToolKind {
195 Pen = 0,
196 Eraser = 1,
197 Marker = 2,
198 Laser = 3,
199 Shape = 4,
200 Arrow = 5,
201}
202
203impl ToolKind {
204 pub fn from_u8(v: u8) -> Self {
205 match v {
206 0 => Self::Pen,
207 1 => Self::Eraser,
208 2 => Self::Marker,
209 3 => Self::Laser,
210 4 => Self::Shape,
211 5 => Self::Arrow,
212 _ => Self::Pen,
213 }
214 }
215}
216
217#[derive(Debug, Clone)]
226pub struct StrokeData {
227 pub points: Box<[StrokePoint]>,
229 pub tool: ToolKind,
231 pub bounds: Aabb,
233}
234
235impl StrokeData {
236 pub fn new(points: Box<[StrokePoint]>, tool: ToolKind) -> Self {
238 let bounds = Aabb::from_points(&points);
239 Self { points, tool, bounds }
240 }
241
242 pub fn simplify(&mut self, epsilon: f32) -> usize {
256 let original = self.points.len();
257 if original <= 2 {
258 return 0;
259 }
260 let indices = rdp_indices(&self.points, epsilon);
261 let kept = indices.len();
262 if kept == original {
263 return 0;
264 }
265 let new_points: Box<[StrokePoint]> = indices.iter().map(|&i| self.points[i]).collect();
266 self.bounds = Aabb::from_points(&new_points);
267 self.points = new_points;
268 original - kept
269 }
270}
271
272#[derive(Debug, Clone)]
277pub struct LwwRegister<T: Clone> {
278 pub value: T,
279 pub timestamp: OpId,
280}
281
282impl<T: Clone> LwwRegister<T> {
283 pub fn new(value: T, timestamp: OpId) -> Self {
284 Self { value, timestamp }
285 }
286
287 pub fn apply(&mut self, value: T, timestamp: OpId) -> bool {
290 if timestamp > self.timestamp {
291 self.value = value;
292 self.timestamp = timestamp;
293 true
294 } else {
295 false
296 }
297 }
298}
299
300#[derive(Debug, Clone, Copy)]
305pub struct Transform2D {
306 pub a: f32,
307 pub b: f32,
308 pub c: f32,
309 pub d: f32,
310 pub tx: f32,
311 pub ty: f32,
312}
313
314impl Default for Transform2D {
315 fn default() -> Self {
316 Self { a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: 0.0, ty: 0.0 }
318 }
319}
320
321impl Transform2D {
322 #[inline]
324 pub fn is_identity(&self) -> bool {
325 self.a == 1.0
326 && self.b == 0.0
327 && self.c == 0.0
328 && self.d == 1.0
329 && self.tx == 0.0
330 && self.ty == 0.0
331 }
332}
333
334#[derive(Debug, Clone)]
342pub struct StrokeProperties {
343 pub color: LwwRegister<u32>,
345 pub stroke_width: LwwRegister<f32>,
346 pub opacity: LwwRegister<f32>,
348 pub transform: LwwRegister<Transform2D>,
349}
350
351impl StrokeProperties {
352 pub fn new(color: u32, stroke_width: f32, opacity: f32, id: OpId) -> Self {
353 Self {
354 color: LwwRegister::new(color, id),
355 stroke_width: LwwRegister::new(stroke_width, id),
356 opacity: LwwRegister::new(opacity, id),
357 transform: LwwRegister::new(Transform2D::default(), id),
358 }
359 }
360}
361
362pub struct StrokeStore {
367 pub(crate) strokes: HashMap<StrokeId, (StrokeData, StrokeProperties)>,
370}
371
372impl StrokeStore {
373 pub fn new() -> Self {
374 Self { strokes: HashMap::new() }
375 }
376
377 pub fn insert(&mut self, id: StrokeId, data: StrokeData, props: StrokeProperties) {
378 self.strokes.insert(id, (data, props));
379 }
380
381 pub fn get(&self, id: &StrokeId) -> Option<(&StrokeData, &StrokeProperties)> {
382 self.strokes.get(id).map(|(d, p)| (d, p))
383 }
384
385 pub fn get_mut(&mut self, id: &StrokeId) -> Option<(&StrokeData, &mut StrokeProperties)> {
386 self.strokes.get_mut(id).map(|(d, p)| (&*d, p))
387 }
388
389 pub fn get_data_mut(&mut self, id: &StrokeId) -> Option<&mut StrokeData> {
390 self.strokes.get_mut(id).map(|(d, _)| d)
391 }
392
393 pub fn remove(&mut self, id: &StrokeId) -> Option<(StrokeData, StrokeProperties)> {
394 self.strokes.remove(id)
395 }
396
397 pub fn contains(&self, id: &StrokeId) -> bool {
398 self.strokes.contains_key(id)
399 }
400}
401
402impl Default for StrokeStore {
403 fn default() -> Self {
404 Self::new()
405 }
406}
407
408#[cfg(test)]
411mod tests {
412 use super::*;
413
414 #[test]
415 fn lww_register_apply() {
416 let id_a = OpId { lamport: LamportTs(1), actor: ActorId(1) };
417 let id_b = OpId { lamport: LamportTs(2), actor: ActorId(1) };
418 let id_c = OpId { lamport: LamportTs(1), actor: ActorId(1) };
419
420 let mut reg = LwwRegister::new(10u32, id_a);
421 assert!(reg.apply(20, id_b));
422 assert_eq!(reg.value, 20);
423 assert!(!reg.apply(5, id_c));
424 assert_eq!(reg.value, 20);
425 }
426
427 #[test]
428 fn transform2d_default_is_identity() {
429 let t = Transform2D::default();
430 assert!(t.is_identity());
431 let t2 = Transform2D { a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: 1.0, ty: 0.0 };
432 assert!(!t2.is_identity());
433 }
434
435 #[test]
436 fn aabb_from_points() {
437 let pts = vec![
438 StrokePoint::basic(0.0, 0.0),
439 StrokePoint::basic(10.0, 5.0),
440 StrokePoint::basic(-2.0, 8.0),
441 ];
442 let b = Aabb::from_points(&pts);
443 assert_eq!(b.min_x, -2.0);
444 assert_eq!(b.min_y, 0.0);
445 assert_eq!(b.max_x, 10.0);
446 assert_eq!(b.max_y, 8.0);
447 }
448
449 #[test]
450 fn aabb_intersects() {
451 let a = Aabb { min_x: 0.0, min_y: 0.0, max_x: 10.0, max_y: 10.0 };
452 let b = Aabb { min_x: 5.0, min_y: 5.0, max_x: 15.0, max_y: 15.0 };
453 let c = Aabb { min_x: 20.0, min_y: 20.0, max_x: 30.0, max_y: 30.0 };
454 assert!(a.intersects(&b));
455 assert!(b.intersects(&a));
456 assert!(!a.intersects(&c));
457 }
458
459 #[test]
460 fn aabb_expanded() {
461 let a = Aabb { min_x: 0.0, min_y: 0.0, max_x: 10.0, max_y: 10.0 };
462 let e = a.expanded(2.0);
463 assert_eq!(e.min_x, -2.0);
464 assert_eq!(e.max_x, 12.0);
465 }
466
467 #[test]
468 fn aabb_transform_identity_leaves_bounds_unchanged() {
469 let a = Aabb { min_x: 1.0, min_y: 2.0, max_x: 5.0, max_y: 6.0 };
470 let t = Transform2D::default();
471 let b = a.transform(&t);
472 assert!((b.min_x - 1.0).abs() < 1e-5);
473 assert!((b.min_y - 2.0).abs() < 1e-5);
474 assert!((b.max_x - 5.0).abs() < 1e-5);
475 assert!((b.max_y - 6.0).abs() < 1e-5);
476 }
477
478 #[test]
479 fn aabb_transform_translation() {
480 let a = Aabb { min_x: 0.0, min_y: 0.0, max_x: 10.0, max_y: 10.0 };
481 let t = Transform2D { a: 1.0, b: 0.0, c: 0.0, d: 1.0, tx: 5.0, ty: -3.0 };
482 let b = a.transform(&t);
483 assert!((b.min_x - 5.0).abs() < 1e-5);
484 assert!((b.min_y - (-3.0)).abs() < 1e-5);
485 assert!((b.max_x - 15.0).abs() < 1e-5);
486 assert!((b.max_y - 7.0).abs() < 1e-5);
487 }
488
489 #[test]
490 fn rdp_straight_line_simplified_to_endpoints() {
491 let pts: Box<[StrokePoint]> = (0..100)
493 .map(|i| StrokePoint::basic(i as f32, i as f32))
494 .collect();
495 let mut data = StrokeData::new(pts, ToolKind::Pen);
496 let removed = data.simplify(0.5);
497 assert_eq!(data.points.len(), 2, "straight line → 2 points");
498 assert_eq!(removed, 98);
499 }
500
501 #[test]
502 fn rdp_curve_keeps_shape() {
503 let pts: Box<[StrokePoint]> = (0..=360)
505 .map(|i| {
506 let t = (i as f32).to_radians();
507 StrokePoint::basic(i as f32, t.sin() * 100.0)
508 })
509 .collect();
510 let n = pts.len();
511 let mut data = StrokeData::new(pts, ToolKind::Pen);
512 let removed = data.simplify(1.0);
513 assert!(data.points.len() < n / 2, "should reduce significantly");
514 assert!(data.points.len() > 2, "should retain shape");
515 assert_eq!(data.points.len() + removed, n);
516 }
517
518 #[test]
519 fn rdp_two_points_unchanged() {
520 let pts: Box<[StrokePoint]> = vec![
521 StrokePoint::basic(0.0, 0.0),
522 StrokePoint::basic(10.0, 10.0),
523 ].into();
524 let mut data = StrokeData::new(pts, ToolKind::Pen);
525 assert_eq!(data.simplify(0.5), 0);
526 assert_eq!(data.points.len(), 2);
527 }
528
529 #[test]
530 fn stroke_data_bounds_computed_on_new() {
531 let pts: Box<[StrokePoint]> = vec![
532 StrokePoint::basic(3.0, 7.0),
533 StrokePoint::basic(9.0, 2.0),
534 ].into();
535 let data = StrokeData::new(pts, ToolKind::Pen);
536 assert_eq!(data.bounds.min_x, 3.0);
537 assert_eq!(data.bounds.min_y, 2.0);
538 assert_eq!(data.bounds.max_x, 9.0);
539 assert_eq!(data.bounds.max_y, 7.0);
540 }
541
542 #[test]
543 fn simplify_recomputes_bounds() {
544 let pts: Box<[StrokePoint]> = (0..=10)
546 .map(|i| StrokePoint::basic(i as f32, i as f32))
547 .collect();
548 let mut data = StrokeData::new(pts, ToolKind::Pen);
549 let removed = data.simplify(0.1);
550 assert!(removed > 0);
551 assert!((data.bounds.min_x - 0.0).abs() < 1e-5);
553 assert!((data.bounds.max_x - 10.0).abs() < 1e-5);
554 }
555}