├── .gitignore ├── Cargo.toml ├── README.md ├── output.png └── src ├── canvas.rs ├── color.rs ├── file.rs ├── geom ├── circle.rs ├── mod.rs ├── point.rs └── rect.rs ├── gif.rs ├── lib.rs ├── math.rs ├── random.rs └── util.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitlib" 3 | version = "0.1.0" 4 | authors = ["Keith Peters "] 5 | 6 | [dependencies] 7 | cairo-rs = { version = "0.3.0", features = ["png"] } 8 | rand = "0.3.18" 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bitlib-rs 2 | A drawing, color, geometry and math library geared towards creating graphics and animation with cairo-rs 3 | 4 | Minimal example: 5 | 6 | Cargo.toml 7 | --- 8 | 9 | [package] 10 | name = "minimal_example" 11 | version = "0.1.0" 12 | authors = ["Your Name "] 13 | 14 | [dependencies] 15 | bitlib = { git = "https://github.com/bit101/bitlib-rs.git" } 16 | 17 | main.rs 18 | --- 19 | 20 | extern crate bitlib; 21 | 22 | use bitlib::canvas::Canvas; 23 | use bitlib::canvas::BitContext; 24 | use bitlib::color::Color; 25 | use bitlib::random::Random; 26 | use bitlib::file::open; 27 | 28 | fn main() { 29 | let canvas = Canvas::create(600.0, 600.0); 30 | let context = canvas.get_context(); 31 | let mut rand = Random::new(); 32 | 33 | for _i in 0..1000 { 34 | let x = rand.float(0.0, 600.0); 35 | let y = rand.float(0.0, 600.0); 36 | let r = rand.float(5.0, 50.0); 37 | context.set_source_color(&Color::random_rgb()); 38 | context.fill_circle(x, y, r); 39 | } 40 | canvas.write("output.png"); 41 | open("output.png"); 42 | } 43 | 44 | Output 45 | --- 46 | ![sample output](output.png) 47 | -------------------------------------------------------------------------------- /output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bit101/bitlib-rs/6a4d2f002109b93826982a4d63f5ac920fc7a705/output.png -------------------------------------------------------------------------------- /src/canvas.rs: -------------------------------------------------------------------------------- 1 | extern crate cairo; 2 | extern crate rand; 3 | 4 | use self::cairo::{Context, ImageSurface, Format}; 5 | use std::fs::File; 6 | use color::Color; 7 | use geom::point::Point; 8 | use math::{ clamp, PI, TWO_PI, HALF_PI }; 9 | 10 | pub struct Canvas { 11 | pub width: f64, 12 | pub height: f64, 13 | context: Context, 14 | surface: ImageSurface, 15 | } 16 | 17 | impl Canvas { 18 | pub fn create(width: f64, height: f64) -> Canvas { 19 | let surface = ImageSurface::create(Format::ARgb32, width as i32, height as i32) 20 | .expect("couldn't create a surface, yo"); 21 | let context = Context::new(&surface); 22 | Canvas { 23 | width: width, 24 | height: height, 25 | surface: surface, 26 | context: context, 27 | } 28 | } 29 | 30 | pub fn write(&self, filename: &str) { 31 | let mut file = File::create(filename).unwrap(); 32 | self.surface.write_to_png(&mut file) 33 | .expect("Couldn't write to png"); 34 | } 35 | 36 | pub fn get_context(&self) -> &Context { 37 | &self.context 38 | } 39 | } 40 | 41 | pub trait BitContext { 42 | fn clear_rgb(&self, r: f64, g: f64, b: f64); 43 | fn clear_color(&self, color: &Color); 44 | 45 | fn set_source_color(&self, color: &Color); 46 | 47 | fn fill_rectangle(&self, x: f64, y: f64, w: f64, h: f64); 48 | fn stroke_rectangle(&self, x: f64, y: f64, w: f64, h: f64); 49 | fn round_rectangle(&self, x: f64, y: f64, w: f64, h: f64, r: f64); 50 | fn stroke_round_rectangle(&self, x: f64, y: f64, w: f64, h: f64, r: f64); 51 | fn fill_round_rectangle(&self, x: f64, y: f64, w: f64, h: f64, r: f64); 52 | 53 | fn line(&self, x0: f64, y0: f64, x1: f64, y1: f64); 54 | 55 | fn circle(&self, x: f64, y: f64, r: f64); 56 | fn fill_circle(&self, x: f64, y: f64, r: f64); 57 | fn stroke_circle(&self, x: f64, y: f64, r: f64); 58 | 59 | fn ellipse(&self, x: f64, y: f64, xr: f64, yr: f64); 60 | fn fill_ellipse(&self, x: f64, y: f64, xr: f64, yr: f64); 61 | fn stroke_ellipse(&self, x: f64, y: f64, xr: f64, yr: f64); 62 | 63 | fn path(&self, points: &[Point]); 64 | fn fill_path(&self, points: &[Point]); 65 | fn stroke_path(&self, points: &[Point], close: bool); 66 | 67 | fn polygon(&self, x: f64, y: f64, r: f64, sides: i32, rotation: f64); 68 | fn stroke_polygon(&self, x: f64, y: f64, r: f64, sides: i32, rotation: f64); 69 | fn fill_polygon(&self, x: f64, y: f64, r: f64, sides: i32, rotation: f64); 70 | 71 | fn star(&self, x: f64, y: f64, r0: f64, r1: f64, points: i32, rotation: f64); 72 | fn stroke_star(&self, x: f64, y: f64, r0: f64, r1: f64, points: i32, rotation: f64); 73 | fn fill_star(&self, x: f64, y: f64, r0: f64, r1: f64, points: i32, rotation: f64); 74 | 75 | fn splat(&self, x: f64, y: f64, num_nodes: i32, radius: f64, inner_radius: f64, variation: f64); 76 | fn stroke_splat(&self, x: f64, y: f64, num_nodes: i32, radius: f64, inner_radius: f64, variation: f64); 77 | fn fill_splat(&self, x: f64, y: f64, num_nodes: i32, radius: f64, inner_radius: f64, variation: f64); 78 | 79 | fn fractal_line(&self, x1: f64, y1: f64, x2: f64, y2: f64, roughness: f64, iterations: i32); 80 | fn stroke_fractal_line(&self, x1: f64, y1: f64, x2: f64, y2: f64, roughness: f64, iterations: i32); 81 | 82 | fn heart(&self, x: f64, y: f64, w: f64, h: f64, r: f64); 83 | fn fill_heart(&self, x: f64, y: f64, w: f64, h: f64, r: f64); 84 | fn stroke_heart(&self, x: f64, y: f64, w: f64, h: f64, r: f64); 85 | 86 | fn grid(&self, x: f64, y: f64, w: f64, h: f64, xres: f64, yres: f64); 87 | 88 | fn points(&self, points: &[Point], radius: f64); 89 | 90 | fn stroke_curve_to(&self, x0: f64, y0: f64, x1: f64, y1: f64, x2: f64, y2: f64); 91 | fn quadratic_curve_to(&self, x0: f64, y0: f64, x1: f64, y1: f64); 92 | fn stroke_quadratic_curve_to(&self, x0: f64, y0: f64, x1: f64, y1: f64); 93 | fn multi_curve(&self, points: &[Point]); 94 | fn stroke_multi_curve(&self, points: &[Point]); 95 | fn multi_loop(&self, points: &[Point]); 96 | fn fill_multi_loop(&self, points: &[Point]); 97 | fn stroke_multi_loop(&self, points: &[Point]); 98 | } 99 | 100 | impl BitContext for Context{ 101 | fn clear_rgb(&self, r: f64, g: f64, b: f64) { 102 | self.save(); 103 | // todo: set identity transform 104 | self.set_source_rgb(r, g, b); 105 | self.paint(); 106 | self.restore(); 107 | } 108 | 109 | fn clear_color(&self, color: &Color) { 110 | self.clear_rgb(color.r, color.g, color.b); 111 | } 112 | 113 | fn set_source_color(&self, color: &Color) { 114 | self.set_source_rgba(color.r, color.g, color.b, color.a); 115 | } 116 | 117 | fn line(&self, x0: f64, y0: f64, x1: f64, y1: f64) { 118 | self.move_to(x0, y0); 119 | self.line_to(x1, y1); 120 | self.stroke(); 121 | } 122 | 123 | fn fill_rectangle(&self, x: f64, y: f64, w: f64, h: f64) { 124 | self.rectangle(x, y, w, h); 125 | self.fill(); 126 | } 127 | 128 | fn stroke_rectangle(&self, x: f64, y: f64, w: f64, h: f64) { 129 | self.rectangle(x, y, w, h); 130 | self.stroke(); 131 | } 132 | 133 | fn round_rectangle(&self, x: f64, y: f64, w: f64, h: f64, r: f64) { 134 | self.move_to(x + r, y); 135 | self.line_to(x + w - r, y); 136 | self.arc(x + w - r, y + r, r, -HALF_PI, 0.0); 137 | self.line_to(x + w, y + h - r); 138 | self.arc(x + w - r, y + h - r, r, 0.0, HALF_PI); 139 | self.line_to(x + r, y + h); 140 | self.arc(x + r, y + h - r, r, HALF_PI, PI); 141 | self.line_to(x, y + r); 142 | self.arc(x + r, y + r, r, PI, -HALF_PI); 143 | } 144 | 145 | fn stroke_round_rectangle(&self, x: f64, y: f64, w: f64, h: f64, r: f64) { 146 | self.round_rectangle(x, y, w, h, r); 147 | self.stroke(); 148 | } 149 | 150 | fn fill_round_rectangle(&self, x: f64, y: f64, w: f64, h: f64, r: f64) { 151 | self.round_rectangle(x, y, w, h, r); 152 | self.fill(); 153 | } 154 | 155 | fn circle(&self, x: f64, y: f64, r: f64) { 156 | self.arc(x, y, r, 0.0, TWO_PI); 157 | } 158 | 159 | fn fill_circle(&self, x: f64, y: f64, r: f64) { 160 | self.circle(x, y, r); 161 | self.fill(); 162 | } 163 | 164 | fn stroke_circle(&self, x: f64, y: f64, r: f64) { 165 | self.circle(x, y, r); 166 | self.stroke(); 167 | } 168 | 169 | fn ellipse(&self, x: f64, y: f64, xr: f64, yr: f64) { 170 | self.save(); 171 | self.translate(x, y); 172 | self.scale(xr, yr); 173 | self.circle(0.0, 0.0, 1.0); 174 | self.restore(); 175 | } 176 | 177 | fn fill_ellipse(&self, x: f64, y: f64, xr: f64, yr: f64) { 178 | self.ellipse(x, y, xr, yr); 179 | self.fill(); 180 | } 181 | 182 | fn stroke_ellipse(&self, x: f64, y: f64, xr: f64, yr: f64) { 183 | self.ellipse(x, y, xr, yr); 184 | self.stroke(); 185 | } 186 | 187 | fn path(&self, points: &[Point]) { 188 | for point in points.iter() { 189 | self.line_to(point.x, point.y); 190 | } 191 | } 192 | 193 | fn fill_path(&self, points: &[Point]) { 194 | self.path(points); 195 | self.fill(); 196 | } 197 | 198 | fn stroke_path(&self, points: &[Point], close: bool) { 199 | self.path(points); 200 | if close { 201 | self.close_path(); 202 | } 203 | self.stroke(); 204 | } 205 | 206 | fn polygon(&self, x: f64, y: f64, r: f64, sides: i32, rotation: f64) { 207 | self.save(); 208 | self.translate(x, y); 209 | self.rotate(rotation); 210 | self.move_to(r, 0.0); 211 | for i in 0..sides { 212 | let angle = TWO_PI / sides as f64 * i as f64; 213 | self.line_to(angle.cos() * r, angle.sin() * r); 214 | } 215 | self.line_to(r, 0.0); 216 | self.restore(); 217 | } 218 | 219 | fn stroke_polygon(&self, x: f64, y: f64, r: f64, sides: i32, rotation: f64) { 220 | self.polygon(x, y, r, sides, rotation); 221 | self.stroke(); 222 | } 223 | 224 | fn fill_polygon(&self, x: f64, y: f64, r: f64, sides: i32, rotation: f64) { 225 | self.polygon(x, y, r, sides, rotation); 226 | self.fill(); 227 | } 228 | 229 | fn star(&self, x: f64, y: f64, r0: f64, r1: f64, points: i32, rotation: f64) { 230 | self.save(); 231 | self.translate(x, y); 232 | self.rotate(rotation); 233 | for i in 0..points * 2 { 234 | let mut r = r1; 235 | if i % 2 == 1 { 236 | r = r0; 237 | } 238 | let angle = PI / points as f64 * i as f64; 239 | self.line_to(angle.cos() * r, angle.sin() * r); 240 | } 241 | self.close_path(); 242 | self.restore(); 243 | } 244 | 245 | fn stroke_star(&self, x: f64, y: f64, r0: f64, r1: f64, points: i32, rotation: f64) { 246 | self.star(x, y, r0, r1, points, rotation); 247 | self.stroke(); 248 | } 249 | 250 | fn fill_star(&self, x: f64, y: f64, r0: f64, r1: f64, points: i32, rotation: f64) { 251 | self.star(x, y, r0, r1, points, rotation); 252 | self.fill(); 253 | } 254 | 255 | fn splat(&self, x: f64, y: f64, num_nodes: i32, radius: f64, inner_radius: f64, variation: f64) { 256 | let mut points: Vec = Vec::new(); 257 | let slice = TWO_PI / (num_nodes * 2) as f64; 258 | let mut angle = 0.0; 259 | let curve = 0.3; 260 | let radius_range = radius - inner_radius; 261 | let variation = clamp(variation, 0.0, 1.0); 262 | for _i in 0..num_nodes { 263 | let radius = radius + variation * (rand::random::() * radius_range * 2.0 - radius_range); 264 | let radius_range = radius - inner_radius; 265 | points.push(make_point(angle - slice * (1.0 + curve), inner_radius)); 266 | points.push(make_point(angle + slice * curve, inner_radius)); 267 | points.push(make_point(angle - slice * curve, inner_radius + radius_range * 0.8)); 268 | points.push(make_point(angle + slice / 2.0, radius)); 269 | points.push(make_point(angle + slice * (1.0 + curve), inner_radius + radius_range * 0.8)); 270 | angle += slice * 2.0; 271 | } 272 | 273 | self.save(); 274 | self.translate(x, y); 275 | self.multi_loop(&points); 276 | self.restore(); 277 | 278 | fn make_point(angle: f64, radius: f64) -> Point { 279 | Point { 280 | x: angle.cos() * radius, 281 | y: angle.sin() * radius 282 | } 283 | } 284 | } 285 | 286 | fn stroke_splat(&self, x: f64, y: f64, num_nodes: i32, radius: f64, inner_radius: f64, variation: f64) { 287 | self.splat(x, y, num_nodes, radius, inner_radius, variation); 288 | self.stroke(); 289 | } 290 | 291 | fn fill_splat(&self, x: f64, y: f64, num_nodes: i32, radius: f64, inner_radius: f64, variation: f64) { 292 | self.splat(x, y, num_nodes, radius, inner_radius, variation); 293 | self.fill(); 294 | } 295 | 296 | fn fractal_line(&self, x1: f64, y1: f64, x2: f64, y2: f64, roughness: f64, iterations: i32) { 297 | let dx = x2 - x1; 298 | let dy = y2 - y1; 299 | let mut offset = (dx * dx + dy * dy).sqrt() * 0.15; 300 | 301 | let mut path: Vec = Vec::new(); 302 | path.push(Point::new(x1, y1)); 303 | path.push(Point::new(x2, y2)); 304 | for _i in 0..iterations { 305 | let mut new_path: Vec = Vec::new(); 306 | for (j, point) in path.iter().enumerate() { 307 | new_path.push(Point::new(point.x, point.y)); 308 | if j < path.len() - 1 { 309 | let x = (point.x + path[j + 1].x) / 2.0 + rand::random::() * offset * 2.0 - offset; 310 | let y = (point.y + path[j + 1].y) / 2.0 + rand::random::() * offset * 2.0 - offset; 311 | new_path.push(Point::new(x, y)); 312 | } 313 | } 314 | offset *= roughness; 315 | path = new_path; 316 | } 317 | self.path(&path); 318 | } 319 | 320 | fn stroke_fractal_line(&self, x1: f64, y1: f64, x2: f64, y2: f64, roughness: f64, iterations: i32) { 321 | self.fractal_line(x1, y1, x2, y2, roughness, iterations); 322 | self.stroke(); 323 | } 324 | 325 | fn heart(&self, x: f64, y: f64, w: f64, h: f64, r: f64) { 326 | self.save(); 327 | self.translate(x, y); 328 | self.rotate(r); 329 | let mut path: Vec = Vec::new(); 330 | let res = (w * h).sqrt() as i32; 331 | for i in 0..res { 332 | let a = TWO_PI * i as f64 / res as f64; 333 | let x = w * a.sin().powf(3.0); 334 | let y = h * (0.8125 * a.cos() 335 | - 0.3125 * (2.0 * a).cos() 336 | - 0.125 * (3.0 * a).cos() 337 | - 0.0625 * (4.0 * a).cos()); 338 | path.push(Point::new(x, -y)); 339 | } 340 | self.path(&path); 341 | self.restore() 342 | } 343 | 344 | fn fill_heart(&self, x: f64, y: f64, w: f64, h: f64, r: f64) { 345 | self.heart(x, y, w, h, r); 346 | self.fill(); 347 | } 348 | 349 | fn stroke_heart(&self, x: f64, y: f64, w: f64, h: f64, r: f64) { 350 | self.heart(x, y, w, h, r); 351 | self.stroke(); 352 | } 353 | 354 | fn points(&self, points: &[Point], radius: f64) { 355 | for point in points.iter() { 356 | self.fill_circle(point.x, point.y, radius); 357 | } 358 | } 359 | 360 | fn stroke_curve_to(&self, x0: f64, y0: f64, x1: f64, y1: f64, x2: f64, y2: f64) { 361 | self.curve_to(x0, y0, x1, y1, x2, y2); 362 | self.stroke(); 363 | } 364 | 365 | fn quadratic_curve_to(&self, x0: f64, y0: f64, x1: f64, y1: f64) { 366 | let p = self.get_current_point(); 367 | self.curve_to( 368 | 2.0 / 3.0 * x0 + 1.0 / 3.0 * p.0, 369 | 2.0 / 3.0 * y0 + 1.0 / 3.0 * p.1, 370 | 2.0 / 3.0 * x0 + 1.0 / 3.0 * x1, 371 | 2.0 / 3.0 * y0 + 1.0 / 3.0 * y1, 372 | x1, y1); 373 | } 374 | 375 | fn stroke_quadratic_curve_to(&self, x0: f64, y0: f64, x1: f64, y1: f64) { 376 | self.quadratic_curve_to(x0, y0, x1, y1); 377 | self.stroke(); 378 | } 379 | 380 | fn multi_curve(&self, points: &[Point]) { 381 | self.move_to(points[0].x, points[0].y); 382 | self.line_to((points[0].x + points[1].x) / 2.0, 383 | (points[0].y + points[1].y) / 2.0); 384 | let mut i = 1; 385 | while i < points.len() - 1 { 386 | let p0 = &points[i]; 387 | let p1 = &points[i + 1]; 388 | let midx = (p0.x + p1.x) / 2.0; 389 | let midy = (p0.y + p1.y) / 2.0; 390 | self.quadratic_curve_to(p0.x, p0.y, midx, midy); 391 | i = i + 1; 392 | 393 | } 394 | let p = &points[points.len() - 1]; 395 | self.line_to(p.x, p.y); 396 | } 397 | 398 | fn stroke_multi_curve(&self, points: &[Point]) { 399 | self.multi_curve(points); 400 | self.stroke(); 401 | } 402 | 403 | fn multi_loop(&self, points: &[Point]) { 404 | let p_a = &points[0]; 405 | let p_z = &points[points.len() - 1]; 406 | let mid1x = (p_z.x + p_a.x) / 2.0; 407 | let mid1y = (p_z.y + p_a.y) / 2.0; 408 | self.move_to(mid1x, mid1y); 409 | for i in 0..points.len() - 1 { 410 | let p0 = &points[i]; 411 | let p1 = &points[i + 1]; 412 | let midx = (p0.x + p1.x) / 2.0; 413 | let midy = (p0.y + p1.y) / 2.0; 414 | self.quadratic_curve_to(p0.x, p0.y, midx, midy); 415 | } 416 | self.quadratic_curve_to(p_z.x, p_z.y, mid1x, mid1y); 417 | } 418 | 419 | fn fill_multi_loop(&self, points: &[Point]) { 420 | self.multi_loop(points); 421 | self.fill(); 422 | } 423 | 424 | fn stroke_multi_loop(&self, points: &[Point]) { 425 | self.multi_loop(points); 426 | self.stroke(); 427 | } 428 | 429 | fn grid(&self, x: f64, y: f64, w: f64, h: f64, xres: f64, yres: f64) { 430 | let mut xx = x; 431 | let mut yy = y; 432 | while xx <= x + w { 433 | self.move_to(xx, y); 434 | self.line_to(xx, y + h); 435 | xx += xres; 436 | } 437 | while yy <= y + h { 438 | self.move_to(x, yy); 439 | self.line_to(x + w, yy); 440 | yy += yres; 441 | } 442 | self.stroke(); 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /src/color.rs: -------------------------------------------------------------------------------- 1 | use random::Random; 2 | 3 | pub struct Color { 4 | pub r: f64, 5 | pub g: f64, 6 | pub b: f64, 7 | pub a: f64, 8 | } 9 | 10 | impl Color { 11 | pub fn rgb(r: f64, g: f64, b: f64) -> Color { 12 | Color { r: r, g: g, b: b, a: 1.0 } 13 | } 14 | 15 | pub fn rgba(r: f64, g: f64, b: f64, a: f64) -> Color { 16 | Color { r: r, g: g, b: b, a: a } 17 | } 18 | 19 | pub fn rgb_int(r: i32, g: i32, b: i32) -> Color { 20 | Color::rgba_int(r, g, b, 255) 21 | } 22 | 23 | pub fn rgba_int(r: i32, g: i32, b: i32, a: i32) -> Color { 24 | Color::rgba(r as f64 / 255.0, g as f64 / 255.0, b as f64 / 255.0, a as f64 / 255.0) 25 | } 26 | 27 | pub fn from_int(value: i64) -> Color { 28 | let a = (value >> 24) as i32; 29 | let r = (value >> 16 & 0xff) as i32; 30 | let g = (value >> 8 & 0xff) as i32; 31 | let b = (value & 0xff) as i32; 32 | Color::rgba_int(r, g, b, a) 33 | } 34 | 35 | pub fn random_rgb() -> Color { 36 | let mut rand = Random::new(); 37 | let r = rand.float(0.0, 1.0); 38 | let g = rand.float(0.0, 1.0); 39 | let b = rand.float(0.0, 1.0); 40 | Color::rgb(r, g, b) 41 | } 42 | 43 | pub fn hsv(h: f64, s: f64, v: f64) -> Color { 44 | let h = h / 360.0; 45 | let i = (h * 6.0).floor(); 46 | let f = h * 6.0 - i; 47 | let p = v * (1.0 - s); 48 | let q = v * (1.0 - f * s); 49 | let t = v * (1.0 - (1.0 - f) * s); 50 | match (i % 6.0) as i32 { 51 | 0 => Color::rgb(v, t, p), 52 | 1 => Color::rgb(q, v, p), 53 | 2 => Color::rgb(p, v, t), 54 | 3 => Color::rgb(p, q, v), 55 | 4 => Color::rgb(t, p, v), 56 | 5 => Color::rgb(v, p, q), 57 | _ => Color::rgb(0.0, 0.0, 0.0), 58 | } 59 | } 60 | 61 | pub fn random_hsv(hmin: f64, hmax: f64, smin: f64, smax: f64, vmin: f64, vmax: f64) -> Color { 62 | let mut rand = Random::new(); 63 | Color::hsv(rand.float(hmin, hmax), rand.float(smin, smax), rand.float(vmin, vmax)) 64 | } 65 | 66 | pub fn grey(shade: f64) -> Color { 67 | Color::rgb(shade, shade, shade) 68 | } 69 | 70 | pub fn grey_int(shade: i32) -> Color { 71 | Color::grey(shade as f64 / 255.0) 72 | } 73 | 74 | pub fn random_grey() -> Color { 75 | Color::random_grey_range(0.0, 1.0) 76 | } 77 | 78 | pub fn random_grey_range(min: f64, max: f64) -> Color { 79 | let mut rand = Random::new(); 80 | Color::grey(rand.float(min, max)) 81 | } 82 | 83 | pub fn white() -> Color { 84 | Color::rgb(1.0, 1.0, 1.0) 85 | } 86 | 87 | pub fn black() -> Color { 88 | Color::rgb(0.0, 0.0, 0.0) 89 | } 90 | 91 | pub fn red() -> Color { 92 | Color::rgb(1.0, 0.0, 0.0) 93 | } 94 | 95 | pub fn yellow() -> Color { 96 | Color::rgb(1.0, 1.0, 0.0) 97 | } 98 | 99 | pub fn green() -> Color { 100 | Color::rgb(0.0, 1.0, 0.0) 101 | } 102 | 103 | pub fn cyan() -> Color { 104 | Color::rgb(0.0, 1.0, 1.0) 105 | } 106 | 107 | pub fn blue() -> Color { 108 | Color::rgb(0.0, 0.0, 1.0) 109 | } 110 | 111 | pub fn magenta() -> Color { 112 | Color::rgb(1.0, 0.0, 1.0) 113 | } 114 | 115 | pub fn from_string(src: &str) -> Color { 116 | if &src[0..1] == "#" { 117 | let num = i64::from_str_radix(&src[1..], 16) 118 | .expect("Could not parse color"); 119 | let mut col = Color::from_int(num); 120 | if src.len() == 7 { 121 | col.a = 1.0; 122 | } 123 | col 124 | } 125 | else { 126 | Color::from_named_string(src) 127 | } 128 | } 129 | 130 | pub fn from_named_string(src: &str) -> Color { 131 | match src { 132 | "blueviolet" => Color::rgb_int(138,43,226), 133 | "brown" => Color::rgb_int(165,42,42), 134 | "aliceblue" => Color::rgb_int(240,248,255), 135 | "antiquewhite" => Color::rgb_int(250,235,215), 136 | "aqua" => Color::rgb_int(0,255,255), 137 | "aquamarine" => Color::rgb_int(127,255,212), 138 | "azure" => Color::rgb_int(240,255,255), 139 | "beige" => Color::rgb_int(245,245,220), 140 | "bisque" => Color::rgb_int(255,228,196), 141 | "black" => Color::rgb_int(0,0,0), 142 | "blanchedalmond" => Color::rgb_int(255,235,205), 143 | "blue" => Color::rgb_int(0,0,255), 144 | "burlywood" => Color::rgb_int(222,184,135), 145 | "cadetblue" => Color::rgb_int(95,158,160), 146 | "chartreuse" => Color::rgb_int(127,255,0), 147 | "chocolate" => Color::rgb_int(210,105,30), 148 | "coral" => Color::rgb_int(255,127,80), 149 | "cornflowerblue" => Color::rgb_int(100,149,237), 150 | "cornsilk" => Color::rgb_int(255,248,220), 151 | "crimson" => Color::rgb_int(220,20,60), 152 | "cyan" => Color::rgb_int(0,255,255), 153 | "darkblue" => Color::rgb_int(0,0,139), 154 | "darkcyan" => Color::rgb_int(0,139,139), 155 | "darkgoldenrod" => Color::rgb_int(184,134,11), 156 | "darkgray" => Color::rgb_int(169,169,169), 157 | "darkgreen" => Color::rgb_int(0,100,0), 158 | "darkgrey" => Color::rgb_int(169,169,169), 159 | "darkkhaki" => Color::rgb_int(189,183,107), 160 | "darkmagenta" => Color::rgb_int(139,0,139), 161 | "darkolivegreen" => Color::rgb_int(85,107,47), 162 | "darkorange" => Color::rgb_int(255,140,0), 163 | "darkorchid" => Color::rgb_int(153,50,204), 164 | "darkred" => Color::rgb_int(139,0,0), 165 | "darksalmon" => Color::rgb_int(233,150,122), 166 | "darkseagreen" => Color::rgb_int(143,188,143), 167 | "darkslateblue" => Color::rgb_int(72,61,139), 168 | "darkslategray" => Color::rgb_int(47,79,79), 169 | "darkslategrey" => Color::rgb_int(47,79,79), 170 | "darkturquoise" => Color::rgb_int(0,206,209), 171 | "darkviolet" => Color::rgb_int(148,0,211), 172 | "deeppink" => Color::rgb_int(255,20,147), 173 | "deepskyblue" => Color::rgb_int(0,191,255), 174 | "dimgray" => Color::rgb_int(105,105,105), 175 | "dimgrey" => Color::rgb_int(105,105,105), 176 | "dodgerblue" => Color::rgb_int(30,144,255), 177 | "firebrick" => Color::rgb_int(178,34,34), 178 | "floralwhite" => Color::rgb_int(255,250,240), 179 | "forestgreen" => Color::rgb_int(34,139,34), 180 | "fuchsia" => Color::rgb_int(255,0,255), 181 | "gainsboro" => Color::rgb_int(220,220,220), 182 | "ghostwhite" => Color::rgb_int(248,248,255), 183 | "gold" => Color::rgb_int(255,215,0), 184 | "goldenrod" => Color::rgb_int(218,165,32), 185 | "gray" => Color::rgb_int(128,128,128), 186 | "green" => Color::rgb_int(0,128,0), 187 | "greenyellow" => Color::rgb_int(173,255,47), 188 | "honeydew" => Color::rgb_int(240,255,240), 189 | "hotpink" => Color::rgb_int(255,105,180), 190 | "indianred" => Color::rgb_int(205,92,92), 191 | "indigo" => Color::rgb_int(75,0,130), 192 | "ivory" => Color::rgb_int(255,255,240), 193 | "khaki" => Color::rgb_int(240,230,140), 194 | "lavender" => Color::rgb_int(230,230,250), 195 | "lavenderblush" => Color::rgb_int(255,240,245), 196 | "lawngreen" => Color::rgb_int(124,252,0), 197 | "lemonchiffon" => Color::rgb_int(255,250,205), 198 | "lightblue" => Color::rgb_int(173,216,230), 199 | "lightcoral" => Color::rgb_int(240,128,128), 200 | "lightcyan" => Color::rgb_int(224,255,255), 201 | "lightgoldenrodyellow" => Color::rgb_int(250,250,210), 202 | "lightgray" => Color::rgb_int(211,211,211), 203 | "lightgreen" => Color::rgb_int(144,238,144), 204 | "lightgrey" => Color::rgb_int(211,211,211), 205 | "lightpink" => Color::rgb_int(255,182,193), 206 | "lightsalmon" => Color::rgb_int(255,160,122), 207 | "lightseagreen" => Color::rgb_int(32,178,170), 208 | "lightskyblue" => Color::rgb_int(135,206,250), 209 | "lightslategray" => Color::rgb_int(119,136,153), 210 | "lightslategrey" => Color::rgb_int(119,136,153), 211 | "lightsteelblue" => Color::rgb_int(176,196,222), 212 | "lightyellow" => Color::rgb_int(255,255,224), 213 | "lime" => Color::rgb_int(0,255,0), 214 | "limegreen" => Color::rgb_int(50,205,50), 215 | "linen" => Color::rgb_int(250,240,230), 216 | "magenta" => Color::rgb_int(255,0,255), 217 | "maroon" => Color::rgb_int(128,0,0), 218 | "mediumaquamarine" => Color::rgb_int(102,205,170), 219 | "mediumblue" => Color::rgb_int(0,0,205), 220 | "mediumorchid" => Color::rgb_int(186,85,211), 221 | "mediumpurple" => Color::rgb_int(147,112,219), 222 | "mediumseagreen" => Color::rgb_int(60,179,113), 223 | "mediumslateblue" => Color::rgb_int(123,104,238), 224 | "mediumspringgreen" => Color::rgb_int(0,250,154), 225 | "mediumturquoise" => Color::rgb_int(72,209,204), 226 | "mediumvioletred" => Color::rgb_int(199,21,133), 227 | "midnightblue" => Color::rgb_int(25,25,112), 228 | "mintcream" => Color::rgb_int(245,255,250), 229 | "mistyrose" => Color::rgb_int(255,228,225), 230 | "moccasin" => Color::rgb_int(255,228,181), 231 | "navajowhite" => Color::rgb_int(255,222,173), 232 | "navy" => Color::rgb_int(0,0,128), 233 | "oldlace" => Color::rgb_int(253,245,230), 234 | "olive" => Color::rgb_int(128,128,0), 235 | "olivedrab" => Color::rgb_int(107,142,35), 236 | "orange" => Color::rgb_int(255,165,0), 237 | "orangered" => Color::rgb_int(255,69,0), 238 | "orchid" => Color::rgb_int(218,112,214), 239 | "palegoldenrod" => Color::rgb_int(238,232,170), 240 | "palegreen" => Color::rgb_int(152,251,152), 241 | "paleturquoise" => Color::rgb_int(175,238,238), 242 | "palevioletred" => Color::rgb_int(219,112,147), 243 | "papayawhip" => Color::rgb_int(255,239,213), 244 | "peachpuff" => Color::rgb_int(255,218,185), 245 | "peru" => Color::rgb_int(205,133,63), 246 | "pink" => Color::rgb_int(255,192,203), 247 | "plum" => Color::rgb_int(221,160,221), 248 | "powderblue" => Color::rgb_int(176,224,230), 249 | "purple" => Color::rgb_int(128,0,128), 250 | "rebeccapurple" => Color::rgb_int(102,51,153), 251 | "red" => Color::rgb_int(255,0,0), 252 | "rosybrown" => Color::rgb_int(188,143,143), 253 | "royalblue" => Color::rgb_int(65,105,225), 254 | "saddlebrown" => Color::rgb_int(139,69,19), 255 | "salmon" => Color::rgb_int(250,128,114), 256 | "sandybrown" => Color::rgb_int(244,164,96), 257 | "seagreen" => Color::rgb_int(46,139,87), 258 | "seashell" => Color::rgb_int(255,245,238), 259 | "sienna" => Color::rgb_int(160,82,45), 260 | "silver" => Color::rgb_int(192,192,192), 261 | "skyblue" => Color::rgb_int(135,206,235), 262 | "slateblue" => Color::rgb_int(106,90,205), 263 | "slategray" => Color::rgb_int(112,128,144), 264 | "slategrey" => Color::rgb_int(112,128,144), 265 | "snow" => Color::rgb_int(255,250,250), 266 | "springgreen" => Color::rgb_int(0,255,127), 267 | "steelblue" => Color::rgb_int(70,130,180), 268 | "tan" => Color::rgb_int(210,180,140), 269 | "teal" => Color::rgb_int(0,128,128), 270 | "thistle" => Color::rgb_int(216,191,216), 271 | "tomato" => Color::rgb_int(255,99,71), 272 | "turquoise" => Color::rgb_int(64,224,208), 273 | "violet" => Color::rgb_int(238,130,238), 274 | "wheat" => Color::rgb_int(245,222,179), 275 | "white" => Color::rgb_int(255,255,255), 276 | "whitesmoke" => Color::rgb_int(245,245,245), 277 | "yellow" => Color::rgb_int(255,255,0), 278 | "yellowgreen" => Color::rgb_int(154,205,50), 279 | _ => Color::rgb(0.0, 0.0, 0.0), 280 | } 281 | } 282 | } 283 | 284 | 285 | 286 | #[cfg(test)] 287 | mod tests { 288 | use super::*; 289 | 290 | #[test] 291 | fn test_rgb_int() { 292 | let c = Color::rgb_int(255, 0, 0); 293 | assert_eq!(c.r, 1.0); 294 | assert_eq!(c.g, 0.0); 295 | assert_eq!(c.b, 0.0); 296 | assert_eq!(c.a, 1.0); 297 | 298 | let c = Color::rgba_int(255, 0, 0, 255); 299 | assert_eq!(c.r, 1.0); 300 | assert_eq!(c.g, 0.0); 301 | assert_eq!(c.b, 0.0); 302 | assert_eq!(c.a, 1.0); 303 | } 304 | 305 | #[test] 306 | fn test_from_int() { 307 | let c = Color::from_int(0xff0000); 308 | assert_eq!(c.r, 1.0); 309 | assert_eq!(c.g, 0.0); 310 | assert_eq!(c.b, 0.0); 311 | assert_eq!(c.a, 0.0); 312 | 313 | let c = Color::from_int(0xffff0000); 314 | assert_eq!(c.r, 1.0); 315 | assert_eq!(c.g, 0.0); 316 | assert_eq!(c.b, 0.0); 317 | assert_eq!(c.a, 1.0); 318 | } 319 | 320 | #[test] 321 | fn test_hsv() { 322 | let c = Color::hsv(0.0, 1.0, 1.0); 323 | assert_eq!(c.r, 1.0); 324 | assert_eq!(c.g, 0.0); 325 | assert_eq!(c.b, 0.0); 326 | assert_eq!(c.a, 1.0); 327 | 328 | let c = Color::hsv(60.0, 1.0, 1.0); 329 | assert_eq!(c.r, 1.0); 330 | assert_eq!(c.g, 1.0); 331 | assert_eq!(c.b, 0.0); 332 | assert_eq!(c.a, 1.0); 333 | 334 | let c = Color::hsv(120.0, 1.0, 1.0); 335 | assert_eq!(c.r, 0.0); 336 | assert_eq!(c.g, 1.0); 337 | assert_eq!(c.b, 0.0); 338 | assert_eq!(c.a, 1.0); 339 | 340 | let c = Color::hsv(240.0, 1.0, 1.0); 341 | assert_eq!(c.r, 0.0); 342 | assert_eq!(c.g, 0.0); 343 | assert_eq!(c.b, 1.0); 344 | assert_eq!(c.a, 1.0); 345 | } 346 | 347 | #[test] 348 | fn test_greys() { 349 | let c = Color::grey(0.56); 350 | assert_eq!(c.r, 0.56); 351 | assert_eq!(c.g, 0.56); 352 | assert_eq!(c.b, 0.56); 353 | assert_eq!(c.a, 1.0); 354 | 355 | let c = Color::grey_int(255); 356 | assert_eq!(c.r, 1.0); 357 | assert_eq!(c.g, 1.0); 358 | assert_eq!(c.b, 1.0); 359 | assert_eq!(c.a, 1.0); 360 | 361 | let c = Color::random_grey_range(0.25, 0.35); 362 | assert!(c.r >= 0.25 && c.r <= 0.35); 363 | assert_eq!(c.r, c.g); 364 | assert_eq!(c.r, c.b); 365 | assert_eq!(c.a, 1.0); 366 | 367 | } 368 | 369 | 370 | #[test] 371 | fn test_from_string() { 372 | let c = Color::from_string("#ff0000"); 373 | assert_eq!(c.r, 1.0); 374 | assert_eq!(c.g, 0.0); 375 | assert_eq!(c.b, 0.0); 376 | assert_eq!(c.a, 1.0); 377 | 378 | let c = Color::from_string("#00ff00"); 379 | assert_eq!(c.r, 0.0); 380 | assert_eq!(c.g, 1.0); 381 | assert_eq!(c.b, 0.0); 382 | assert_eq!(c.a, 1.0); 383 | 384 | let c = Color::from_string("#00ff0000"); 385 | assert_eq!(c.r, 1.0); 386 | assert_eq!(c.g, 0.0); 387 | assert_eq!(c.b, 0.0); 388 | assert_eq!(c.a, 0.0); 389 | 390 | let c = Color::from_string("#ffffffff"); 391 | assert_eq!(c.r, 1.0); 392 | assert_eq!(c.g, 1.0); 393 | assert_eq!(c.b, 1.0); 394 | assert_eq!(c.a, 1.0); 395 | 396 | let c = Color::from_string("#FFFFFFFF"); 397 | assert_eq!(c.r, 1.0); 398 | assert_eq!(c.g, 1.0); 399 | assert_eq!(c.b, 1.0); 400 | assert_eq!(c.a, 1.0); 401 | 402 | let c = Color::from_string("red"); 403 | assert_eq!(c.r, 1.0); 404 | assert_eq!(c.g, 0.0); 405 | assert_eq!(c.b, 0.0); 406 | assert_eq!(c.a, 1.0); 407 | 408 | let c = Color::from_string("lime"); 409 | assert_eq!(c.r, 0.0); 410 | assert_eq!(c.g, 1.0); 411 | assert_eq!(c.b, 0.0); 412 | assert_eq!(c.a, 1.0); 413 | 414 | let c = Color::from_string("blue"); 415 | assert_eq!(c.r, 0.0); 416 | assert_eq!(c.g, 0.0); 417 | assert_eq!(c.b, 1.0); 418 | assert_eq!(c.a, 1.0); 419 | 420 | let c = Color::from_string("yellow"); 421 | assert_eq!(c.r, 1.0); 422 | assert_eq!(c.g, 1.0); 423 | assert_eq!(c.b, 0.0); 424 | assert_eq!(c.a, 1.0); 425 | 426 | // bad color = black 427 | let c = Color::from_string("foo"); 428 | assert_eq!(c.r, 0.0); 429 | assert_eq!(c.g, 0.0); 430 | assert_eq!(c.b, 0.0); 431 | assert_eq!(c.a, 1.0); 432 | } 433 | 434 | #[test] 435 | fn test_raw_colors() { 436 | let c = Color::black(); 437 | assert_eq!(c.r, 0.0); 438 | assert_eq!(c.g, 0.0); 439 | assert_eq!(c.b, 0.0); 440 | assert_eq!(c.a, 1.0); 441 | 442 | let c = Color::white(); 443 | assert_eq!(c.r, 1.0); 444 | assert_eq!(c.g, 1.0); 445 | assert_eq!(c.b, 1.0); 446 | assert_eq!(c.a, 1.0); 447 | 448 | let c = Color::red(); 449 | assert_eq!(c.r, 1.0); 450 | assert_eq!(c.g, 0.0); 451 | assert_eq!(c.b, 0.0); 452 | assert_eq!(c.a, 1.0); 453 | 454 | let c = Color::green(); 455 | assert_eq!(c.r, 0.0); 456 | assert_eq!(c.g, 1.0); 457 | assert_eq!(c.b, 0.0); 458 | assert_eq!(c.a, 1.0); 459 | 460 | let c = Color::blue(); 461 | assert_eq!(c.r, 0.0); 462 | assert_eq!(c.g, 0.0); 463 | assert_eq!(c.b, 1.0); 464 | assert_eq!(c.a, 1.0); 465 | 466 | let c = Color::yellow(); 467 | assert_eq!(c.r, 1.0); 468 | assert_eq!(c.g, 1.0); 469 | assert_eq!(c.b, 0.0); 470 | assert_eq!(c.a, 1.0); 471 | 472 | let c = Color::cyan(); 473 | assert_eq!(c.r, 0.0); 474 | assert_eq!(c.g, 1.0); 475 | assert_eq!(c.b, 1.0); 476 | assert_eq!(c.a, 1.0); 477 | 478 | let c = Color::magenta(); 479 | assert_eq!(c.r, 1.0); 480 | assert_eq!(c.g, 0.0); 481 | assert_eq!(c.b, 1.0); 482 | assert_eq!(c.a, 1.0); 483 | 484 | } 485 | } 486 | -------------------------------------------------------------------------------- /src/file.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | use std::fs; 3 | use std::path::Path; 4 | 5 | /// Opens a file with it's specified application. 6 | #[cfg(target_os="macos")] 7 | pub fn open(path: &str) { 8 | Command::new("sh") 9 | .arg("-c") 10 | .arg(format!("open {}", path)) 11 | .output() 12 | .expect("couldn't display gif"); 13 | } 14 | 15 | /// Opens a file with it's specified application. 16 | #[cfg(target_os="linux")] 17 | pub fn open(path: &str) { 18 | Command::new("sh") 19 | .arg("-c") 20 | .arg(format!("xdg-open {}", path)) 21 | .output() 22 | .expect("couldn't display gif"); 23 | } 24 | 25 | // TODO: windows? 26 | 27 | /// Creates a directory at `path` if one does not already exist there. 28 | /// If there is already a directory there and `wipe` is true, it will attempt to remove that directory first. 29 | /// If a file already exists there, it will probably panic. 30 | pub fn make_dir(path: &str, wipe: bool) { 31 | if Path::new(path).exists() { 32 | if wipe { 33 | wipe_dir(path); 34 | fs::create_dir(path).unwrap(); 35 | } 36 | } 37 | else { 38 | fs::create_dir(path).unwrap(); 39 | } 40 | } 41 | 42 | /// Removes a directory if it exists. 43 | pub fn wipe_dir(path: &str) { 44 | if Path::new(path).exists() { 45 | fs::remove_dir_all(path).unwrap(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/geom/circle.rs: -------------------------------------------------------------------------------- 1 | use super::point::Point; 2 | use super::dist; 3 | 4 | pub struct Circle { 5 | pub x: f64, 6 | pub y: f64, 7 | pub r: f64, 8 | } 9 | 10 | impl Circle { 11 | pub fn contains_point(&self, p: &Point) -> bool { 12 | dist(p, &self.center()) <= self.r 13 | } 14 | 15 | pub fn intersects_circle(&self, other: Circle) -> bool { 16 | let d = dist(&self.center(), &other.center()); 17 | d < self.r + other.r 18 | } 19 | 20 | pub fn center(&self) -> Point { 21 | Point::new(self.x, self.y) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/geom/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod point; 2 | pub mod rect; 3 | pub mod circle; 4 | 5 | use math::lerp; 6 | use self::point::Point; 7 | use self::circle::Circle; 8 | 9 | pub fn dot_product(p0: &Point, p1: &Point, p2: &Point, p3: &Point) -> f64 { 10 | let dx0 = p1.x - p0.x; 11 | let dy0 = p1.y - p0.y; 12 | let dx1 = p3.x - p2.x; 13 | let dy1 = p3.y - p2.y; 14 | dx0 * dx1 + dy0 * dy1 15 | } 16 | 17 | pub fn angle_between(p0: &Point, p1: &Point, p2: &Point, p3: &Point) -> f64 { 18 | let dp = dot_product(p0, p1, p2, p3); 19 | let mag0 = dist(p0, p1); 20 | let mag1 = dist(p2, p3); 21 | (dp / mag0 / mag1).acos() 22 | } 23 | 24 | 25 | pub fn dist(p0: &Point, p1: &Point) -> f64 { 26 | let dx = p1.x - p0.x; 27 | let dy = p1.y - p0.y; 28 | (dx * dx + dy * dy).sqrt() 29 | } 30 | 31 | pub fn lerp_point(p0: Point, p1: Point, t: f64) -> Point { 32 | Point::new(lerp(p0.x, p1.x, t), lerp(p0.y, p1.y, t)) 33 | } 34 | 35 | pub fn bezier_point(p0: Point, p1: Point, p2: Point, p3: Point, t: f64) -> Point { 36 | let one_minus_t = 1.0 - t; 37 | let m0 = one_minus_t * one_minus_t * one_minus_t; 38 | let m1 = 3.0 * one_minus_t * one_minus_t * t; 39 | let m2 = 3.0 * one_minus_t * t * t; 40 | let m3 = t * t * t; 41 | Point { 42 | x: m0 * p0.x + m1 * p1.x + m2 * p2.x + m3 * p3.x, 43 | y: m0 * p0.y + m1 * p1.y + m2 * p2.y + m3 * p3.y 44 | } 45 | } 46 | 47 | pub fn quadratic_point(p0: Point, p1: Point, p2: Point, t: f64) -> Point { 48 | let one_minus_t = 1.0 - t; 49 | let m0 = one_minus_t * one_minus_t; 50 | let m1 = 2.0 * one_minus_t * t; 51 | let m2 = t * t; 52 | Point { 53 | x: m0 * p0.x + m1 * p1.x + m2 * p2.x, 54 | y: m0 * p0.y + m1 * p1.y + m2 * p2.y 55 | } 56 | } 57 | 58 | pub fn segment_intersect(p0: Point, p1: Point, p2: Point, p3: Point) -> Option { 59 | let a1 = p1.y - p0.y; 60 | let b1 = p0.x - p1.x; 61 | let c1 = a1 * p0.x + b1 * p0.y; 62 | let a2 = p3.y - p2.y; 63 | let b2 = p2.x - p3.x; 64 | let c2 = a2 * p2.x + b2 * p2.y; 65 | let denominator = a1 * b2 - a2 * b1; 66 | 67 | if denominator == 0.0 { 68 | None 69 | } 70 | else { 71 | let intersect_x = (b2 * c1 - b1 * c2) / denominator; 72 | let intersect_y = (a1 * c2 - a2 * c1) / denominator; 73 | let rx0 = (intersect_x - p0.x) / (p1.x - p0.x); 74 | let ry0 = (intersect_y - p0.y) / (p1.y - p0.y); 75 | let rx1 = (intersect_x - p2.x) / (p3.x - p2.x); 76 | let ry1 = (intersect_y - p2.y) / (p3.y - p2.y); 77 | 78 | if ((rx0 >= 0.0 && rx0 <= 1.0) || (ry0 >= 0.0 && ry0 <= 1.0)) && 79 | ((rx1 >= 0.0 && rx1 <= 1.0) || (ry1 >= 0.0 && ry1 <= 1.0)) { 80 | Some(Point { 81 | x: intersect_x, 82 | y: intersect_y 83 | }) 84 | } 85 | else { 86 | None 87 | } 88 | } 89 | } 90 | 91 | pub fn tangent_point_to_circle(point: &Point, circle: &Circle, anticlockwise: bool) -> Point { 92 | let d = dist(point, &circle.center()); 93 | let mut dir = -1.0; 94 | if anticlockwise { 95 | dir = 1.0; 96 | } 97 | let angle = (-circle.r / d).cos() * dir; 98 | let base_angle = (circle.y - point.y).atan2(circle.x - point.x); 99 | let total_angle = base_angle + angle; 100 | 101 | Point { 102 | x: circle.x + (total_angle).cos() * circle.r, 103 | y: circle.y + (total_angle).sin() * circle.r 104 | } 105 | } 106 | 107 | 108 | 109 | #[cfg(test)] 110 | mod tests { 111 | use super::*; 112 | 113 | #[test] 114 | fn test_intersect() { 115 | let p0 = Point::new(-10.0, 0.0); 116 | let p1 = Point::new( 10.0, 0.0); 117 | let p2 = Point::new(0.0, -10.0); 118 | let p3 = Point::new(0.0, 10.0); 119 | assert_eq!(segment_intersect(p0, p1, p2, p3).expect("no intersection"), Point::new(0.0, 0.0)); 120 | } 121 | 122 | #[test] 123 | fn test_no_intersect() { 124 | let p0 = Point::new(-10.0, 0.0); 125 | let p1 = Point::new( 10.0, 0.0); 126 | let p2 = Point::new(20.0, -10.0); 127 | let p3 = Point::new(20.0, 10.0); 128 | assert_eq!(segment_intersect(p0, p1, p2, p3), None); 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/geom/point.rs: -------------------------------------------------------------------------------- 1 | #[derive(PartialEq)] 2 | #[derive(Debug)] 3 | pub struct Point { 4 | pub x: f64, 5 | pub y: f64, 6 | } 7 | 8 | impl Point { 9 | pub fn new(x: f64, y: f64) -> Point { 10 | Point { x: x, y: y } 11 | } 12 | 13 | pub fn from_polar(angle: f64, radius: f64) -> Point { 14 | Point::new((angle).cos() * radius, (angle).sin() * radius) 15 | } 16 | 17 | pub fn angle(&self) -> f64 { 18 | self.y.atan2(self.x) 19 | } 20 | 21 | pub fn magnitude(&self) -> f64 { 22 | (self.x * self.x + self.y * self.y).sqrt() 23 | } 24 | 25 | pub fn dist(&self, other: &Point) -> f64 { 26 | super::dist(self, other) 27 | } 28 | 29 | pub fn translate(&self, x: f64, y: f64) -> Point { 30 | Point::new(self.x + x, self.y + y) 31 | } 32 | 33 | pub fn scale(&self, scale_x: f64, scale_y: f64) -> Point { 34 | Point::new(self.x * scale_x, self.y * scale_y) 35 | } 36 | 37 | pub fn rotate(&self, angle: &f64) -> Point { 38 | let x = self.x * angle.cos() + self.y * angle.sin(); 39 | let y = self.y * angle.cos() - self.x * angle.sin(); 40 | Point::new(x, y) 41 | } 42 | 43 | pub fn clone(&self) -> Point { 44 | Point::new(self.x, self.y) 45 | } 46 | } 47 | 48 | #[cfg(test)] 49 | mod tests { 50 | use super::*; 51 | use std::f64::consts::PI; 52 | 53 | fn almost_eq(m: f64, n: f64) -> bool { 54 | let max_diff = 0.0001; 55 | let diff = f64::abs(m - n); 56 | diff < max_diff 57 | } 58 | 59 | #[test] 60 | fn test_new_point() { 61 | let point = Point::new(0.0, 0.0); 62 | assert_eq!(point.x, 0.0); 63 | assert_eq!(point.y, 0.0); 64 | let point = Point::new(10.0, -20.0); 65 | assert_eq!(point.x, 10.0); 66 | assert_eq!(point.y, -20.0); 67 | } 68 | 69 | #[test] 70 | fn test_from_polar() { 71 | let point = Point::from_polar(0.0, 100.0); 72 | assert_eq!(point.x, 100.0); 73 | assert_eq!(point.y, 0.0); 74 | let point = Point::from_polar(-PI / 2.0, 100.0); 75 | assert!(almost_eq(point.x, 0.0)); 76 | assert!(almost_eq(point.y, -100.0)); 77 | let point = Point::from_polar(PI, 100.0); 78 | assert!(almost_eq(point.x, -100.0)); 79 | assert!(almost_eq(point.y, 0.0)); 80 | let point = Point::from_polar(PI / 2.0, 100.0); 81 | assert!(almost_eq(point.x, 0.0)); 82 | assert!(almost_eq(point.y, 100.0)); 83 | let point = Point::from_polar(0.0, 0.0); 84 | assert_eq!(point.x, 0.0); 85 | assert_eq!(point.y, 0.0); 86 | } 87 | 88 | #[test] 89 | fn test_angle() { 90 | let point = Point::new(100.0, 0.0); 91 | assert_eq!(point.angle(), 0.0); 92 | let point = Point::new(100.0, 100.0); 93 | assert_eq!(point.angle(), PI / 4.0); 94 | let point = Point::new(0.0, 100.0); 95 | assert_eq!(point.angle(), PI / 2.0); 96 | let point = Point::new(-100.0, 0.0); 97 | assert_eq!(point.angle(), PI); 98 | let point = Point::new(0.0, -100.0); 99 | assert_eq!(point.angle(), -PI / 2.0); 100 | } 101 | 102 | #[test] 103 | fn test_magnitude() { 104 | let point = Point::new(100.0, 0.0); 105 | assert_eq!(point.magnitude(), 100.0); 106 | let point = Point::new(0.0, 100.0); 107 | assert_eq!(point.magnitude(), 100.0); 108 | let point = Point::new(-100.0, 0.0); 109 | assert_eq!(point.magnitude(), 100.0); 110 | let point = Point::new(0.0, -100.0); 111 | assert_eq!(point.magnitude(), 100.0); 112 | let point = Point::new(3.0, 4.0); 113 | assert_eq!(point.magnitude(), 5.0); 114 | let point = Point::new(0.0, 0.0); 115 | assert_eq!(point.magnitude(), 0.0); 116 | } 117 | 118 | #[test] 119 | fn test_dist() { 120 | let point_a = Point::new(10.0, 10.0); 121 | let point_b = Point::new(13.0, 14.0); 122 | assert_eq!(point_a.dist(&point_b), 5.0); 123 | let point_a = Point::new(-15.0, -10.0); 124 | let point_b = Point::new(25.0, 20.0); 125 | assert_eq!(point_a.dist(&point_b), 50.0); 126 | } 127 | } 128 | 129 | 130 | -------------------------------------------------------------------------------- /src/geom/rect.rs: -------------------------------------------------------------------------------- 1 | use super::point::Point; 2 | 3 | pub struct Rect { 4 | pub x: f64, 5 | pub y: f64, 6 | pub w: f64, 7 | pub h: f64, 8 | } 9 | 10 | impl Rect { 11 | pub fn contains_point(&self, p: Point) -> bool { 12 | p.x >= self.x && p.y >= self.y && p.x <= self.x + self.w && p.y <= self.y + self.h 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/gif.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | use canvas::Canvas; 3 | 4 | pub fn make_gif(canvas: &Canvas, 5 | frame_count: i32, 6 | fps: u8, 7 | temp_dir: &str, 8 | output_file: &str, 9 | render_fn: fn(&Canvas, f64)) { 10 | let frames_spec = format!("{}/*.png", temp_dir); 11 | make_frames(canvas, frame_count, temp_dir, render_fn); 12 | convert_gif(fps, &frames_spec, output_file); 13 | } 14 | 15 | // requires imagemagick to be installed. 16 | pub fn convert_gif(fps: u8, input: &str, output: &str) { 17 | let delay = 100.0 / fps as f64; 18 | Command::new("sh") 19 | .arg("-c") 20 | .arg(format!("convert -delay {} {} {}", delay, input, output)) 21 | .output() 22 | .expect("Unable to make animated gif"); 23 | } 24 | 25 | pub fn make_frames(canvas: &Canvas, num_frames: i32, frames_path: &str, render_fn: fn(&Canvas, f64)) { 26 | for i in 0..num_frames { 27 | render_fn(canvas, i as f64 / num_frames as f64); 28 | let filename = format!("{}/anim_{:03}.png", frames_path, i); 29 | canvas.write(filename.as_str()); 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod canvas; 2 | pub mod color; 3 | pub mod geom; 4 | pub mod gif; 5 | pub mod file; 6 | pub mod math; 7 | pub mod random; 8 | pub mod util; 9 | -------------------------------------------------------------------------------- /src/math.rs: -------------------------------------------------------------------------------- 1 | use std::f64::consts; 2 | 3 | pub static PI: f64 = consts::PI; 4 | pub static TWO_PI: f64 = consts::PI * 2.0; 5 | pub static HALF_PI: f64 = consts::PI / 2.0; 6 | 7 | pub fn norm(value: f64, min: f64, max: f64) -> f64 { 8 | (value - min) / (max - min) 9 | } 10 | 11 | pub fn lerp(min: f64, max: f64, t: f64) -> f64 { 12 | min + (max - min) * t 13 | } 14 | 15 | pub fn map(src_value: f64, src_min: f64, src_max: f64, dst_min: f64, dst_max: f64) -> f64 { 16 | let norm = norm(src_value, src_min, src_max); 17 | lerp(dst_min, dst_max, norm) 18 | } 19 | 20 | pub fn clamp(value: f64, min: f64, max: f64) -> f64 { 21 | // let min and max be reversed and still work. 22 | let mut real_min = min; 23 | let mut real_max = max; 24 | if min > max { 25 | real_min = max; 26 | real_max = min; 27 | } 28 | let mut result = value; 29 | if value < real_min { 30 | result = real_min; 31 | } 32 | if value > real_max { 33 | result = real_max; 34 | } 35 | result 36 | } 37 | 38 | pub fn sin_range(angle: f64, min: f64, max: f64) -> f64 { 39 | map(angle.sin(), -1.0, 1.0, min, max) 40 | } 41 | 42 | pub fn cos_range(angle: f64, min: f64, max: f64) -> f64 { 43 | map(angle.cos(), -1.0, 1.0, min, max) 44 | } 45 | 46 | 47 | #[cfg(test)] 48 | mod tests { 49 | use super::*; 50 | use util::approx_eq; 51 | 52 | #[test] 53 | fn test_norm() { 54 | // in range 55 | assert_eq!(norm(25.0, 0.0, 100.0), 0.25); 56 | assert_eq!(norm(50.0, 0.0, 100.0), 0.5); 57 | assert_eq!(norm(0.0, 0.0, 100.0), 0.0); 58 | assert_eq!(norm(100.0, 0.0, 100.0), 1.0); 59 | // reverse range 60 | assert_eq!(norm(25.0, 100.0, 0.0), 0.75); 61 | // below range 62 | assert_eq!(norm(-25.0, 0.0, 100.0), -0.25); 63 | // above range 64 | assert_eq!(norm(125.0, 0.0, 100.0), 1.25); 65 | } 66 | 67 | #[test] 68 | fn test_lerp() { 69 | // in range 70 | assert_eq!(lerp(0.0, 100.0, 0.25), 25.0); 71 | assert_eq!(lerp(0.0, 100.0, 0.5), 50.0); 72 | assert_eq!(lerp(0.0, 100.0, 0.75), 75.0); 73 | assert_eq!(lerp(0.0, 100.0, 0.0), 0.0); 74 | assert_eq!(lerp(0.0, 100.0, 1.0), 100.0); 75 | // reverse range 76 | assert_eq!(lerp(100.0, 0.0, 0.25), 75.0); 77 | // below range 78 | assert_eq!(lerp(0.0, 100.0, -0.25), -25.0); 79 | // above range 80 | assert_eq!(lerp(0.0, 100.0, 1.25), 125.0); 81 | } 82 | 83 | #[test] 84 | fn test_clamp() { 85 | // in range 86 | assert_eq!(clamp(0.0, 0.0, 100.0), 0.0); 87 | assert_eq!(clamp(100.0, 0.0, 100.0), 100.0); 88 | // out of range 89 | assert_eq!(clamp(-50.0, 0.0, 100.0), 0.0); 90 | assert_eq!(clamp(110.0, 0.0, 100.0), 100.0); 91 | // reversed min/max 92 | assert_eq!(clamp(-50.0, 100.0, 0.0), 0.0); 93 | assert_eq!(clamp(110.0, 100.0, 0.0), 100.0); 94 | } 95 | 96 | #[test] 97 | fn test_sin_range() { 98 | assert!(approx_eq(sin_range(0.0, 100.0, 400.0), 250.0)); 99 | assert!(approx_eq(sin_range(HALF_PI, 100.0, 400.0), 400.0)); 100 | assert!(approx_eq(sin_range(PI, 100.0, 400.0), 250.0)); 101 | assert!(approx_eq(sin_range(PI * 1.5, 100.0, 400.0), 100.0)); 102 | } 103 | 104 | #[test] 105 | fn test_cos_range() { 106 | assert!(approx_eq(cos_range(0.0, 100.0, 400.0), 400.0)); 107 | assert!(approx_eq(cos_range(HALF_PI, 100.0, 400.0), 250.0)); 108 | assert!(approx_eq(cos_range(PI, 100.0, 400.0), 100.0)); 109 | assert!(approx_eq(cos_range(PI * 1.5, 100.0, 400.0), 250.0)); 110 | } 111 | } 112 | 113 | -------------------------------------------------------------------------------- /src/random.rs: -------------------------------------------------------------------------------- 1 | extern crate rand; 2 | 3 | use self::rand::{ Rng, StdRng, SeedableRng }; 4 | 5 | pub struct Random { 6 | rng: StdRng, 7 | } 8 | 9 | impl Random { 10 | pub fn from_seed(seed: usize) -> Random { 11 | let rand_seed: &[_] = &[seed]; 12 | Random { 13 | rng: SeedableRng::from_seed(rand_seed), 14 | } 15 | } 16 | 17 | pub fn new() -> Random { 18 | let seed = rand::random::(); 19 | Random::from_seed(seed) 20 | } 21 | 22 | pub fn reseed(&mut self, seed: usize) { 23 | let rand_seed: &[_] = &[seed]; 24 | self.rng.reseed(rand_seed); 25 | } 26 | 27 | pub fn float(&mut self, min: f64, max: f64) -> f64 { 28 | self.rng.gen_range(min, max) 29 | } 30 | 31 | pub fn int(&mut self, min: i32, max: i32) -> i32 { 32 | self.rng.gen_range(min, max) 33 | } 34 | 35 | pub fn boolean(&mut self) -> bool { 36 | self.weighted_bool(0.5) 37 | } 38 | 39 | pub fn weighted_bool(&mut self, weight: f64) -> bool { 40 | self.float(0.0, 1.0) < weight 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | pub fn approx_eq(m: f64, n: f64) -> bool { 2 | (m - n).abs() < 0.000001 3 | } 4 | 5 | #[test] 6 | fn test_approx() { 7 | assert!(approx_eq(3.141592, 355.0 / 113.0)); 8 | } 9 | 10 | --------------------------------------------------------------------------------