Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7e4fb09
Rust 2021, deps bump for step/
virtualritz May 17, 2024
8216aee
Clippy for step/
virtualritz May 17, 2024
9ed5eb9
Rust 2021, deps bump for triangulate/
virtualritz May 17, 2024
e3960d1
Clippy for triangulate/
virtualritz May 17, 2024
9237100
Workspace resolver v2.
virtualritz May 17, 2024
94bab3d
Rust 2021, deps bump for gui/
virtualritz May 17, 2024
5cc5574
Clippy & rustfmt for gui/
virtualritz May 17, 2024
a7efac6
Rust 2021, deps bump for nurbs/
virtualritz May 17, 2024
240d510
Clippy & rustfmt for nurbs/
virtualritz May 17, 2024
eb67375
Renamed some structs to comply w. https://rust-lang.github.io/api-gui…
virtualritz May 17, 2024
d8584e6
Renamed 'parallel' feature to 'rayon' and switched on by default.
virtualritz May 17, 2024
8144d22
Rust 2021, deps bump, rustfmt for cdt/
virtualritz May 17, 2024
b0f7c04
Clippy for cdt/
virtualritz May 17, 2024
98f1ca5
Rust 2021, deps bump, rustfmt for express/
virtualritz May 17, 2024
ca924e2
Clippy for express/
virtualritz May 17, 2024
5871f21
Rust 2021, deps bump, rustfmt for wasm/
virtualritz May 17, 2024
f0de0f7
Ran cargo sort.
virtualritz May 17, 2024
8e0bad1
Another exmaple.
virtualritz May 17, 2024
1799eb6
Update to Rust 2024 edition and fix all warnings.
virtualritz Sep 22, 2025
6589f15
CDT: Add test cases for problematic input
beholdnec Oct 19, 2024
0f4e65e
Update to Rust 2024 edition and fix all warnings.
virtualritz Sep 23, 2025
d65b130
Fix deprecated rand API usage and update CDT tests.
virtualritz Sep 23, 2025
71825bf
Handle FaceSurface and unknown logical values
virtualritz Nov 22, 2025
938eb87
Handle graceful shutdown on close
virtualritz Nov 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
[profile.release]
debug = true
codegen-units = 1

[workspace]
resolver = "2"
members = [
"cdt",
"express",
"step",
"gui",
"nurbs",
"step",
"triangulate",
]
exclude = [
"wasm",
]
exclude = ["wasm"]
[profile.release]
debug = true
codegen-units = 1
12 changes: 6 additions & 6 deletions cdt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "cdt"
version = "0.1.0"
authors = ["Matt Keeter <matt@formlabs.com>"]
edition = "2018"
edition = "2024"
license = "MIT OR Apache-2.0"
repository = "https://github.com/Formlabs/foxtrot"
homepage = "https://github.com/Formlabs/foxtrot/tree/master/cdt"
Expand All @@ -13,14 +13,14 @@ categories = ["graphics", "mathematics", "algorithms"]

[dependencies]
geometry-predicates = "0.3.0"
thiserror = "1.0"
thiserror = "2"

[features]
long-indexes = []

[dev-dependencies]
clap = "2.33"
itertools = "0.10.0"
rand = "0.8.3"
rand_chacha = "0.3.0"
clap = "3"
itertools = "0.14"
rand = "0.9"
rand_chacha = "0.9"
rusttype = "0.9"
84 changes: 47 additions & 37 deletions cdt/examples/font.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
use clap::{Arg, App};
use rusttype::{point, Font, Scale, OutlineBuilder};
use clap::{App, Arg};
use rusttype::{Font, OutlineBuilder, Scale, point};

const BEZIER_RESOLUTION: usize = 4;

#[derive(Default)]
struct Builder {
points: Vec<(f64, f64)>,
contours: Vec<Vec<usize>>,
x: f32, y: f32,
dx: f32, dy: f32,
x: f32,
y: f32,
dx: f32,
dy: f32,
}

impl Builder {
Expand All @@ -20,7 +22,8 @@ impl Builder {
fn set_position(&mut self, x: f32, y: f32) {
self.x = x;
self.y = -y;
self.points.push(((x + self.dx) as f64, (self.dy - y) as f64));
self.points
.push(((x + self.dx) as f64, (self.dy - y) as f64));
}
}

Expand Down Expand Up @@ -52,8 +55,7 @@ impl OutlineBuilder for Builder {
let y0 = -self.y;
for i in 1..=BEZIER_RESOLUTION {
let t = i as f32 / (BEZIER_RESOLUTION as f32);
let f = |a, b, c| (1.0 - t).powf(2.0) * a +
2.0 * (1.0 - t) * t * b + t.powf(2.0) * c;
let f = |a, b, c| (1.0 - t).powf(2.0) * a + 2.0 * (1.0 - t) * t * b + t.powf(2.0) * c;
self.line_to(f(x0, x1, x2), f(y0, y1, y2));
}
}
Expand All @@ -64,12 +66,13 @@ impl OutlineBuilder for Builder {

for i in 1..=BEZIER_RESOLUTION {
let t = i as f32 / (BEZIER_RESOLUTION as f32);
let f = |a, b, c, d|
(1.0 - t).powf(3.0) * a +
3.0 * (1.0 - t).powf(2.0) * t * b +
3.0 * (1.0 - t) * t.powf(2.0) * c +
t.powf(3.0) * d;
self.line_to(f(x0, x1, x2, x3), f(y0, y1, y2, y3));
let f = |a, b, c, d| {
(1.0 - t).powf(3.0) * a
+ 3.0 * (1.0 - t).powf(2.0) * t * b
+ 3.0 * (1.0 - t) * t.powf(2.0) * c
+ t.powf(3.0) * d
};
self.line_to(f(x0, x1, x2, x3), f(y0, y1, y2, y3));
}
}
}
Expand All @@ -78,31 +81,40 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let matches = App::new("font")
.author("Matt Keeter <matt.j.keeter@gmail.com>")
.about("Triangulates a few characters from a font")
.arg(Arg::with_name("font")
.short("f")
.long("font")
.help("path to the font TTF")
.takes_value(true))
.arg(Arg::with_name("output")
.short("o")
.long("out")
.help("svg file to target")
.takes_value(true))
.arg(Arg::with_name("check")
.short("c")
.long("check")
.help("check invariants after each step (slow)"))
.arg(Arg::with_name("text")
.short("t")
.long("text")
.help("text to triangulate")
.takes_value(true))
.arg(
Arg::with_name("font")
.short('f')
.long("font")
.help("path to the font TTF")
.takes_value(true),
)
.arg(
Arg::with_name("output")
.short('o')
.long("out")
.help("svg file to target")
.takes_value(true),
)
.arg(
Arg::with_name("check")
.short('c')
.long("check")
.help("check invariants after each step (slow)"),
)
.arg(
Arg::with_name("text")
.short('t')
.long("text")
.help("text to triangulate")
.takes_value(true),
)
.get_matches();

