diff --git a/workspaces/circular-sequence/src/Article/index.tsx b/workspaces/circular-sequence/src/Article/index.tsx index 3c3c0edf..b704ae94 100644 --- a/workspaces/circular-sequence/src/Article/index.tsx +++ b/workspaces/circular-sequence/src/Article/index.tsx @@ -4,9 +4,16 @@ import { ArticleFigLink, ArticleFigs, ArticlePage, + DeepPartial, ProjectPageLink, } from '@hogg/common'; -import { ColorMode, TilingRenderer, meta as tilingsMeta } from '@hogg/tilings'; +import { + Layer, + Options, + ScaleMode, + TilingRenderer, + meta as tilingsMeta, +} from '@hogg/tilings'; import { ArticleHeading, ArticleParagraph, @@ -14,7 +21,6 @@ import { Code, Link, Text, - sizeX12Px, } from 'preshape'; import fileContentsGetMatch from '../../src-rust/get_match.rs?raw'; import fileContentsMinPermutation from '../../src-rust/min_permutation.rs?raw'; @@ -22,6 +28,13 @@ import fileContentsSequence from '../../src-rust/sequence.rs?raw'; import ConcatenatedSequencesFig from './Figs/ConcatenatedSequencesFig'; import MinPermutationFigWithWasApi from './Figs/MinPermutationFig'; +const tilingRendererOptions: DeepPartial = { + scaleMode: ScaleMode.Contain, + showLayers: { + [Layer.Transform]: true, + }, +}; + const Article = () => { return ( @@ -52,9 +65,7 @@ const Article = () => { @@ -174,10 +185,7 @@ let seq_2: Sequence = [6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0]; height="200px" validations={[]} notation="4-3,4,3,12" - options={{ - colorMode: ColorMode.None, - padding: sizeX12Px, - }} + options={tilingRendererOptions} /> } language="rust" @@ -208,12 +216,7 @@ let seq_2: Sequence = [6, 6, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0]; } language="rust" diff --git a/workspaces/common/src/types.ts b/workspaces/common/src/types.ts index 4dcb836e..1c72744a 100644 --- a/workspaces/common/src/types.ts +++ b/workspaces/common/src/types.ts @@ -27,3 +27,9 @@ export type Project = { deploy?: boolean; wip?: boolean; }; + +export type DeepPartial = T extends object + ? { + [P in keyof T]?: DeepPartial; + } + : T; diff --git a/workspaces/line-segment-extending/src/Article.tsx b/workspaces/line-segment-extending/src/Article.tsx index 1ba7dc98..8cf243e9 100644 --- a/workspaces/line-segment-extending/src/Article.tsx +++ b/workspaces/line-segment-extending/src/Article.tsx @@ -5,11 +5,13 @@ import { ArticleFigLink, ArticleFigs, ArticlePage, + DeepPartial, ProjectPageLink, } from '@hogg/common'; import { Layer, Options, + ScaleMode, TilingRenderer, meta as tilingsMeta, } from '@hogg/tilings'; @@ -26,18 +28,11 @@ import { type Props = {}; -const tilingRendererOptions: Partial = { +const tilingRendererOptions: DeepPartial = { + scaleMode: ScaleMode.Contain, showTransformIndex: 1, showLayers: { - [Layer.Axis]: false, - [Layer.BoundingBoxes]: false, - [Layer.GridLineSegment]: false, - [Layer.GridPolygon]: false, - [Layer.PlaneOutline]: false, - [Layer.ShapeBorder]: true, - [Layer.ShapeFill]: true, [Layer.Transform]: true, - [Layer.TransformPoints]: false, }, }; diff --git a/workspaces/spatial-grid-map/src-rust/bucket.rs b/workspaces/spatial-grid-map/src-rust/bucket.rs index 022fa058..e6e157bf 100644 --- a/workspaces/spatial-grid-map/src-rust/bucket.rs +++ b/workspaces/spatial-grid-map/src-rust/bucket.rs @@ -1,25 +1,24 @@ use std::{ - cmp::Ordering, - collections::{BTreeSet, HashMap}, + collections::HashMap, ops::{Deref, DerefMut}, }; use serde::{Deserialize, Serialize}; use typeshare::typeshare; -use crate::utils::{compare_coordinate, compare_radians, coordinate_equals, normalize_radian}; +use crate::utils::{coordinate_equals, normalize_radian}; #[derive(Clone, Debug, Default, Deserialize, Serialize)] #[typeshare] pub struct Bucket { #[typeshare(serialized_as = "Vec>")] - entries: BTreeSet>, + pub entries: Vec>, } impl Bucket { pub fn new(point: (f64, f64), value: TEntryValue, size: f32) -> Self { Bucket { - entries: BTreeSet::from([BucketEntry { + entries: Vec::from([BucketEntry { point, value, size, @@ -55,7 +54,7 @@ impl Bucket { pub fn get_entry(&self, point: &(f64, f64)) -> Option<&BucketEntry> { self .get_entry_index(point) - .and_then(|index| self.entries.iter().nth(index)) + .and_then(|index| self.entries.get(index)) } pub fn get_entry_mut(&mut self, point: &(f64, f64)) -> Option> { @@ -65,13 +64,6 @@ impl Bucket { }) } - pub fn take_entry(&mut self, point: &(f64, f64)) -> Option> { - self - .get_entry_index(point) - .and_then(|index| self.entries.iter().nth(index).cloned()) - .and_then(|entry| self.entries.take(&entry)) - } - pub fn get_value(&self, point: &(f64, f64)) -> Option<&TEntryValue> { self.get_entry(point).map(|entry| &entry.value) } @@ -81,18 +73,18 @@ impl Bucket { } pub fn insert(&mut self, entry: BucketEntry) -> bool { - if !self.contains(&entry.point) { - return self.entries.insert(entry); + if self.contains(&entry.point) { + return false; } - false + self.entries.push(entry); + true } pub fn remove(&mut self, point: &(f64, f64)) -> Option> { self - .get_entry(point) - .cloned() - .and_then(|entry| self.entries.take(&entry)) + .get_entry_index(point) + .map(|index| self.entries.remove(index)) } pub fn increment_counter(&mut self, point: &(f64, f64), counter: &str) { @@ -158,36 +150,10 @@ impl BucketEntry { } } -impl Eq for BucketEntry {} - -impl PartialEq for BucketEntry { - fn eq(&self, other: &Self) -> bool { - coordinate_equals(self.point.0, other.point.0) && coordinate_equals(self.point.0, other.point.0) - } -} - -impl Ord for BucketEntry { - fn cmp(&self, other: &Self) -> Ordering { - let theta_comparison = compare_radians(self.theta(), other.theta()); - - if theta_comparison != Ordering::Equal { - return theta_comparison; - } - - compare_coordinate(self.distance_to_center(), other.distance_to_center()) - } -} - -impl PartialOrd for BucketEntry { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - // Helper struct to mimic a mutable reference pub struct MutBucketEntry<'a, TEntryValue: Clone + Default> { - item: BucketEntry, - parent: &'a mut Bucket, + pub item: BucketEntry, + pub parent: &'a mut Bucket, } impl Deref for MutBucketEntry<'_, TEntryValue> { @@ -208,6 +174,6 @@ impl DerefMut for MutBucketEntry<'_, TEntryValue> impl Drop for MutBucketEntry<'_, TEntryValue> { fn drop(&mut self) { let item = std::mem::take(&mut self.item); - self.parent.entries.insert(item); + self.parent.entries.push(item); } } diff --git a/workspaces/spatial-grid-map/src-rust/grid.rs b/workspaces/spatial-grid-map/src-rust/grid.rs index d6c00026..ca50314e 100644 --- a/workspaces/spatial-grid-map/src-rust/grid.rs +++ b/workspaces/spatial-grid-map/src-rust/grid.rs @@ -6,11 +6,13 @@ use serde::{Deserialize, Serialize}; use typeshare::typeshare; use core::f64; +use std::cmp::Ordering; use std::collections::{BTreeSet, HashMap}; use std::mem; use crate::bucket::{Bucket, BucketEntry, MutBucketEntry}; use crate::location::{self, Location}; +use crate::utils::compare_coordinate; use crate::visitor::Visitor; #[derive(Debug, Clone, Copy, Deserialize, Serialize)] @@ -100,7 +102,7 @@ impl SpatialGridMap fn get_bucket_by_point_mut(&mut self, point: &(f64, f64)) -> Option<&mut Bucket> { self .get_location(point) - .and_then(|location| self.store.get_mut(&location.key)) + .and_then(|location| self.get_bucket_by_location_mut(&location)) } pub fn get_value(&self, point: &(f64, f64)) -> Option<&TEntryValue> { @@ -157,19 +159,26 @@ impl SpatialGridMap self.get_value(point).is_some() } - fn insert_entry(&mut self, entry: BucketEntry) -> MutBucketEntry { + fn insert_entry( + &mut self, + entry: BucketEntry, + update_size_check: bool, + ) -> MutBucketEntry { match self.get_location(&entry.point) { None => { self.increase_size(); - self.insert_entry(entry) + self.insert_entry(entry, update_size_check) } Some(location) => { let point = entry.point; let size = entry.size; if self.store.entry(location.key).or_default().insert(entry) { - self.locations.insert(location); - self.update_spacing(size); + self.locations.insert(location.clone()); + + if update_size_check { + self.update_spacing(size); + } } self @@ -192,6 +201,7 @@ impl SpatialGridMap .with_point(point) .with_value(value) .with_size(size as f32), + true, ) } @@ -264,14 +274,14 @@ impl SpatialGridMap return; } ResizeMethod::Maximum => { - if new_spacing > self.get_spacing() as f32 { + if compare_coordinate(new_spacing as f64, self.get_spacing()) == Ordering::Greater { self.spacing = Some(new_spacing); } else { return; } } ResizeMethod::Minimum => { - if new_spacing < self.get_spacing() as f32 { + if compare_coordinate(new_spacing as f64, self.get_spacing()) == Ordering::Less { self.spacing = Some(new_spacing); } else { return; @@ -289,14 +299,16 @@ impl SpatialGridMap .get_mut(&location.key) .expect("Bucket not found while updating spacing"); let bucket_entry = bucket - .take_entry(&location.point) + .remove(&location.point) .expect("Bucket entry not found while updating spacing"); self.insert_entry( BucketEntry::default() .with_point(location.point) .with_value(bucket_entry.value) + .with_size(bucket_entry.size) .with_counters(bucket_entry.counters), + false, ); } } diff --git a/workspaces/tilings/src-rust/renderer/src/canvas/component/arrow.rs b/workspaces/tilings/src-rust/renderer/src/canvas/component/arrow.rs index 9d5d3b84..dcb43d65 100644 --- a/workspaces/tilings/src-rust/renderer/src/canvas/component/arrow.rs +++ b/workspaces/tilings/src-rust/renderer/src/canvas/component/arrow.rs @@ -25,8 +25,8 @@ impl Arrow { } fn get_chevron(&self, scale: &Scale) -> Chevron { - let direction = self.line_segment.p2.radian_to(&self.line_segment.p1) - PI * 0.5; - let point = self.line_segment.p2; + let direction = self.line_segment.end.radian_to(&self.line_segment.start) - PI * 0.5; + let point = self.line_segment.end; Chevron::default() .with_point(point) diff --git a/workspaces/tilings/src-rust/renderer/src/canvas/component/line_segment.rs b/workspaces/tilings/src-rust/renderer/src/canvas/component/line_segment.rs index 57c1cfe1..f64f7486 100644 --- a/workspaces/tilings/src-rust/renderer/src/canvas/component/line_segment.rs +++ b/workspaces/tilings/src-rust/renderer/src/canvas/component/line_segment.rs @@ -172,7 +172,7 @@ pub fn get_extended_points_to_bbox( .with_start(points[0]) .with_end(points[1]) .extend_to_bbox(bbox, extend_start, false); - point = line_segment.p1; + point = line_segment.start; } if extend_end && index == points.len() - 1 { @@ -181,7 +181,7 @@ pub fn get_extended_points_to_bbox( .with_end(points[points.len() - 1]) .extend_to_bbox(bbox, false, extend_end); - point = line_segment.p2; + point = line_segment.end; } extended_points.push(point); diff --git a/workspaces/tilings/src-rust/renderer/src/canvas/component/line_segment_arrows.rs b/workspaces/tilings/src-rust/renderer/src/canvas/component/line_segment_arrows.rs index 37714d01..d899f682 100644 --- a/workspaces/tilings/src-rust/renderer/src/canvas/component/line_segment_arrows.rs +++ b/workspaces/tilings/src-rust/renderer/src/canvas/component/line_segment_arrows.rs @@ -63,16 +63,17 @@ impl LineSegmentArrows { while shift < line_segment.length() { let p1 = line_segment .set_length(line_segment.length() - shift, LineSegmentOrigin::End) - .p1; + .start; let p2 = line_segment .set_length( line_segment.length() - shift - length - gap_from_line_segment, LineSegmentOrigin::End, ) - .p1; + .start; - let mut arrow_line_segment = LineSegment { p1, p2 }.rotate(self.direction, Some(&p1)); + let mut arrow_line_segment = + LineSegment { start: p1, end: p2 }.rotate(self.direction, Some(&p1)); arrow_line_segment = arrow_line_segment.set_length( arrow_line_segment.length() - gap_from_line_segment, diff --git a/workspaces/tilings/src-rust/renderer/src/canvas/mod.rs b/workspaces/tilings/src-rust/renderer/src/canvas/mod.rs index e2be0c11..a05486c6 100644 --- a/workspaces/tilings/src-rust/renderer/src/canvas/mod.rs +++ b/workspaces/tilings/src-rust/renderer/src/canvas/mod.rs @@ -7,6 +7,7 @@ use std::collections::{BTreeMap, HashMap, VecDeque}; use anyhow::Result; use tiling::geometry::BBox; +use tiling::Tiling; use wasm_bindgen::JsCast; use self::collision::Theia; @@ -36,7 +37,7 @@ impl Canvas { pub fn new( canvas: web_sys::OffscreenCanvas, options: &Options, - min_point: tiling::geometry::Point, + tiling: &Tiling, ) -> Result { let context = canvas.get_context("2d"); let scale = Scale::default() @@ -86,7 +87,7 @@ impl Canvas { context, scale: scale .with_canvas_bbox(canvas_bbox) - .with_min_point(min_point), + .with_convex_hull(tiling.plane.convex_hull.clone()), draw_bounding_boxes: layers_enabled .get(&Layer::BoundingBoxes) diff --git a/workspaces/tilings/src-rust/renderer/src/canvas/scale.rs b/workspaces/tilings/src-rust/renderer/src/canvas/scale.rs index 8e3a0d2a..4d1f46cb 100644 --- a/workspaces/tilings/src-rust/renderer/src/canvas/scale.rs +++ b/workspaces/tilings/src-rust/renderer/src/canvas/scale.rs @@ -1,7 +1,7 @@ use core::f64; use serde::{Deserialize, Serialize}; -use tiling::geometry::{BBox, LineSegment, Point}; +use tiling::geometry::{BBox, ConvexHull}; use typeshare::typeshare; use crate::Error; @@ -24,7 +24,7 @@ pub struct Scale { auto_rotate: bool, canvas_bbox: BBox, content_bbox: BBox, - min_point: Point, + convex_hull: ConvexHull, mode: ScaleMode, padding: f64, @@ -40,7 +40,7 @@ impl Default for Scale { auto_rotate: false, canvas_bbox: BBox::default(), content_bbox: BBox::default(), - min_point: Point::default(), + convex_hull: ConvexHull::default(), mode: ScaleMode::default(), padding: 0.0, @@ -82,8 +82,8 @@ impl Scale { self } - pub fn with_min_point(mut self, min_point: Point) -> Self { - self.min_point = min_point; + pub fn with_convex_hull(mut self, convex_hull: ConvexHull) -> Self { + self.convex_hull = convex_hull; self.update(); self } @@ -151,6 +151,13 @@ impl Scale { self.translate_x = -content_centroid.x; self.translate_y = -content_centroid.y; + // If the canvas or content dimensions haven't been set yet, + // we can't calculate the scale. + if canvas_height == 0.0 || canvas_width == 0.0 || content_height == 0.0 || content_width == 0.0 + { + return; + } + // Rotate the content if it's not the same orientation as the canvas. self.rotate = if self.auto_rotate && ((content_ratio < 1.0 && canvas_ratio > 1.0) @@ -161,46 +168,20 @@ impl Scale { 0.0 }; - // Scale the content to fit within the canvas. - self.scale = if self.rotate == 0.0 { - (canvas_height / content_height).min(canvas_width / content_width) - } else { - (canvas_width / content_height).min(canvas_height / content_width) - }; - - // At this point, the scale is set to grow/shrink - // the contents so it's fully visible within the canvas - // and touching the bounds of the canvas. - // - // When the mode is set to cover, we want to scale the - // content so it covers the canvas, which means taking the - // distance of the scaled minimum point to the distance - // of the diagonal of the canvas. - if self.mode == ScaleMode::Cover { - if self.min_point.eq(&Point::default()) { - return; + self.scale = match self.mode { + // Scale the content to fit within the canvas. + ScaleMode::Contain => { + if self.rotate == 0.0 { + (canvas_height / content_height).min(canvas_width / content_width) + } else { + (canvas_width / content_height).min(canvas_height / content_width) + } } - - let scaled_min_point = self.min_point.scale(self.scale); - let min_point_radians = scaled_min_point.radian_to(&Point::default()); - let target_radians = std::f64::consts::PI * 0.25; - - let shift_radians = target_radians - min_point_radians; - let rotated_min_point = scaled_min_point.rotate(shift_radians, None); - - let line_segment_length = LineSegment::default() - .with_start(Point::default()) - .with_end(rotated_min_point) - .length(); - - let diagonal_length = LineSegment::default() - .with_start(Point::default()) - .with_end(Point::at(canvas_width, canvas_height)) - .length(); - - let scale = diagonal_length / line_segment_length; - - self.scale *= scale; - } + // Scale the content to cover the canvas. + ScaleMode::Cover => self + .convex_hull + .rotate(self.rotate, None) + .get_bbox_scale_value(&self.canvas_bbox), + }; } } diff --git a/workspaces/tilings/src-rust/renderer/src/draw/layers/draw_transform_eccentric.rs b/workspaces/tilings/src-rust/renderer/src/draw/layers/draw_transform_eccentric.rs index f0fd9017..3176ea6d 100644 --- a/workspaces/tilings/src-rust/renderer/src/draw/layers/draw_transform_eccentric.rs +++ b/workspaces/tilings/src-rust/renderer/src/draw/layers/draw_transform_eccentric.rs @@ -80,11 +80,11 @@ fn draw_transform_eccentric_reflect( ) -> Result<(), Error> { let line_segment_p1 = tiling::geometry::LineSegment::default() .with_start(*origin_point) - .with_end(line_segment.p1); + .with_end(line_segment.start); let line_segment_p2 = tiling::geometry::LineSegment::default() .with_start(*origin_point) - .with_end(line_segment.p2); + .with_end(line_segment.end); canvas.add_component( Layer::Transform, @@ -121,7 +121,7 @@ fn draw_transform_eccentric_rotate( let start_angle = match origin_type { OriginType::MidPoint => { if let Some(line_segment) = tiling.plane.line_segments.get_value(&origin_point.into()) { - line_segment.p2.radian_to(origin_point) + line_segment.end.radian_to(origin_point) } else { 0.0 } diff --git a/workspaces/tilings/src-rust/renderer/src/draw/mod.rs b/workspaces/tilings/src-rust/renderer/src/draw/mod.rs index 3fb89ad5..f6f53dff 100644 --- a/workspaces/tilings/src-rust/renderer/src/draw/mod.rs +++ b/workspaces/tilings/src-rust/renderer/src/draw/mod.rs @@ -21,11 +21,7 @@ pub fn draw( options: Options, ) -> Result { let mut metrics = Metrics::default(); - let min_point = tiling - .plane - .get_nearest_edge_point() - .expect("No points in tiling"); - let mut canvas = Canvas::new(offscreen_canvas, &options, min_point)?; + let mut canvas = Canvas::new(offscreen_canvas, &options, tiling)?; let show_layers = options.show_layers.clone().unwrap_or_default(); diff --git a/workspaces/tilings/src-rust/tiling/src/build/plane.rs b/workspaces/tilings/src-rust/tiling/src/build/plane.rs index fdc2f473..4e3be641 100644 --- a/workspaces/tilings/src-rust/tiling/src/build/plane.rs +++ b/workspaces/tilings/src-rust/tiling/src/build/plane.rs @@ -66,7 +66,7 @@ impl Plane { pub fn reset(&mut self) { self.convex_hull = ConvexHull::default(); - self.line_segments = SpatialGridMap::default().with_resize_method(ResizeMethod::Maximum); + self.line_segments = SpatialGridMap::default().with_resize_method(ResizeMethod::First); self.line_segments_by_shape_group = Vec::new(); self.points_center = SpatialGridMap::default().with_resize_method(ResizeMethod::Maximum); self.points_end = SpatialGridMap::default().with_resize_method(ResizeMethod::First); @@ -117,7 +117,8 @@ impl Plane { self.validate_gaps()?; self.validate_vertex_types()?; - self.convex_hull = ConvexHull::from_line_segments(self.get_line_segment_edges()); + self.convex_hull = + ConvexHull::from_line_segments(self.get_line_segment_edges().iter_values()); } Ok(()) @@ -403,7 +404,7 @@ impl Plane { self .get_line_segment_edges() .iter_values() - .flat_map(|line_segment| [line_segment.p1, line_segment.p2]) + .flat_map(|line_segment| [line_segment.start, line_segment.end]) .min_by(|a, b| { compare_coordinate( a.distance_to(&Point::default()), diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/bbox.rs b/workspaces/tilings/src-rust/tiling/src/geometry/bbox.rs index 390fd68e..af50dfcb 100644 --- a/workspaces/tilings/src-rust/tiling/src/geometry/bbox.rs +++ b/workspaces/tilings/src-rust/tiling/src/geometry/bbox.rs @@ -144,7 +144,7 @@ impl BBox { for a_line_segment in a_line_segments.iter() { for b_line_segment in b_line_segments.iter() { - if a_line_segment.intersects(b_line_segment) { + if a_line_segment.is_intersection_with_polygon_line_segment(b_line_segment) { return true; } } diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/convex_hull.rs b/workspaces/tilings/src-rust/tiling/src/geometry/convex_hull.rs index 299a84c1..6eecc734 100644 --- a/workspaces/tilings/src-rust/tiling/src/geometry/convex_hull.rs +++ b/workspaces/tilings/src-rust/tiling/src/geometry/convex_hull.rs @@ -1,11 +1,14 @@ +#[path = "./convex_hull_tests.rs"] +#[cfg(test)] +mod tests; + use std::cmp::Ordering; use serde::{Deserialize, Serialize}; -use spatial_grid_map::SpatialGridMap; -use crate::utils::math::{compare_coordinate, compare_radians}; +use crate::utils::math::{compare_coordinate, compare_radians, is_between_radians}; -use super::{LineSegment, Point}; +use super::{BBox, LineSegment, Point}; #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct ConvexHull { @@ -13,25 +16,30 @@ pub struct ConvexHull { } impl ConvexHull { - pub fn from_line_segments(line_segments: SpatialGridMap) -> Self { + pub fn from_line_segments<'a>(line_segments: impl Iterator) -> Self { let mut points = line_segments - .iter_values() // The second point of the line_segment will be the first point // of the next line_segment so we only need to take the first point - .map(|line_segment| line_segment.p1) + .map(|line_segment| line_segment.start) .collect::>(); + if points.len() < 3 { + return Self::default(); + } + // We'll perform the Graham's scan algorithm to find the convex hull // of the points - // Find the point with the lowest y-coordinate + // Find the point with the lowest y-coordinate, we could quite + // easily also use the centroid of all the points too. let lowest_point = points .iter() .cloned() .max_by(|a, b| compare_coordinate(a.y, b.y)) - .expect("ConvexHull: there should be at least one point"); + .expect("There should be at least one point"); - // Sort the points by the angle they make with the lowest point + // Sort the points by their angle to the lowest point. + // This gets all the points in order around the polygon. points.sort_by(|a, b| { let a_radians = lowest_point.radian_to(a); let b_radians = lowest_point.radian_to(b); @@ -60,6 +68,90 @@ impl ConvexHull { ConvexHull { points: stack } } + + fn find_line_segment(&self, terminal_point: Point) -> Option { + let radians = terminal_point.radian_to_center(); + let points_count = self.points.len(); + + // log::info!("Finding line segment for {}", terminal_point); + //log::info!("Radian: {}", radians); + //log::info!("Points: {:?}", self.points); + + for i in 0..points_count { + let p1 = self.points[i]; + let p2 = self.points[(i + 1) % points_count]; + + //log::info!("Checking line segment: {} -> {}", p1, p2); + + let p1_radians = p1.radian_to_center(); + let p2_radians = p2.radian_to_center(); + + //log::info!("Radian: {} -> {}", p1_radians, p2_radians); + + if is_between_radians(p1_radians, radians, p2_radians) { + return Some(LineSegment::default().with_start(p1).with_end(p2)); + } else { + //log::info!("Radian not between {} and {}", p1_radians, p2_radians); + } + } + + None + } + + pub fn get_bbox_scale_value(&self, bbox: &BBox) -> f64 { + if self.points.is_empty() { + return 1.0; + } + + let bbox_points: [Point; 4] = bbox.into(); + + //log::info!("BBox points: {:?}", bbox_points); + + bbox_points + .iter() + .map(|origin| { + // At this point, we're working with the assumption that the BBox is bigger + // than the convex hull so we need a vector that goes from the origin outwards. + let vector = LineSegment::default() + .with_start(Point::default()) + // We scale the vector to the maximum radius of the BBox + // so that it will intersect with an edge of the bbox. + .with_end(*origin); + + // Using radians, we can find the 2 points on either + // side of the vectors terminal point. + let line_segment = self + .find_line_segment(vector.end) + .expect("Line segment not found"); + + let intersection_point = line_segment + .get_intersection_point(&vector) + .expect("Intersection point not found"); + + let origin_distance = origin.distance_to(&Point::default()); + let intersection_point_distance = intersection_point.distance_to(&Point::default()); + + origin_distance / intersection_point_distance + }) + .max_by(|a, b| compare_coordinate(*a, *b)) + .expect("There should be at least one point") + } + + pub fn scale(&self, scale: f64) -> Self { + ConvexHull { + points: self.points.iter().map(|point| point.scale(scale)).collect(), + } + } + + pub fn rotate(&self, theta: f64, origin: Option<&Point>) -> Self { + ConvexHull { + points: self + .points + .iter() + .map(|point| point.rotate(theta, origin)) + .collect(), + } + } } // Check if the points make a left turn by checking the sign of the cross product diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/convex_hull_tests.rs b/workspaces/tilings/src-rust/tiling/src/geometry/convex_hull_tests.rs new file mode 100644 index 00000000..1a5056bf --- /dev/null +++ b/workspaces/tilings/src-rust/tiling/src/geometry/convex_hull_tests.rs @@ -0,0 +1,118 @@ +use core::f64; + +use insta::assert_json_snapshot; + +use crate::{geometry::Polygon, notation::Shape}; + +use super::*; + +#[test] +fn creates_an_empty_convex_hull_from_0_line_segments() { + // 0 line_segments + assert_json_snapshot!(ConvexHull::from_line_segments([].iter())); +} + +#[test] +fn creates_an_empty_convex_hull_from_1_line_segments() { + // 1 line_segment + assert_json_snapshot!(ConvexHull::from_line_segments( + [LineSegment::default()].iter() + )); +} + +#[test] +fn creates_an_empty_convex_hull_from_2_line_segments() { + // 2 line_segments + assert_json_snapshot!(ConvexHull::from_line_segments( + [LineSegment::default(), LineSegment::default()].iter() + )); +} + +#[test] +fn orders_points_correctly_from_triangle_line_segments() { + let polygon = Polygon::default().with_shape(Shape::Triangle).at_center(); + + assert_json_snapshot!(ConvexHull::from_line_segments(polygon.line_segments.iter())); +} + +#[test] +fn orders_points_correctly_from_square_line_segments() { + let polygon = Polygon::default().with_shape(Shape::Square).at_center(); + + assert_json_snapshot!(ConvexHull::from_line_segments(polygon.line_segments.iter())); +} + +#[test] +fn orders_points_correctly_from_hexagon_line_segments() { + let polygon = Polygon::default().with_shape(Shape::Hexagon).at_center(); + + assert_json_snapshot!(ConvexHull::from_line_segments(polygon.line_segments.iter())); +} + +#[test] +fn orders_points_correctly_from_octagon_line_segments() { + let polygon = Polygon::default().with_shape(Shape::Hexagon).at_center(); + + assert_json_snapshot!(ConvexHull::from_line_segments(polygon.line_segments.iter())); +} + +#[test] +fn orders_points_correctly_from_dodecagon_line_segments() { + let polygon = Polygon::default().with_shape(Shape::Dodecagon).at_center(); + + assert_json_snapshot!(ConvexHull::from_line_segments(polygon.line_segments.iter())); +} + +#[test] +fn finds_line_segments_for_vector_terminal_points_top() { + // Lets use a square which is drawn with a rotation that means + // it's sides are flat along the top and bottom. + let polygon = Polygon::default().with_shape(Shape::Square).at_center(); + let convex_hull = ConvexHull::from_line_segments(polygon.line_segments.iter()); + + assert_json_snapshot!(convex_hull.find_line_segment( + Point::default() + .with_x(0.0) + .with_y(-1.0) + .rotate(f64::consts::PI * 0.0, None) + )); +} + +#[test] +fn finds_line_segments_for_vector_terminal_points_right() { + let polygon = Polygon::default().with_shape(Shape::Square).at_center(); + let convex_hull = ConvexHull::from_line_segments(polygon.line_segments.iter()); + + assert_json_snapshot!(convex_hull.find_line_segment( + Point::default() + .with_x(0.0) + .with_y(-1.0) + .rotate(f64::consts::PI * 0.5, None) + )); +} + +#[test] +fn finds_line_segments_for_vector_terminal_points_bottom() { + let polygon = Polygon::default().with_shape(Shape::Square).at_center(); + let convex_hull = ConvexHull::from_line_segments(polygon.line_segments.iter()); + + assert_json_snapshot!(convex_hull.find_line_segment( + Point::default() + .with_x(0.0) + .with_y(-1.0) + .rotate(f64::consts::PI, None) + )); +} + +#[test] +fn finds_line_segments_for_vector_terminal_points_left() { + let polygon = Polygon::default().with_shape(Shape::Square).at_center(); + let convex_hull = ConvexHull::from_line_segments(polygon.line_segments.iter()); + + assert_json_snapshot!(convex_hull.find_line_segment( + Point::default() + .with_x(0.0) + .with_y(-1.0) + .rotate(f64::consts::PI * 1.5, None) + )); +} diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/line_segment.rs b/workspaces/tilings/src-rust/tiling/src/geometry/line_segment.rs index 42ee3eca..9cf8b730 100644 --- a/workspaces/tilings/src-rust/tiling/src/geometry/line_segment.rs +++ b/workspaces/tilings/src-rust/tiling/src/geometry/line_segment.rs @@ -28,18 +28,18 @@ pub enum LineSegmentOrigin { #[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)] #[typeshare] pub struct LineSegment { - pub p1: Point, - pub p2: Point, + pub start: Point, + pub end: Point, } impl LineSegment { pub fn with_start(mut self, p1: Point) -> Self { - self.p1 = p1; + self.start = p1; self } pub fn with_end(mut self, p2: Point) -> Self { - self.p2 = p2; + self.end = p2; self } @@ -47,52 +47,52 @@ impl LineSegment { let mut min = Point::at(0.0, 0.0); let mut max = Point::at(0.0, 0.0); - if self.p1.x < self.p2.x { - min.x = self.p1.x; - max.x = self.p2.x; + if self.start.x < self.end.x { + min.x = self.start.x; + max.x = self.end.x; } else { - min.x = self.p2.x; - max.x = self.p1.x; + min.x = self.end.x; + max.x = self.start.x; } - if self.p1.y < self.p2.y { - min.y = self.p1.y; - max.y = self.p2.y; + if self.start.y < self.end.y { + min.y = self.start.y; + max.y = self.end.y; } else { - min.y = self.p2.y; - max.y = self.p1.y; + min.y = self.end.y; + max.y = self.start.y; } BBox::from_min_max(min, max) } pub fn mid_point(&self) -> Point { - get_point_at_percentage(self.p1, self.p2, 0.5, 0.0) + get_point_at_percentage(self.start, self.end, 0.5, 0.0) } pub fn length(&self) -> f64 { - self.p1.distance_to(&self.p2) + self.start.distance_to(&self.end) } pub fn theta(&self) -> f64 { - self.p2.radian_to(&self.p1) + self.end.radian_to(&self.start) } pub fn flip(&self) -> Self { - Self::default().with_start(self.p2).with_end(self.p1) + Self::default().with_start(self.end).with_end(self.start) } pub fn is_connected(&self, other: &Self) -> bool { - self.p2 == other.p1 + self.end == other.start } pub fn get_point_delta(&self, point: &Point) -> isize { let x = point.x; let y = point.y; - let x0 = self.p1.x; - let y0 = self.p1.y; - let x1 = self.p2.x; - let y1 = self.p2.y; + let x0 = self.start.x; + let y0 = self.start.y; + let x1 = self.end.x; + let y1 = self.end.y; let pos = (y - y0) * (x1 - x0) - (x - x0) * (y1 - y0); @@ -104,33 +104,33 @@ impl LineSegment { } pub fn get_point_at_percentage(&self, percentage: f64, offset: f64) -> Point { - get_point_at_percentage(self.p1, self.p2, percentage, offset) + get_point_at_percentage(self.start, self.end, percentage, offset) } pub fn set_length(&self, length: f64, origin: LineSegmentOrigin) -> Self { - let dx = self.p2.x - self.p1.x; - let dy = self.p2.y - self.p1.y; + let dx = self.end.x - self.start.x; + let dy = self.end.y - self.start.y; let theta = dy.atan2(dx); - let mut p1 = self.p1; - let mut p2 = self.p2; + let mut p1 = self.start; + let mut p2 = self.end; match origin { LineSegmentOrigin::Start => { - p2.x = self.p1.x + length * theta.cos(); - p2.y = self.p1.y + length * theta.sin(); + p2.x = self.start.x + length * theta.cos(); + p2.y = self.start.y + length * theta.sin(); } LineSegmentOrigin::Middle => { let half_length = length / 2.0; - p1.x = self.p2.x - half_length * theta.cos(); - p1.y = self.p2.y - half_length * theta.sin(); - p2.x = self.p1.x + half_length * theta.cos(); - p2.y = self.p1.y + half_length * theta.sin(); + p1.x = self.end.x - half_length * theta.cos(); + p1.y = self.end.y - half_length * theta.sin(); + p2.x = self.start.x + half_length * theta.cos(); + p2.y = self.start.y + half_length * theta.sin(); } LineSegmentOrigin::End => { - p1.x = self.p2.x - length * theta.cos(); - p1.y = self.p2.y - length * theta.sin(); + p1.x = self.end.x - length * theta.cos(); + p1.y = self.end.y - length * theta.sin(); } } @@ -142,14 +142,14 @@ impl LineSegment { let origin = origin.or(Some(&mid_point)); Self::default() - .with_start(self.p1.rotate(theta, origin)) - .with_end(self.p2.rotate(theta, origin)) + .with_start(self.start.rotate(theta, origin)) + .with_end(self.end.rotate(theta, origin)) } pub fn scale(&self, scale: f64) -> Self { Self::default() - .with_start(self.p1.scale(scale)) - .with_end(self.p2.scale(scale)) + .with_start(self.start.scale(scale)) + .with_end(self.end.scale(scale)) } pub fn extend_to_bbox(&self, bbox: &BBox, extend_start: bool, extend_end: bool) -> Self { @@ -157,7 +157,7 @@ impl LineSegment { let bbox_max = bbox.max(); let (x1, y1, x2, y2) = extend_line_segment( - (self.p1.x, self.p1.y, self.p2.x, self.p2.y), + (self.start.x, self.start.y, self.end.x, self.end.y), (bbox_min.x, bbox_min.y, bbox_max.x, bbox_max.y), extend_start, extend_end, @@ -168,56 +168,58 @@ impl LineSegment { .with_end(Point::at(x2, y2)) } - pub fn intersects(&self, other: &LineSegment) -> bool { + pub fn get_intersection_point(&self, other: &LineSegment) -> Option { // If any of the points are the same, then we say // they don't intersect. This is because line segments // make up shapes, and 2 sibling line segments would share // the same point, so we don't want to count that as an // intersection - if self.p1 == other.p1 || self.p1 == other.p2 || self.p2 == other.p1 || self.p2 == other.p2 { - return false; + if self.start == other.start || self.start == other.end { + return Some(self.start); } - let a = self.p1.x; - let b = self.p1.y; - let c = self.p2.x; - let d = self.p2.y; - let p = other.p1.x; - let q = other.p1.y; - let r = other.p2.x; - let s = other.p2.y; + if self.end == other.start || self.end == other.end { + return Some(self.end); + } + + let a = self.start.x; + let b = self.start.y; + let c = self.end.x; + let d = self.end.y; + let p = other.start.x; + let q = other.start.y; + let r = other.end.x; + let s = other.end.y; let denominator = (c - a) * (s - q) - (r - p) * (d - b); if denominator == 0.0 { - return false; + return None; } let y_numerator = ((s - q) * (r - a) + (p - r) * (s - b)) / denominator; let x_numerator = ((b - d) * (r - a) + (c - a) * (s - b)) / denominator; - (0.0 < y_numerator) && (y_numerator < 1.0) && (0.0 < x_numerator) && (x_numerator < 1.0) + if (0.0 < y_numerator) && (y_numerator < 1.0) && (0.0 < x_numerator) && (x_numerator < 1.0) { + return Some(Point::at( + a + x_numerator * (c - a), + b + y_numerator * (d - b), + )); + } + + None } - // pub fn intersects_bbox(&self, other: &BBox) -> bool { - // let top_line_segment = LineSegment::default() - // .with_start(other.min) - // .with_end(Point::at(other.max.x, other.min.y)); - // let right_line_segment = LineSegment::default() - // .with_start(Point::at(other.max.x, other.min.y)) - // .with_end(other.max); - // let bottom_line_segment = LineSegment::default() - // .with_start(other.max) - // .with_end(Point::at(other.min.x, other.max.y)); - // let left_line_segment = LineSegment::default() - // .with_start(other.min) - // .with_end(Point::at(other.min.x, other.max.y)); - - // self.intersects(&top_line_segment) - // || self.intersects(&right_line_segment) - // || self.intersects(&bottom_line_segment) - // || self.intersects(&left_line_segment) - // } + pub fn is_intersection_with_polygon_line_segment(&self, other: &LineSegment) -> bool { + let intersection_point = self.get_intersection_point(other); + + match intersection_point { + Some(p) if p == self.start => false, + Some(p) if p == self.end => false, + Some(_) => true, + None => false, + } + } } impl Ord for LineSegment { @@ -260,12 +262,12 @@ impl Eq for LineSegment {} impl PartialEq for LineSegment { fn eq(&self, other: &Self) -> bool { - self.p1 == other.p1 && self.p2 == other.p2 + self.start == other.start && self.end == other.end } } impl From for Vec { fn from(line_segment: LineSegment) -> Self { - vec![line_segment.p1, line_segment.p2] + vec![line_segment.start, line_segment.end] } } diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/line_segment_tests.rs b/workspaces/tilings/src-rust/tiling/src/geometry/line_segment_tests.rs index aa4dc1a3..3c4d58c6 100644 --- a/workspaces/tilings/src-rust/tiling/src/geometry/line_segment_tests.rs +++ b/workspaces/tilings/src-rust/tiling/src/geometry/line_segment_tests.rs @@ -60,7 +60,7 @@ fn ordering() { } #[test] -fn intersects() { +fn is_intersection_with_polygon_line_segment() { let line_segment1 = LineSegment::default() .with_start(Point::at(0.0, 0.0)) .with_end(Point::at(1.0, 1.0)); @@ -68,7 +68,7 @@ fn intersects() { .with_start(Point::at(0.0, 1.0)) .with_end(Point::at(1.0, 0.0)); - assert!(line_segment1.intersects(&line_segment2)); + assert!(line_segment1.is_intersection_with_polygon_line_segment(&line_segment2)); } #[test] diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/point.rs b/workspaces/tilings/src-rust/tiling/src/geometry/point.rs index 4e2a81ce..cd600357 100644 --- a/workspaces/tilings/src-rust/tiling/src/geometry/point.rs +++ b/workspaces/tilings/src-rust/tiling/src/geometry/point.rs @@ -55,6 +55,10 @@ impl Point { get_radians_for_x_y(self.x - point.x, self.y - point.y) } + pub fn radian_to_center(&self) -> f64 { + self.radian_to(&Self::at(0.0, 0.0)) + } + pub fn multiply(&self, scalar: f64) -> Self { Self::at(self.x * scalar, self.y * scalar).with_index(self.index) } diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/polygon.rs b/workspaces/tilings/src-rust/tiling/src/geometry/polygon.rs index 4a581353..1347bcca 100644 --- a/workspaces/tilings/src-rust/tiling/src/geometry/polygon.rs +++ b/workspaces/tilings/src-rust/tiling/src/geometry/polygon.rs @@ -105,11 +105,11 @@ impl Polygon { pub fn on_line_segment(self, line_segment: &LineSegment, point_index_offset: u8) -> Self { let sides = self.shape as u8; - let length = line_segment.p1.distance_to(&line_segment.p2); + let length = line_segment.start.distance_to(&line_segment.end); let shape_radians = self.shape.get_internal_angle(); - let mut theta = line_segment.p1.radian_to(&line_segment.p2) + shape_radians + PI * 0.5; + let mut theta = line_segment.start.radian_to(&line_segment.end) + shape_radians + PI * 0.5; - let mut points = vec![line_segment.p1, line_segment.p2]; + let mut points = vec![line_segment.start, line_segment.end]; points.reserve_exact(sides as usize); for i in 2..sides { @@ -174,7 +174,7 @@ impl Polygon { points.reserve_exact(self.points.len()); for point in &self.points { - points.push(point.reflect(&line_segment.p1, &line_segment.p2)); + points.push(point.reflect(&line_segment.start, &line_segment.end)); } self.with_points(points) diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__creates_an_empty_convex_hull_from_0_line_segments.snap b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__creates_an_empty_convex_hull_from_0_line_segments.snap new file mode 100644 index 00000000..33322a15 --- /dev/null +++ b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__creates_an_empty_convex_hull_from_0_line_segments.snap @@ -0,0 +1,7 @@ +--- +source: workspaces/tilings/src-rust/tiling/src/geometry/./convex_hull_tests.rs +expression: "ConvexHull::from_line_segments([].iter())" +--- +{ + "points": [] +} diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__creates_an_empty_convex_hull_from_1_line_segments.snap b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__creates_an_empty_convex_hull_from_1_line_segments.snap new file mode 100644 index 00000000..6ffde0a4 --- /dev/null +++ b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__creates_an_empty_convex_hull_from_1_line_segments.snap @@ -0,0 +1,7 @@ +--- +source: workspaces/tilings/src-rust/tiling/src/geometry/./convex_hull_tests.rs +expression: "ConvexHull::from_line_segments([LineSegment::default()].iter())" +--- +{ + "points": [] +} diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__creates_an_empty_convex_hull_from_2_line_segments.snap b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__creates_an_empty_convex_hull_from_2_line_segments.snap new file mode 100644 index 00000000..cf5d2c53 --- /dev/null +++ b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__creates_an_empty_convex_hull_from_2_line_segments.snap @@ -0,0 +1,7 @@ +--- +source: workspaces/tilings/src-rust/tiling/src/geometry/./convex_hull_tests.rs +expression: "ConvexHull::from_line_segments([LineSegment::default(),\nLineSegment::default()].iter())" +--- +{ + "points": [] +} diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__creates_an_empty_convex_hull_from_lt_3_line_segments.snap b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__creates_an_empty_convex_hull_from_lt_3_line_segments.snap new file mode 100644 index 00000000..33322a15 --- /dev/null +++ b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__creates_an_empty_convex_hull_from_lt_3_line_segments.snap @@ -0,0 +1,7 @@ +--- +source: workspaces/tilings/src-rust/tiling/src/geometry/./convex_hull_tests.rs +expression: "ConvexHull::from_line_segments([].iter())" +--- +{ + "points": [] +} diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__finds_line_segments_for_vector_terminal_points_bottom.snap b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__finds_line_segments_for_vector_terminal_points_bottom.snap new file mode 100644 index 00000000..5bcd9c22 --- /dev/null +++ b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__finds_line_segments_for_vector_terminal_points_bottom.snap @@ -0,0 +1,16 @@ +--- +source: workspaces/tilings/src-rust/tiling/src/geometry/./convex_hull_tests.rs +expression: "convex_hull.find_line_segment(Point::default().with_x(0.0).with_y(-1.0).rotate(f64::consts::PI,\nNone))" +--- +{ + "start": { + "x": 0.7071067811865476, + "y": 0.7071067811865475, + "index": 0 + }, + "end": { + "x": -0.7071067811865475, + "y": 0.7071067811865476, + "index": 1 + } +} diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__finds_line_segments_for_vector_terminal_points_left.snap b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__finds_line_segments_for_vector_terminal_points_left.snap new file mode 100644 index 00000000..3657f8ab --- /dev/null +++ b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__finds_line_segments_for_vector_terminal_points_left.snap @@ -0,0 +1,16 @@ +--- +source: workspaces/tilings/src-rust/tiling/src/geometry/./convex_hull_tests.rs +expression: "convex_hull.find_line_segment(Point::default().with_x(0.0).with_y(-1.0).rotate(f64::consts::PI\n* 1.5, None))" +--- +{ + "start": { + "x": -0.7071067811865475, + "y": 0.7071067811865476, + "index": 1 + }, + "end": { + "x": -0.7071067811865477, + "y": -0.7071067811865474, + "index": 2 + } +} diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__finds_line_segments_for_vector_terminal_points_right.snap b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__finds_line_segments_for_vector_terminal_points_right.snap new file mode 100644 index 00000000..13cefcbb --- /dev/null +++ b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__finds_line_segments_for_vector_terminal_points_right.snap @@ -0,0 +1,16 @@ +--- +source: workspaces/tilings/src-rust/tiling/src/geometry/./convex_hull_tests.rs +expression: "convex_hull.find_line_segment(Point::default().with_x(0.0).with_y(-1.0).rotate(f64::consts::PI\n* 0.5, None))" +--- +{ + "start": { + "x": 0.7071067811865474, + "y": -0.7071067811865477, + "index": 3 + }, + "end": { + "x": 0.7071067811865476, + "y": 0.7071067811865475, + "index": 0 + } +} diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__finds_line_segments_for_vector_terminal_points_top.snap b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__finds_line_segments_for_vector_terminal_points_top.snap new file mode 100644 index 00000000..ba8a5adb --- /dev/null +++ b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__finds_line_segments_for_vector_terminal_points_top.snap @@ -0,0 +1,16 @@ +--- +source: workspaces/tilings/src-rust/tiling/src/geometry/./convex_hull_tests.rs +expression: "convex_hull.find_line_segment(Point::default().with_x(0.0).with_y(-1.0).rotate(f64::consts::PI\n* 0.0, None))" +--- +{ + "start": { + "x": -0.7071067811865477, + "y": -0.7071067811865474, + "index": 2 + }, + "end": { + "x": 0.7071067811865474, + "y": -0.7071067811865477, + "index": 3 + } +} diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__orders_points_correctly_from_dodecagon_line_segments.snap b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__orders_points_correctly_from_dodecagon_line_segments.snap new file mode 100644 index 00000000..81c0c171 --- /dev/null +++ b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__orders_points_correctly_from_dodecagon_line_segments.snap @@ -0,0 +1,68 @@ +--- +source: workspaces/tilings/src-rust/tiling/src/geometry/./convex_hull_tests.rs +expression: "ConvexHull::from_line_segments(polygon.line_segments.iter())" +--- +{ + "points": [ + { + "x": -0.2588190451025207, + "y": 0.9659258262890683, + "index": 3 + }, + { + "x": -0.7071067811865474, + "y": 0.7071067811865477, + "index": 4 + }, + { + "x": -0.9659258262890682, + "y": 0.2588190451025212, + "index": 5 + }, + { + "x": -0.9659258262890683, + "y": -0.25881904510252063, + "index": 6 + }, + { + "x": -0.7071067811865477, + "y": -0.7071067811865474, + "index": 7 + }, + { + "x": -0.2588190451025213, + "y": -0.9659258262890681, + "index": 8 + }, + { + "x": 0.2588190451025206, + "y": -0.9659258262890683, + "index": 9 + }, + { + "x": 0.707106781186547, + "y": -0.7071067811865481, + "index": 10 + }, + { + "x": 0.9659258262890681, + "y": -0.2588190451025213, + "index": 11 + }, + { + "x": 0.9659258262890683, + "y": 0.25881904510252074, + "index": 0 + }, + { + "x": 0.7071067811865476, + "y": 0.7071067811865475, + "index": 1 + }, + { + "x": 0.2588190451025209, + "y": 0.9659258262890682, + "index": 2 + } + ] +} diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__orders_points_correctly_from_hexagon_line_segments.snap b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__orders_points_correctly_from_hexagon_line_segments.snap new file mode 100644 index 00000000..eb4f9a88 --- /dev/null +++ b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__orders_points_correctly_from_hexagon_line_segments.snap @@ -0,0 +1,38 @@ +--- +source: workspaces/tilings/src-rust/tiling/src/geometry/./convex_hull_tests.rs +expression: "ConvexHull::from_line_segments(polygon.line_segments.iter())" +--- +{ + "points": [ + { + "x": -0.49999999999999983, + "y": 0.8660254037844387, + "index": 2 + }, + { + "x": -1.0, + "y": 0.00000000000000012246467991473532, + "index": 3 + }, + { + "x": -0.5000000000000004, + "y": -0.8660254037844384, + "index": 4 + }, + { + "x": 0.49999999999999933, + "y": -0.866025403784439, + "index": 5 + }, + { + "x": 1.0, + "y": 0.0, + "index": 0 + }, + { + "x": 0.5000000000000001, + "y": 0.8660254037844386, + "index": 1 + } + ] +} diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__orders_points_correctly_from_line_segments.snap b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__orders_points_correctly_from_line_segments.snap new file mode 100644 index 00000000..39d7534d --- /dev/null +++ b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__orders_points_correctly_from_line_segments.snap @@ -0,0 +1,28 @@ +--- +source: workspaces/tilings/src-rust/tiling/src/geometry/./convex_hull_tests.rs +expression: "ConvexHull::from_line_segments(square.line_segments.iter())" +--- +{ + "points": [ + { + "x": -0.7071067811865475, + "y": 0.7071067811865476, + "index": 1 + }, + { + "x": -0.7071067811865477, + "y": -0.7071067811865474, + "index": 2 + }, + { + "x": 0.7071067811865474, + "y": -0.7071067811865477, + "index": 3 + }, + { + "x": 0.7071067811865476, + "y": 0.7071067811865475, + "index": 0 + } + ] +} diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__orders_points_correctly_from_octagon_line_segments.snap b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__orders_points_correctly_from_octagon_line_segments.snap new file mode 100644 index 00000000..eb4f9a88 --- /dev/null +++ b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__orders_points_correctly_from_octagon_line_segments.snap @@ -0,0 +1,38 @@ +--- +source: workspaces/tilings/src-rust/tiling/src/geometry/./convex_hull_tests.rs +expression: "ConvexHull::from_line_segments(polygon.line_segments.iter())" +--- +{ + "points": [ + { + "x": -0.49999999999999983, + "y": 0.8660254037844387, + "index": 2 + }, + { + "x": -1.0, + "y": 0.00000000000000012246467991473532, + "index": 3 + }, + { + "x": -0.5000000000000004, + "y": -0.8660254037844384, + "index": 4 + }, + { + "x": 0.49999999999999933, + "y": -0.866025403784439, + "index": 5 + }, + { + "x": 1.0, + "y": 0.0, + "index": 0 + }, + { + "x": 0.5000000000000001, + "y": 0.8660254037844386, + "index": 1 + } + ] +} diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__orders_points_correctly_from_square_line_segments.snap b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__orders_points_correctly_from_square_line_segments.snap new file mode 100644 index 00000000..615d0869 --- /dev/null +++ b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__orders_points_correctly_from_square_line_segments.snap @@ -0,0 +1,28 @@ +--- +source: workspaces/tilings/src-rust/tiling/src/geometry/./convex_hull_tests.rs +expression: "ConvexHull::from_line_segments(polygon.line_segments.iter())" +--- +{ + "points": [ + { + "x": -0.7071067811865475, + "y": 0.7071067811865476, + "index": 1 + }, + { + "x": -0.7071067811865477, + "y": -0.7071067811865474, + "index": 2 + }, + { + "x": 0.7071067811865474, + "y": -0.7071067811865477, + "index": 3 + }, + { + "x": 0.7071067811865476, + "y": 0.7071067811865475, + "index": 0 + } + ] +} diff --git a/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__orders_points_correctly_from_triangle_line_segments.snap b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__orders_points_correctly_from_triangle_line_segments.snap new file mode 100644 index 00000000..1cc827e4 --- /dev/null +++ b/workspaces/tilings/src-rust/tiling/src/geometry/snapshots/tiling__geometry__convex_hull__tests__orders_points_correctly_from_triangle_line_segments.snap @@ -0,0 +1,23 @@ +--- +source: workspaces/tilings/src-rust/tiling/src/geometry/./convex_hull_tests.rs +expression: "ConvexHull::from_line_segments(polygon.line_segments.iter())" +--- +{ + "points": [ + { + "x": 0.00000000000000027755575615628914, + "y": -0.00000000000000000000000000000001699538841808614, + "index": 0 + }, + { + "x": -0.00000000000000010605752387249068, + "y": -1.7320508075688772, + "index": 1 + }, + { + "x": 1.5000000000000002, + "y": -0.8660254037844394, + "index": 2 + } + ] +} diff --git a/workspaces/tilings/src-rust/tiling/src/utils/math.rs b/workspaces/tilings/src-rust/tiling/src/utils/math.rs index 67227968..0ddf4cb4 100644 --- a/workspaces/tilings/src-rust/tiling/src/utils/math.rs +++ b/workspaces/tilings/src-rust/tiling/src/utils/math.rs @@ -20,6 +20,24 @@ pub fn compare_radians(a: f64, b: f64) -> std::cmp::Ordering { compare_f64(a, b, PRECISION_RADIAN) } +/// Returns true if a <= b <= c. This assumes that +/// we're always working with normalized radians (0.0 to 2PI) +/// and that a and c are clockwise. +/// +/// It handles cases where a <= PI * 2.0 and c >= 0.0 +pub fn is_between_radians(a: f64, b: f64, c: f64) -> bool { + let ab_comp = compare_radians(a, b); + let bc_comp = compare_radians(b, c); + + if compare_radians(a, c) == Ordering::Greater { + return ab_comp == Ordering::Less || bc_comp == Ordering::Less; + } + + ab_comp == Ordering::Equal + || bc_comp == Ordering::Equal + || (ab_comp == Ordering::Less && bc_comp == Ordering::Less) +} + pub fn compare_coordinate(a: f64, b: f64) -> std::cmp::Ordering { compare_f64(a, b, PRECISION_COORDINATE) } diff --git a/workspaces/tilings/src-rust/tiling/src/validation/validator.rs b/workspaces/tilings/src-rust/tiling/src/validation/validator.rs index 6e82ddc2..99961764 100644 --- a/workspaces/tilings/src-rust/tiling/src/validation/validator.rs +++ b/workspaces/tilings/src-rust/tiling/src/validation/validator.rs @@ -50,7 +50,7 @@ impl Validator { // If a line segment intersects with another line segment // then it's overlapping with another line segment for nearby_line_segment in nearby_line_segments { - if line_segment.intersects(nearby_line_segment) { + if line_segment.is_intersection_with_polygon_line_segment(nearby_line_segment) { return Err(Error::Overlaps {}); } } diff --git a/workspaces/tilings/src/Presentation/ArrangementInformation/ArrangementCard.tsx b/workspaces/tilings/src/Presentation/ArrangementInformation/ArrangementCard.tsx index e21f63e9..99b5323b 100644 --- a/workspaces/tilings/src/Presentation/ArrangementInformation/ArrangementCard.tsx +++ b/workspaces/tilings/src/Presentation/ArrangementInformation/ArrangementCard.tsx @@ -1,15 +1,19 @@ -import { PatternBackground } from '@hogg/common'; +import { DeepPartial, PatternBackground } from '@hogg/common'; import { Box, Text } from 'preshape'; import TilingRenderer, { TilingRendererProps } from '../../TilingRenderer'; -import { ColorMode, Options } from '../../types'; +import { ColorMode, Layer, Options, ScaleMode } from '../../types'; export type ArrangementCardProps = Omit & { label: string; notation: string; }; -const options: Partial = { +const options: DeepPartial = { colorMode: ColorMode.None, + scaleMode: ScaleMode.Contain, + showLayers: { + [Layer.ConvexHull]: false, + }, }; export default function ArrangementCard({ diff --git a/workspaces/tilings/src/Presentation/Renderer/Renderer.tsx b/workspaces/tilings/src/Presentation/Renderer/Renderer.tsx index 9eba3878..5bb62882 100644 --- a/workspaces/tilings/src/Presentation/Renderer/Renderer.tsx +++ b/workspaces/tilings/src/Presentation/Renderer/Renderer.tsx @@ -1,3 +1,4 @@ +import { DeepPartial } from '@hogg/common'; import { useWasmApi } from '@hogg/wasm'; import merge from 'lodash/merge'; import { @@ -20,7 +21,7 @@ import { export type RendererProps = { expansionPhases?: number; - options?: Partial; + options?: DeepPartial; scale?: number; validations?: Flag[]; withPlayer?: boolean; diff --git a/workspaces/tilings/src/Presentation/Renderer/defaultOptions.ts b/workspaces/tilings/src/Presentation/Renderer/defaultOptions.ts index 32889b0c..a6b10929 100644 --- a/workspaces/tilings/src/Presentation/Renderer/defaultOptions.ts +++ b/workspaces/tilings/src/Presentation/Renderer/defaultOptions.ts @@ -20,7 +20,7 @@ export const defaultOptions: Pick< [Layer.ConvexHull]: false, [Layer.GridLineSegment]: false, [Layer.GridPolygon]: false, - [Layer.PlaneOutline]: true, + [Layer.PlaneOutline]: false, [Layer.ShapeBorder]: true, [Layer.ShapeFill]: true, [Layer.Transform]: false, diff --git a/workspaces/tilings/src/Presentation/Settings/useSettingsContext.ts b/workspaces/tilings/src/Presentation/Settings/useSettingsContext.ts index 64ce0b75..58186d25 100644 --- a/workspaces/tilings/src/Presentation/Settings/useSettingsContext.ts +++ b/workspaces/tilings/src/Presentation/Settings/useSettingsContext.ts @@ -10,7 +10,6 @@ export type Settings = { colorMode: ColorMode; expansionPhases: number; scaleMode: ScaleMode; - scaleSize: number; showLayers: Record; }; diff --git a/workspaces/tilings/src/Presentation/index.tsx b/workspaces/tilings/src/Presentation/index.tsx index 789ab080..42115c1e 100644 --- a/workspaces/tilings/src/Presentation/index.tsx +++ b/workspaces/tilings/src/Presentation/index.tsx @@ -16,9 +16,8 @@ import Settings from './Settings/Settings'; import SettingsProvider from './Settings/SettingsProvider'; import { useSettingsContext } from './Settings/useSettingsContext'; -// const DEFAULT_NOTATION = '3-4,3-3,3,3/m60/m(c3)'; +const DEFAULT_NOTATION = '3-4,3-3,3,3/m60/m(c3)'; // const DEFAULT_NOTATION = '3-4,3-3,3,3/r60/r(h3)'; -const DEFAULT_NOTATION = '6/m90/r(h6)'; function PresentationInner(props: RendererProps) { const { diff --git a/workspaces/tilings/src/types.ts b/workspaces/tilings/src/types.ts index 25335635..990713bc 100644 --- a/workspaces/tilings/src/types.ts +++ b/workspaces/tilings/src/types.ts @@ -277,8 +277,8 @@ export interface BBox { } export interface LineSegment { - p1: Point; - p2: Point; + start: Point; + end: Point; } export enum Offset {