GithubHelp home page GithubHelp logo

mourner / delaunator-rs Goto Github PK

View Code? Open in Web Editor NEW
187.0 5.0 26.0 122 KB

Fast 2D Delaunay triangulation in Rust. A port of Delaunator.

Home Page: https://docs.rs/delaunator

License: ISC License

Rust 100.00%
delaunay-triangulation geometry spatial algorithms rust

delaunator-rs's Introduction

delaunator-rs

An incredibly fast and robust Rust library for Delaunay triangulation of 2D points. A port of Delaunator.

delaunator on Crates.io Tests

Example

use delaunator::{Point, triangulate};

let points = vec![
    Point { x: 0., y: 0. },
    Point { x: 1., y: 0. },
    Point { x: 1., y: 1. },
    Point { x: 0., y: 1. },
];

let result = triangulate(&points);

println!("{:?}", result.triangles); // [0, 2, 1, 0, 3, 2]

Performance

Results for 3.1 GHz Intel Core i7 on a Macbook Pro 15'' (2017):

points time
100 16.478µs
1,000 277.64µs
10,000 3.753ms
100,000 63.627ms
1,000,000 898.78ms
10,000,000 11.857s

delaunator-rs's People

Contributors

andreesteve avatar andribaal avatar chris--b avatar martinfrances107 avatar mourner avatar notgull avatar oovm avatar rreverser avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

delaunator-rs's Issues

Crash: index out of bounds: the len is 14 but the index is 18446744073709551615

I'm trying to triangulate a detailed map of the world. On several "chunks" (think islands on the map) of my data, delaunator crashes. Here is one example:

extern crate delaunator;

use delaunator::{Point, triangulate};

fn main() {
    let points = vec!(
        Point { x: 11.484139442443848, y: 65.20500183105469 },
        Point { x: 11.484139442443848, y: 65.2066421508789 },
        Point { x: 11.484999656677246, y: 65.20708465576172 },
        Point { x: 11.491666793823242, y: 65.20708465576172 },
        Point { x: 11.492444038391113, y: 65.2066650390625 },
        Point { x: 11.492500305175781, y: 65.20580291748047 },
        Point { x: 11.491639137268066, y: 65.20541381835938 },
        Point { x: 11.489860534667969, y: 65.20541381835938 },
        Point { x: 11.488332748413086, y: 65.20622253417969 },
        Point { x: 11.487471580505371, y: 65.2058334350586 },
        Point { x: 11.487388610839844, y: 65.20500183105469 },
        Point { x: 11.486528396606445, y: 65.20455932617188 },
        Point { x: 11.484916687011719, y: 65.20458221435547 },
        Point { x: 11.484139442443848, y: 65.20500183105469 },
    );
    let result = triangulate(&points).expect("No triangulation exists.");
    println!("{:?}", result.triangles);
}

When I run this code, delaunator panics:

> rustc -V
rustc 1.30.1 (1433507eb 2018-11-07)
> RUST_BACKTRACE=1 cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s                                                                                 
     Running `target/debug/tmprusttest`
thread 'main' panicked at 'index out of bounds: the len is 14 but the index is 18446744073709551615', libcore/slice/mod.rs:2046:10
stack backtrace:
   0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
             at libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
   1: std::sys_common::backtrace::print
             at libstd/sys_common/backtrace.rs:71
             at libstd/sys_common/backtrace.rs:59
   2: std::panicking::default_hook::{{closure}}
             at libstd/panicking.rs:211
   3: std::panicking::default_hook
             at libstd/panicking.rs:227
   4: std::panicking::rust_panic_with_hook
             at libstd/panicking.rs:477
   5: std::panicking::continue_panic_fmt
             at libstd/panicking.rs:391
   6: rust_begin_unwind
             at libstd/panicking.rs:326
   7: core::panicking::panic_fmt
             at libcore/panicking.rs:77
   8: core::panicking::panic_bounds_check
             at libcore/panicking.rs:59
   9: <usize as core::slice::SliceIndex<[T]>>::index
             at libcore/slice/mod.rs:2046
  10: core::slice::<impl core::ops::index::Index<I> for [T]>::index
             at libcore/slice/mod.rs:1914
  11: <alloc::vec::Vec<T> as core::ops::index::Index<I>>::index
             at liballoc/vec.rs:1725
  12: delaunator::Triangulation::legalize
             at /home/trehn/.cargo/registry/src/github.com-1ecc6299db9ec823/delaunator-0.2.0/src/lib.rs:232
  13: delaunator::triangulate
             at /home/trehn/.cargo/registry/src/github.com-1ecc6299db9ec823/delaunator-0.2.0/src/lib.rs:484
  14: tmprusttest::main
             at src/main.rs:27
  15: std::rt::lang_start::{{closure}}
             at libstd/rt.rs:74
  16: std::panicking::try::do_call
             at libstd/rt.rs:59
             at libstd/panicking.rs:310
  17: __rust_maybe_catch_panic
             at libpanic_unwind/lib.rs:103
  18: std::rt::lang_start_internal
             at libstd/panicking.rs:289
             at libstd/panic.rs:392
             at libstd/rt.rs:58
  19: std::rt::lang_start
             at libstd/rt.rs:74
  20: main
  21: __libc_start_main
  22: _start

