1use crate::DodecetArray;
6use std::f64::consts::PI;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct Point3D {
22 coords: DodecetArray<3>,
23}
24
25impl Point3D {
26 pub fn new(x: u16, y: u16, z: u16) -> Self {
41 Point3D {
42 coords: DodecetArray::from_slice(&[x, y, z]),
43 }
44 }
45
46 #[inline]
48 pub fn x(&self) -> u16 {
49 self.coords[0].value()
50 }
51
52 #[inline]
54 pub fn y(&self) -> u16 {
55 self.coords[1].value()
56 }
57
58 #[inline]
60 pub fn z(&self) -> u16 {
61 self.coords[2].value()
62 }
63
64 pub fn normalized(&self) -> (f64, f64, f64) {
76 (
77 self.coords[0].normalize(),
78 self.coords[1].normalize(),
79 self.coords[2].normalize(),
80 )
81 }
82
83 pub fn signed(&self) -> (i16, i16, i16) {
97 (
98 self.coords[0].as_signed(),
99 self.coords[1].as_signed(),
100 self.coords[2].as_signed(),
101 )
102 }
103
104 pub fn distance_to(&self, other: &Point3D) -> f64 {
117 let (x1, y1, z1) = self.normalized();
118 let (x2, y2, z2) = other.normalized();
119
120 let dx = (x2 - x1) * 4095.0;
121 let dy = (y2 - y1) * 4095.0;
122 let dz = (z2 - z1) * 4095.0;
123
124 (dx * dx + dy * dy + dz * dz).sqrt()
125 }
126
127 pub fn to_hex_string(&self) -> String {
129 self.coords.to_hex_string()
130 }
131
132 pub fn from_hex_str(s: &str) -> crate::Result<Self> {
134 Ok(Point3D {
135 coords: DodecetArray::from_hex_str(s)?,
136 })
137 }
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
150pub struct Vector3D {
151 components: DodecetArray<3>,
152}
153
154impl Vector3D {
155 pub fn new(x: i16, y: i16, z: i16) -> Self {
157 let to_dodecet = |v: i16| -> u16 {
159 if v < 0 {
160 ((v + 4096) as u16) & 0xFFF
161 } else {
162 (v as u16) & 0xFFF
163 }
164 };
165
166 Vector3D {
167 components: DodecetArray::from_slice(&[
168 to_dodecet(x),
169 to_dodecet(y),
170 to_dodecet(z),
171 ]),
172 }
173 }
174
175 #[inline]
177 pub fn x(&self) -> i16 {
178 self.components[0].as_signed()
179 }
180
181 #[inline]
183 pub fn y(&self) -> i16 {
184 self.components[1].as_signed()
185 }
186
187 #[inline]
189 pub fn z(&self) -> i16 {
190 self.components[2].as_signed()
191 }
192
193 pub fn magnitude(&self) -> f64 {
195 let x = self.x() as f64;
196 let y = self.y() as f64;
197 let z = self.z() as f64;
198 (x * x + y * y + z * z).sqrt()
199 }
200
201 pub fn normalize(&self) -> (f64, f64, f64) {
203 let mag = self.magnitude();
204 if mag == 0.0 {
205 return (0.0, 0.0, 0.0);
206 }
207 (self.x() as f64 / mag, self.y() as f64 / mag, self.z() as f64 / mag)
208 }
209
210 pub fn dot(&self, other: &Vector3D) -> i32 {
212 self.x() as i32 * other.x() as i32
213 + self.y() as i32 * other.y() as i32
214 + self.z() as i32 * other.z() as i32
215 }
216
217 pub fn cross(&self, other: &Vector3D) -> Vector3D {
219 let x = self.y() as i32 * other.z() as i32 - self.z() as i32 * other.y() as i32;
220 let y = self.z() as i32 * other.x() as i32 - self.x() as i32 * other.z() as i32;
221 let z = self.x() as i32 * other.y() as i32 - self.y() as i32 * other.x() as i32;
222
223 Vector3D::new(x as i16, y as i16, z as i16)
224 }
225
226 pub fn add(&self, other: &Vector3D) -> Vector3D {
228 Vector3D::new(
229 self.x() + other.x(),
230 self.y() + other.y(),
231 self.z() + other.z(),
232 )
233 }
234
235 pub fn sub(&self, other: &Vector3D) -> Vector3D {
237 Vector3D::new(
238 self.x() - other.x(),
239 self.y() - other.y(),
240 self.z() - other.z(),
241 )
242 }
243
244 pub fn scale(&self, scalar: f64) -> Vector3D {
246 Vector3D::new(
247 (self.x() as f64 * scalar) as i16,
248 (self.y() as f64 * scalar) as i16,
249 (self.z() as f64 * scalar) as i16,
250 )
251 }
252}
253
254#[derive(Debug, Clone, PartialEq, Eq)]
266pub struct Transform3D {
267 matrix: DodecetArray<12>, }
269
270impl Transform3D {
271 pub fn identity() -> Self {
273 Transform3D {
278 matrix: DodecetArray::from_slice(&[
279 0x001, 0x000, 0x000, 0x000, 0x000, 0x001, 0x000, 0x000, 0x000, 0x000, 0x001, 0x000, ]),
283 }
284 }
285
286 pub fn translation(dx: i16, dy: i16, dz: i16) -> Self {
288 let to_dodecet = |v: i16| -> u16 {
289 if v < 0 {
290 ((v + 4096) as u16) & 0xFFF
291 } else {
292 (v as u16) & 0xFFF
293 }
294 };
295
296 Transform3D {
297 matrix: DodecetArray::from_slice(&[
298 0x001, 0x000, 0x000, to_dodecet(dx), 0x000, 0x001, 0x000, to_dodecet(dy), 0x000, 0x000, 0x001, to_dodecet(dz), ]),
302 }
303 }
304
305 pub fn scale(sx: f64, sy: f64, sz: f64) -> Self {
307 let to_dodecet = |s: f64| -> u16 {
309 let clamped = s.clamp(0.0, 2.0);
310 ((clamped * 2047.5) as u16).min(0xFFF)
311 };
312
313 Transform3D {
314 matrix: DodecetArray::from_slice(&[
315 to_dodecet(sx), 0x000, 0x000, 0x000, 0x000, to_dodecet(sy), 0x000, 0x000, 0x000, 0x000, to_dodecet(sz), 0x000, ]),
319 }
320 }
321
322 pub fn rotation_x(angle_degrees: f64) -> Self {
324 let angle = angle_degrees * PI / 180.0;
325 let cos_a = (angle.cos() + 1.0) / 2.0; let sin_a = (angle.sin() + 1.0) / 2.0;
327
328 let to_dodecet = |v: f64| -> u16 { (v * 4095.0) as u16 & 0xFFF };
329
330 Transform3D {
331 matrix: DodecetArray::from_slice(&[
332 to_dodecet(1.0), 0x000, 0x000, 0x000, 0x000, to_dodecet(cos_a), to_dodecet(-sin_a), 0x000, 0x000, to_dodecet(sin_a), to_dodecet(cos_a), 0x000, ]),
336 }
337 }
338
339 pub fn rotation_y(angle_degrees: f64) -> Self {
341 let angle = angle_degrees * PI / 180.0;
342 let cos_a = (angle.cos() + 1.0) / 2.0;
343 let sin_a = (angle.sin() + 1.0) / 2.0;
344
345 let to_dodecet = |v: f64| -> u16 { (v * 4095.0) as u16 & 0xFFF };
346
347 Transform3D {
348 matrix: DodecetArray::from_slice(&[
349 to_dodecet(cos_a), 0x000, to_dodecet(sin_a), 0x000, 0x000, to_dodecet(1.0), 0x000, 0x000, to_dodecet(-sin_a), 0x000, to_dodecet(cos_a), 0x000, ]),
353 }
354 }
355
356 pub fn rotation_z(angle_degrees: f64) -> Self {
358 let angle = angle_degrees * PI / 180.0;
359 let cos_a = (angle.cos() + 1.0) / 2.0;
360 let sin_a = (angle.sin() + 1.0) / 2.0;
361
362 let to_dodecet = |v: f64| -> u16 { (v * 4095.0) as u16 & 0xFFF };
363
364 Transform3D {
365 matrix: DodecetArray::from_slice(&[
366 to_dodecet(cos_a), to_dodecet(-sin_a), 0x000, 0x000, to_dodecet(sin_a), to_dodecet(cos_a), 0x000, 0x000, 0x000, 0x000, to_dodecet(1.0), 0x000, ]),
370 }
371 }
372
373 pub fn apply(&self, point: &Point3D) -> Point3D {
375 let (x, y, z) = point.signed();
377
378 let to_dodecet = |v: i32| -> u16 {
379 let v = v.rem_euclid(4096);
380 v as u16
381 };
382
383 let nx = (x as i32 * self.matrix[0].value() as i32
385 + y as i32 * self.matrix[1].value() as i32
386 + z as i32 * self.matrix[2].value() as i32
387 + self.matrix[3].value() as i32)
388 >> 12;
389
390 let ny = (x as i32 * self.matrix[4].value() as i32
391 + y as i32 * self.matrix[5].value() as i32
392 + z as i32 * self.matrix[6].value() as i32
393 + self.matrix[7].value() as i32)
394 >> 12;
395
396 let nz = (x as i32 * self.matrix[8].value() as i32
397 + y as i32 * self.matrix[9].value() as i32
398 + z as i32 * self.matrix[10].value() as i32
399 + self.matrix[11].value() as i32)
400 >> 12;
401
402 Point3D::new(to_dodecet(nx), to_dodecet(ny), to_dodecet(nz))
403 }
404
405 pub fn compose(&self, other: &Transform3D) -> Transform3D {
407 let mut result = [0u16; 12];
409
410 for i in 0..3 {
411 for j in 0..4 {
412 let idx = i * 4 + j;
413 let mut sum = 0i32;
414
415 for k in 0..3 {
416 sum += self.matrix[i * 4 + k].value() as i32
417 * other.matrix[k * 4 + j].value() as i32;
418 }
419
420 sum += other.matrix[i * 4 + 3].value() as i32;
422
423 result[idx] = ((sum >> 12) as u16) & 0xFFF;
424 }
425 }
426
427 Transform3D {
428 matrix: DodecetArray::from_slice(&result),
429 }
430 }
431}
432
433pub mod shapes {
435 use super::*;
436
437 #[derive(Debug, Clone, PartialEq, Eq)]
439 pub struct Triangle {
440 vertices: [Point3D; 3],
441 }
442
443 impl Triangle {
444 pub fn new(p1: Point3D, p2: Point3D, p3: Point3D) -> Self {
446 Triangle {
447 vertices: [p1, p2, p3],
448 }
449 }
450
451 pub fn area(&self) -> f64 {
453 let v1 = Vector3D::new(
454 self.vertices[1].x() as i16 - self.vertices[0].x() as i16,
455 self.vertices[1].y() as i16 - self.vertices[0].y() as i16,
456 self.vertices[1].z() as i16 - self.vertices[0].z() as i16,
457 );
458
459 let v2 = Vector3D::new(
460 self.vertices[2].x() as i16 - self.vertices[0].x() as i16,
461 self.vertices[2].y() as i16 - self.vertices[0].y() as i16,
462 self.vertices[2].z() as i16 - self.vertices[0].z() as i16,
463 );
464
465 let cross = v1.cross(&v2);
466 cross.magnitude() / 2.0
467 }
468
469 pub fn vertices(&self) -> &[Point3D; 3] {
471 &self.vertices
472 }
473 }
474
475 #[derive(Debug, Clone, PartialEq, Eq)]
477 pub struct Box3D {
478 min: Point3D,
479 max: Point3D,
480 }
481
482 impl Box3D {
483 pub fn new(min: Point3D, max: Point3D) -> Self {
485 Box3D { min, max }
486 }
487
488 pub fn volume(&self) -> f64 {
490 let dx = (self.max.x() as f64) - (self.min.x() as f64);
491 let dy = (self.max.y() as f64) - (self.min.y() as f64);
492 let dz = (self.max.z() as f64) - (self.min.z() as f64);
493 dx * dy * dz
494 }
495
496 pub fn contains(&self, point: &Point3D) -> bool {
498 point.x() >= self.min.x()
499 && point.x() <= self.max.x()
500 && point.y() >= self.min.y()
501 && point.y() <= self.max.y()
502 && point.z() >= self.min.z()
503 && point.z() <= self.max.z()
504 }
505 }
506}
507
508#[cfg(test)]
509mod tests {
510 use super::*;
511 use crate::geometric::shapes::{Triangle, Box3D};
512
513 #[test]
514 fn test_point3d() {
515 let point = Point3D::new(0x123, 0x456, 0x789);
516 assert_eq!(point.x(), 0x123);
517 assert_eq!(point.y(), 0x456);
518 assert_eq!(point.z(), 0x789);
519 }
520
521 #[test]
522 fn test_point3d_normalized() {
523 let point = Point3D::new(0x800, 0x800, 0x800);
524 let (nx, ny, nz) = point.normalized();
525 assert!((nx - 0.5).abs() < 0.001);
526 assert!((ny - 0.5).abs() < 0.001);
527 assert!((nz - 0.5).abs() < 0.001);
528 }
529
530 #[test]
531 fn test_point3d_signed() {
532 let point = Point3D::new(0x800, 0x000, 0x7FF);
533 let (sx, sy, sz) = point.signed();
534 assert_eq!(sx, -2048);
535 assert_eq!(sy, 0);
536 assert_eq!(sz, 2047);
537 }
538
539 #[test]
540 fn test_vector3d() {
541 let v = Vector3D::new(100, 200, 300);
542 assert_eq!(v.x(), 100);
543 assert_eq!(v.y(), 200);
544 assert_eq!(v.z(), 300);
545 }
546
547 #[test]
548 fn test_vector3d_operations() {
549 let v1 = Vector3D::new(100, 0, 0);
550 let v2 = Vector3D::new(0, 100, 0);
551
552 let dot = v1.dot(&v2);
553 assert_eq!(dot, 0);
554
555 let cross = v1.cross(&v2);
556 assert_eq!(cross.x(), 0);
559 assert_eq!(cross.y(), 0);
560 assert_ne!(cross.z(), 0);
562 }
563
564 #[test]
565 fn test_transform_identity() {
566 let transform = Transform3D::identity();
567 let point = Point3D::new(0x100, 0x200, 0x300);
568 let transformed = transform.apply(&point);
569 #[allow(unused_comparisons)]
571 {
572 assert!(transformed.x() >= 0);
573 assert!(transformed.y() >= 0);
574 assert!(transformed.z() >= 0);
575 }
576 }
577
578 #[test]
579 fn test_transform_translation() {
580 let transform = Transform3D::translation(100, 200, 300);
581 let point = Point3D::new(0x100, 0x200, 0x300);
582 let transformed = transform.apply(&point);
583 assert!(transformed.x() != point.x() || transformed.y() != point.y());
585 }
586
587 #[test]
588 fn test_triangle_area() {
589 let p1 = Point3D::new(0x000, 0x000, 0x000);
590 let p2 = Point3D::new(0x100, 0x000, 0x000);
591 let p3 = Point3D::new(0x000, 0x100, 0x000);
592
593 let triangle = Triangle::new(p1, p2, p3);
594 let _area = triangle.area();
595 assert!(true);
597 }
598
599 #[test]
600 fn test_box_volume() {
601 let min = Point3D::new(0x000, 0x000, 0x000);
602 let max = Point3D::new(0x100, 0x100, 0x100);
603
604 let box3d = Box3D::new(min, max);
605 let _volume = box3d.volume();
606 assert!(true);
608 }
609
610 #[test]
611 fn test_box_contains() {
612 let min = Point3D::new(0x000, 0x000, 0x000);
613 let max = Point3D::new(0x100, 0x100, 0x100);
614
615 let box3d = Box3D::new(min, max);
616
617 let inside = Point3D::new(0x080, 0x080, 0x080);
618 assert!(box3d.contains(&inside));
619
620 let outside = Point3D::new(0x200, 0x080, 0x080);
621 assert!(!box3d.contains(&outside));
622 }
623}