let font_path = matches.value_of("font")
let font_path = matches
.value_of("font")
.unwrap_or("/Library/Fonts/Georgia.ttf");
let font = {
let data = std::fs::read(&font_path)?;
let data = std::fs::read(font_path)?;
Font::try_from_vec(data).unwrap()
};

Expand All @@ -119,9 +131,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

// Then, do the work of triangulation
let now = std::time::Instant::now();
let mut t = cdt::Triangulation::new_from_contours(
&builder.points,
&builder.contours)?;
let mut t = cdt::Triangulation::new_from_contours(&builder.points, &builder.contours)?;
while !t.done() {
t.step()?;
if matches.is_present("check") {
Expand Down
74 changes: 42 additions & 32 deletions cdt/examples/fuzz.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::iter::repeat_with;
use rand::{Rng, SeedableRng};
use std::iter::repeat_with;

use clap::{Arg, App};
use clap::{App, Arg};
use itertools::Itertools;

const N: usize = 64;
Expand All @@ -10,27 +10,36 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let matches = App::new("fuzz")
.author("Matt Keeter <matt.j.keeter@gmail.com>")
.about("Fuzzes the triangulator")
.arg(Arg::with_name("num")
.short("n")
.long("num")
.help("number of points")
.takes_value(true))
.arg(Arg::with_name("output")
.short("o")
.long("out")
.help("svg file to target")
.takes_value(true))
.arg(Arg::with_name("check")
.short("c")
.long("check")
.help("check invariants after each step (slow)"))
.arg(Arg::with_name("lock")
.short("l")
.long("lock")
.help("lock three edges to test constrained triangulation"))
.arg(
Arg::with_name("num")
.short('n')
.long("num")
.help("number of points")
.takes_value(true),
)
.arg(
Arg::with_name("output")
.short('o')
.long("out")
.help("svg file to target")
.takes_value(true),
)
.arg(
Arg::with_name("check")
.short('c')
.long("check")
.help("check invariants after each step (slow)"),
)
.arg(
Arg::with_name("lock")
.short('l')
.long("lock")
.help("lock three edges to test constrained triangulation"),
)
.get_matches();

let num = matches.value_of("num")
let num = matches
.value_of("num")
.map(|s| s.parse())
.unwrap_or(Ok(N))?;

Expand All @@ -43,28 +52,29 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
i += 1;

let seed = rand::thread_rng().gen();
let seed = rand::rng().random();
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(seed);

// We generate random points as f32, to make it more likely that
// some will line up exactly on one axis or another, which can trigger
// interesting edge cases. Experimentally, we have X or Y collisions
// at a rate of about one per 4K fuzzed samples.
let points: Vec<_> = repeat_with(|| rng.gen_range(0.0..1.0))
let points: Vec<_> = repeat_with(|| rng.random_range(0.0..1.0))
.tuple_windows()
.map(|(a, b): (f32, f32)| (a as f64, b as f64))
.take(num)
.collect();

// Generator to build the triangulation
let gen = || if matches.is_present("lock") {
cdt::Triangulation::new_with_edges(&points,
&[(0, 1), (1, 2), (2, 0)])
} else {
cdt::Triangulation::new(&points)
let r#gen = || {
if matches.is_present("lock") {
cdt::Triangulation::new_with_edges(&points, &[(0, 1), (1, 2), (2, 0)])
} else {
cdt::Triangulation::new(&points)
}
};

let mut t = gen()?;
let mut t = r#gen()?;
t.check();
let result = std::panic::catch_unwind(move || {
while !t.done() {
Expand All @@ -79,7 +89,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
if result.is_err() {
let mut safe_steps = 0;
for i in 0..points.len() {
let mut t = gen()?;
let mut t = r#gen()?;
let result = std::panic::catch_unwind(move || {
for _ in 0..i {
t.step().expect("oh no");
Expand All @@ -95,7 +105,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
}
}

let mut t = gen()?;
let mut t = r#gen()?;
for _ in 0..safe_steps {
t.step().expect("Failed too early");
}
Expand All @@ -107,7 +117,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("{}", t.to_svg(true));
}
eprintln!("Crashed with seed: {}", seed);
break Ok(())
break Ok(());
}
}
}
Loading