Any idea what's going on here?

sort_unstable_by has no fallback to equal

I use delaunator in a project of mine and sometimes on specific conditions the delaunator crashes because of the partial_cmp of 2 floats without a fallback. Specifically, this needs to be changed:

fn sortf(f: &mut [(usize, f64)]) {
    f.sort_unstable_by(|&(_, da), &(_, db)| da.partial_cmp(&db).unwrap());
}

to:

fn sortf(f: &mut [(usize, f64)]) {
    f.sort_unstable_by(|&(_, da), &(_, db)| da.partial_cmp(&db).unwrap_or(core::cmp::Ordering::Equal));
}

Algorithm fails to generate correct Delaunay triangulation

I was using this library for a project of mine, and I came across a bug caused by the algorithm generating a triangulation that badly fails to meet the Delaunay condition.

Here is code that demonstrates the bug:

use delaunator::{triangulate, Point};

fn main {
    let points_raw = [
        [-1.0849334460551594, -1.0849334460551594], // 0
        [-1.5265847189170323, -1.5265847189170323], // 1
        [-2.095815826893781, -1.274850699584578], // 2
        [-2.770865690566727, -0.9763199041819535], // 3
        [-3.6843898126113546, -0.5723274031100705], // 4
        [-4.403658177176129, -0.25424163117441645], // 5
        [-5.02567003534954, 0.020833892521026076], // 6
        [-5.701084620214019, 0.3195259804640507], // 7
        [-6.561463270870451, 0.7000156846325452], // 8
        [-7.31105511135829, 1.0315115642859167], // 9
        [-8.0, 1.336187228503853], // 10
        [-7.339371017948894, 1.1048861305058357], // 11
        [-6.616986689211032, 0.8519630935920505], // 12
        [-5.816767071935726, 0.5717881676590966], // 13
        [-5.121128245254447, 0.3282293338915143], // 14
        [-4.512948676142796, 0.11529195763725286], // 15
        [-3.850960067633096, -0.11648517623155441], // 16
        [-3.1594534122386113, -0.3585972436874558], // 17
        [-2.288934450277422, -0.663385554827794], // 18
        [-1.6751076145244035, -0.8783001664294665], // 19
    ];
    let mut points = Vec::with_capacity(20);
    for [x, y] in &points_raw {
        points.push(Point { x: *x, y: *y });
    }
    let tris = triangulate(&points).unwrap();
    println!("{:?}", tris.triangles);
}

The output is:

[5, 6, 15, 6, 14, 15, 14, 16, 15, 15, 16, 5, 6, 7, 14, 16, 4, 5, 5, 4, 6, 7, 13, 14, 16, 17, 4, 14, 17, 16, 9, 8, 7, 7, 8, 13, 17, 3, 4, 4, 3, 6, 8, 12, 13, 19, 18, 17, 17, 18, 3, 6, 9, 7, 8, 9, 12, 18, 2, 3, 9, 11, 12, 12, 11, 13, 13, 11, 14, 14, 19, 17, 18, 19, 2, 19, 1, 2, 2, 10, 3, 10, 0, 3]

Which contains the triangle [10, 0, 3]. By plotting these points we can see that this triangle definitely shouldn't be part of the Delaunay triangulation, as the circumcircle of these points contains most of the other points:
Screenshot from 2019-08-27 11-25-40
It looks like there are other triangles in this triangulation that fail the condition. I have idea why this specific set of points causes a problem.

A disconnected triangulation may be generated

Depending on the input, a triangulation may be returned, however not all points are connected.

For example, for the input below,

[
    [0, 0],
    [0.0000000000000002220446049250313, 0.0000000000000002220446049250313],
    [0.0000000000000004440892098500626, 0.0000000000000002220446049250313],
    [-0.0000000000000004440892098500626, 0.0000000000000002220446049250313],
    [-0.0000000000000005551115123125783, 0.0000000000000004440892098500626]
]

The following triangulation is returned:

Triangulation {
    triangles: [
        0,
        1,
        2,
        0,
        3,
        1,
        1,
        3,
        2,
    ],
    halfedges: [
        5,
        8,
        18446744073709551615,
        18446744073709551615,
        6,
        0,
        4,
        18446744073709551615,
        1,
    ],
    hull: [
        0,
        3,
        2,
    ],
}

which does not include the last point.

image

Either the algorithm does not generate valid triangles, or I am missing something

