This repository has no description
0

Configure Feed

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

1use crate::{Angle, Containable, Fill, Filter, Point, Region, Transformation}; 2use anyhow::anyhow; 3use itertools::Itertools; 4use std::fmt::Display; 5#[cfg(feature = "web")] 6use wasm_bindgen::prelude::*; 7 8use super::{Color, fill::FillOperations}; 9 10#[derive(Debug, Clone, PartialEq, Eq)] 11pub enum LineSegment { 12 Straight(Point), 13 InwardCurve(Point), 14 OutwardCurve(Point), 15} 16 17#[derive(Debug, Clone)] 18pub enum Object { 19 Polygon(Point, Vec<LineSegment>), 20 Line(Point, Point, f32), 21 CurveOutward(Point, Point, f32), 22 CurveInward(Point, Point, f32), 23 SmallCircle(Point), 24 BigDot(Point), 25 Dot(Point), 26 BigCircle(Point), 27 Text(Point, String, f32), 28 CenteredText(Point, String, f32), 29 // FittedText(Region, String), 30 Rectangle(Point, Point), 31 Image(Region, String), 32 RawSVG(String), 33 // Tiling(Region, Box<Object>), 34} 35 36impl Object { 37 pub fn filled(self, fill: Fill) -> ColoredObject { 38 ColoredObject::from((self, Some(fill))) 39 } 40 41 pub fn colored(self, color: Color) -> ColoredObject { 42 ColoredObject::from((self, None)).colored(color) 43 } 44 45 pub fn filtered(self, filter: Filter) -> ColoredObject { 46 ColoredObject::from((self, None)).filtered(filter) 47 } 48 49 pub fn transform(self, transformation: Transformation) -> ColoredObject { 50 ColoredObject::from((self, None)).transformed(transformation) 51 } 52} 53 54#[derive(Debug, Clone)] 55pub struct ColoredObject { 56 pub object: Object, 57 pub fill: Option<Fill>, 58 pub filters: Vec<Filter>, 59 pub transformations: Vec<Transformation>, 60 pub tags: Vec<String>, 61 pub clip_to: Option<Region>, 62} 63 64impl ColoredObject { 65 pub fn filtered(mut self, filter: Filter) -> Self { 66 self.filters.push(filter); 67 self 68 } 69 70 pub fn transformed(mut self, transformation: Transformation) -> Self { 71 self.transformations.push(transformation); 72 self 73 } 74 75 pub fn filled(mut self, fill: Fill) -> Self { 76 self.fill = Some(fill); 77 self 78 } 79 80 pub fn colored(mut self, color: Color) -> Self { 81 self.fill = Some(Fill::Solid(color)); 82 self 83 } 84 85 pub fn opacified(mut self, opacity: f32) -> Self { 86 if let Some(fill) = &mut self.fill { 87 *fill = fill.opacify(opacity); 88 } 89 self 90 } 91 92 pub fn clipped_to(mut self, region: impl Into<Region>) -> Self { 93 self.clip_to = Some(region.into()); 94 self 95 } 96 97 pub fn clear_filters(&mut self) { 98 self.filters.clear(); 99 } 100 101 pub fn refill(&mut self, fill: Fill) { 102 self.fill = Some(fill); 103 } 104 105 pub fn recolor(&mut self, color: Color) { 106 self.fill = Some(Fill::Solid(color)) 107 } 108 109 pub fn filter(&mut self, filter: Filter) { 110 self.filters.push(filter) 111 } 112 113 pub fn rotate(&mut self, angle: Angle) { 114 self.transformations 115 .push(Transformation::Rotate(angle.degrees())) 116 } 117 118 pub fn set_rotation(&mut self, angle: Angle) { 119 self.transformations 120 .retain(|t| !matches!(t, Transformation::Rotate(_))); 121 self.transformations 122 .push(Transformation::Rotate(angle.degrees())) 123 } 124 125 pub fn region(&self) -> Region { 126 self.object.region() 127 } 128 129 pub fn tag(&mut self, tag: impl Display) { 130 self.tags.push(format!("{tag}")); 131 } 132 133 pub fn remove_tag(&mut self, tag: impl Display) { 134 let tag_str = format!("{tag}"); 135 self.tags.retain(|t| t != &tag_str); 136 } 137 138 pub fn tagged(mut self, tag: impl Display) -> Self { 139 self.tags.push(format!("{tag}")); 140 self 141 } 142 143 pub fn has_tag(&self, tag: impl Display) -> bool { 144 let tag_str = format!("{tag}"); 145 self.tags.iter().any(|t| t == &tag_str) 146 } 147} 148 149impl std::fmt::Display for ColoredObject { 150 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 151 let ColoredObject { 152 object, 153 fill, 154 filters, 155 transformations, 156 tags, 157 clip_to, 158 } = self; 159 160 if fill.is_some() { 161 write!(f, "{:?} {:?}", fill.unwrap(), object)?; 162 } else { 163 write!(f, "transparent {:?}", object)?; 164 } 165 166 if !filters.is_empty() { 167 write!(f, " with filters {:?}", filters)?; 168 } 169 170 if !transformations.is_empty() { 171 write!(f, " with transformations {:?}", transformations)?; 172 } 173 174 if !tags.is_empty() { 175 write!(f, "{}", tags.iter().map(|t| format!("#{t}")).join(" "))?; 176 } 177 178 if let Some(clip_to) = clip_to { 179 write!(f, " (clipped to {:?})", clip_to)?; 180 } 181 182 Ok(()) 183 } 184} 185 186impl From<Object> for ColoredObject { 187 fn from(value: Object) -> Self { 188 ColoredObject { 189 object: value, 190 fill: None, 191 filters: vec![], 192 transformations: vec![], 193 tags: vec![], 194 clip_to: None, 195 } 196 } 197} 198 199impl From<(Object, Option<Fill>)> for ColoredObject { 200 fn from((object, fill): (Object, Option<Fill>)) -> Self { 201 ColoredObject { 202 object, 203 fill, 204 filters: vec![], 205 transformations: vec![], 206 tags: vec![], 207 clip_to: None, 208 } 209 } 210} 211 212#[cfg_attr(feature = "web", wasm_bindgen)] 213#[derive(Debug, Clone, Copy)] 214pub struct ObjectSizes { 215 pub empty_shape_stroke_width: f32, 216 pub small_circle_radius: f32, 217 pub dot_radius: f32, 218 pub default_line_width: f32, 219} 220 221impl Default for ObjectSizes { 222 fn default() -> Self { 223 Self { 224 empty_shape_stroke_width: 0.5, 225 small_circle_radius: 5.0, 226 dot_radius: 2.0, 227 default_line_width: 2.0, 228 } 229 } 230} 231 232impl Object { 233 pub fn translate(&mut self, dx: i32, dy: i32) { 234 match self { 235 Object::Polygon(start, lines) => { 236 start.translate(dx, dy); 237 for line in lines { 238 match line { 239 LineSegment::InwardCurve(anchor) 240 | LineSegment::OutwardCurve(anchor) 241 | LineSegment::Straight(anchor) => { 242 anchor.translate(dx, dy) 243 } 244 } 245 } 246 } 247 Object::Line(start, end, _) 248 | Object::CurveInward(start, end, _) 249 | Object::CurveOutward(start, end, _) 250 | Object::Rectangle(start, end) => { 251 start.translate(dx, dy); 252 end.translate(dx, dy); 253 } 254 Object::Text(anchor, _, _) 255 | Object::CenteredText(anchor, ..) 256 | Object::Dot(anchor) 257 | Object::BigDot(anchor) => anchor.translate(dx, dy), 258 Object::BigCircle(center) | Object::SmallCircle(center) => { 259 center.translate(dx, dy) 260 } 261 Object::Image(region, ..) => region.translate(dx, dy), 262 Object::RawSVG(_) => { 263 unimplemented!() 264 } 265 } 266 } 267 268 pub fn translate_with(&mut self, delta: (i32, i32)) { 269 self.translate(delta.0, delta.1) 270 } 271 272 pub fn teleport(&mut self, x: i32, y: i32) { 273 let (current_x, current_y) = self.region().start.xy::<i32>(); 274 let delta_x = x - current_x; 275 let delta_y = y - current_y; 276 self.translate(delta_x, delta_y); 277 } 278 279 pub fn teleport_with(&mut self, position: (i32, i32)) { 280 self.teleport(position.0, position.1) 281 } 282 283 pub fn region(&self) -> Region { 284 match self { 285 Object::Polygon(start, lines) => { 286 let mut region: Region = (start, start).into(); 287 for line in lines { 288 match line { 289 LineSegment::InwardCurve(anchor) 290 | LineSegment::OutwardCurve(anchor) 291 | LineSegment::Straight(anchor) => { 292 // println!( 293 // "extending region {} with {}", 294 // region, 295 // Region::from((start, anchor)) 296 // ); 297 region = *region.max(&(start, anchor).into()) 298 } 299 } 300 } 301 // println!("region for {:?} -> {}", self, region); 302 region 303 } 304 Object::Line(s, e, _) 305 | Object::CurveInward(s, e, _) 306 | Object::CurveOutward(s, e, _) => { 307 let (x1, y1, x2, y2) = (s.x(), s.y(), e.y(), e.x()); 308 309 let region = Region::new( 310 (x1.min(x2), y1.min(y2)), 311 (x1.max(x2), y1.max(y2)), 312 ) 313 .map_err(|e| { 314 anyhow!("Could not construct region of {self:?}: {e:?}") 315 }) 316 .unwrap(); 317 318 region.enlarged( 319 if region.width() > 1 { -1 } else { 0 }, 320 if region.height() > 1 { -1 } else { 0 }, 321 ) 322 } 323 Object::Rectangle(start, end) => { 324 Region::new(*start, *end).unwrap().enlarged(-1, -1) 325 } 326 Object::Text(anchor, _, _) 327 | Object::CenteredText(anchor, ..) 328 | Object::Dot(anchor) 329 | Object::BigDot(anchor) 330 | Object::SmallCircle(anchor) => anchor.region(), 331 Object::BigCircle(center) => center.region(), 332 Object::Image(region, ..) => *region, 333 Object::RawSVG(_) => { 334 unimplemented!() 335 } 336 } 337 } 338 339 pub fn fillable(&self) -> bool { 340 !matches!( 341 self, 342 Object::Line(..) | Object::CurveInward(..) | Object::CurveOutward(..) 343 ) 344 } 345 346 pub fn hatchable(&self) -> bool { 347 self.fillable() && !matches!(self, Object::Dot(..)) 348 } 349 350 pub fn point_is_on_line(self, point: Point) -> bool { 351 match (&self, point) { 352 (Object::Line(s, e, _), Point::Corner(x, y)) => { 353 if !self.region().contains(&point) { 354 return false; 355 } 356 357 let (sx, sy) = s.xy::<f32>(); 358 let (ex, ey) = e.xy::<f32>(); 359 360 let m = (ey - sy) / (ex - sx); 361 let p = sy - m * sx; 362 363 (m * x as f32 + p) as usize == y 364 } 365 (Object::Line(..), _) => panic!("Point type not supported"), 366 _ => panic!("{self:?} is not a line object"), 367 } 368 } 369 370 // 371 // ```rs 372 // use shapemaker::{Object::Line, Point::Corner}; 373 // let line = |x1: usize, y1: usize, x2: usize, y2: usize| Line(Corner(x1, y1), Corner(x2, y2), 1.0); 374 // assert!(line(1, 1, 4, 4).intersects_with(line(1, 4, 4, 1))); 375 // assert!(line(7, 6, 9, 7).intersects_with(line(7, 7, 9, 4))); 376 // ``` 377 pub fn intersects_with(&self, line: Object) -> bool { 378 match (self, &line) { 379 (&Object::Line(s1, e1, _), &Object::Line(s2, e2, _)) => { 380 let (dx1, dy1) = e1 - s1; 381 let (dx2, dy2) = e2 - s2; 382 let (dx3, dy3) = s2 - s1; 383 384 let det = dx1 * dy2 - dy1 * dx2; 385 386 let det1 = dx1 * dy3 - dx3 * dy1; 387 let det2 = dx2 * dy3 - dx3 * dy2; 388 389 if det == 0 { 390 if det1 != 0 || det2 != 0 { 391 return false; 392 } 393 394 let (x1, y1) = s1.xy::<isize>(); 395 let (x2, y2) = e1.xy::<isize>(); 396 let (x3, y3) = s2.xy::<isize>(); 397 398 if dx1 != 0 && (x1 < x3 && x3 < x2 || x1 > x3 && x3 > x2) { 399 return true; 400 } 401 402 if dx1 == 0 && (y1 < y3 && y3 < y2 || y1 > y3 && y3 > y2) { 403 return true; 404 } 405 406 return false; 407 } 408 409 let frac_less_than_one = |num: isize, den: isize| { 410 if num.signum() != den.signum() { 411 return false; 412 } 413 414 num.abs() <= den.abs() 415 }; 416 417 frac_less_than_one(det1, det) && frac_less_than_one(det2, det) 418 } 419 _ => { 420 unimplemented!( 421 "Intersection not implemented for {self:?} and {:?}", 422 line.clone() 423 ) 424 } 425 } 426 } 427}