I'd like to use this algorithm in my breadx project, as a way of breaking shapes into triangles before sending them to the X11 server. As a test, I ran the algorithm on this shape:

    // create an arbitrary polygon and convert it to triangles
    let t: Vec<_> = tesselate_shape(&vec![
        Pointfix {
            x: double_to_fixed(100.0),
            y: double_to_fixed(100.0),
        },
        Pointfix {
            x: double_to_fixed(100.0),
            y: double_to_fixed(150.0),
        },
        Pointfix {
            x: double_to_fixed(150.0),
            y: double_to_fixed(250.0),
        },
        Pointfix {
            x: double_to_fixed(200.0),
            y: double_to_fixed(250.0),
        },
        Pointfix {
            x: double_to_fixed(200.0),
            y: double_to_fixed(150.0),
        },
        Pointfix {
            x: double_to_fixed(250.0),
            y: double_to_fixed(100.0),
        },
        Pointfix {
            x: double_to_fixed(300.0),
            y: double_to_fixed(150.0),
        },
        Pointfix {
            x: double_to_fixed(300.0),
            y: double_to_fixed(300.0),
        },
        Pointfix {
            x: double_to_fixed(150.0),
            y: double_to_fixed(450.0),
        },
        Pointfix {
            x: double_to_fixed(50.0),
            y: double_to_fixed(250.0),
        },
        Pointfix {
            x: double_to_fixed(50.0),
            y: double_to_fixed(150.0),
        },
    ])
    .collect();

I implemented tesselate_shape using the following:

/// From the given set of points, return an iterator over the triangles.
#[inline]
pub fn tesselate_shape<'a>(points: &'a [Pointfix]) -> impl Iterator<Item = Triangle> + 'a {
    let floating_points: Vec<Point> = points
        .iter()
        .copied()
        .map(|Pointfix { x, y }| Point {
            x: fixed_to_double(x),
            y: fixed_to_double(y),
        })
        .collect();

    let vector = match triangulate(&floating_points) {
        Some(t) => t.triangles ,
        None => vec![],
    };

    vector
        .into_iter()
        .map(move |index| &points[index])
        .copied()
        .scan(ArrayVec::<[Pointfix; 3]>::new(), |av, point| {
            av.push(point);
            if av.len() == 3 {
                let [p1, p2, p3] = mem::take(av).into_inner();
                Some(Some(Triangle { p1, p2, p3 }))
            } else {
                Some(None)
            }
        })
        .flatten()
}

Note: XRender does things in fixed-point 32-bit numbers by default, so I have to convert them to and from floating point notation.

When I run this, it produces radically different results than expected. Here's what I should see; this is what I get when I manually triangulated the shape:

stage1

However, this happens when I use delaunator:

stage

I feel like this is probably a result of me misunderstanding how the crate is supposed to work; is there something I'm missing?

Master ticket for fixes that need to be ported to match Delaunator

cc @andreesteve

Triangulation.hull clock-wise instead of counter-clockwise?

Thanks for the port!

The docs indicate that the indices returned by Triangulation.hull are counter-clockwise.

hull: Vec<usize>
[−]
A vector of indices that reference points on the convex hull of the triangulation, counter-clockwise.

However I see them being returned clockwise. Which one is the right expectation?

Sample test:

use delaunator::*;

fn main() {
    let p = vec![
        Point { x: 0.0, y: 0.0 },
        Point { x: 1.0, y: 1.0 },
        Point { x: -1.0, y: 1.0 },
    ];
    triangulate(&p)
        .unwrap().hull.iter()
        .for_each(|s| println!("Hull {}: {:?}", *s, p[*s]));
}

for which I get:

Hull 0: [0, 0]
Hull 2: [-1, 1]
Hull 1: [1, 1]

Filtering out new convex hull geometry?

Not quite an issue but there is no discussion section on this project; Is there any mechanism to filter out hull geometry that lies outside of the original concave polygon input? Simply filtering by halfedges[i] != EMPTY ends up eliminating some regular triangles too.

Switch to GitHub Actions for CI

Travis suddenly stopped running builds for this repo, but because its UI is a disaster, I can't really figure out why — the error is Could not authorize build request for mourner/delaunator-rs, and some StackOverflow suggests it might have to do with not being on a paid plan.

Instead of figuring it out, we should just switch to GitHub Actions which is a much better experience overall.

no_std support

I'd like to use this lib on a no_std target. Use of alloc would be fine. Do you have plans to support this?

Robustness questions

Thanks for your library. I saw that you use robust for orient2d but I was wondering why you did not use

I can make a PR if you agree to these changes.

unstable_sort_by panics when given NaN

There is a panic on the following line when one of the values is NaN:

dists.sort_unstable_by(|&(_, da), &(_, db)| da.partial_cmp(&db).unwrap());

You can get around this by implementing a default ordering with something like

dists.sort_unstable_by(|&(_, da), &(_, db)| da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Less));

Or by checking if a given argument contains a NaN in the first place if NaN breaks further work. I think having one of these and returning None is preferable to a panic inside the method. At the very least this behavior should be documented.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.