abellgithub / delaunator-cpp

A really fast C++ library for Delaunay triangulation of 2D points
MIT License
154 stars 33 forks source link

Has anyone been able to write the delaunator-js demo in C++? #15

Closed tankorsmash closed 4 years ago

tankorsmash commented 4 years ago

The demo here lists a bunch of code that works with the JS version of this lib, but because of what I'm assuming is type differences across the implementations, porting it isn't straight forward. Has anyone been able to render the voronoi edges like the js demo does?

tankorsmash commented 4 years ago

Here's my attempt incase anyone knows what the issue is, but I'd definitely love to look at someone else's completed version because mine is broken. Attached is what it renders out too (red lines are the delauntor's coords indexes by triangles, and the circles are the circumcenters, but the black in the corner is supposed to be the vornoi edges, or even triangle edges which I'd expect to be overlaid over the red triangles).

image


    using edge_t = size_t;
    using v_double_t = std::vector<double>;
    using double_pair_t = std::pair<double, double>;
    using v_double_pair_t = std::vector<double_pair_t>;

    auto triangleOfEdge = [](edge_t e) {
        return (edge_t)std::floor(((size_t)e) / (size_t)3.0);
    };

    //returns 3 edge IDs
    auto edgesOfTriangle = [](edge_t t) -> std::vector<edge_t> {
        return std::vector<edge_t>{3* t, 3* t + 1, 3 * t + 2};
    };

    //finds 3 points for a given triangle id
    auto pointsOfTriangle = [edgesOfTriangle](const delaunator::Delaunator& delaunator, edge_t tri_id) ->std::vector<size_t> {
        auto edges = edgesOfTriangle(tri_id);

        std::vector<size_t> points;
        auto find_tri_for_edge = [delaunator](edge_t e) -> size_t{ return delaunator.triangles[e]; };
        std::transform(edges.begin(), edges.end(), std::back_inserter(points), find_tri_for_edge);

        return points;
    };

    auto edgesAroundPoint = [nextHalfEdge](const delaunator::Delaunator& delaunator, edge_t start) {
        std::vector<edge_t> result;

        edge_t incoming = start;
        do {
            result.push_back(incoming);
            const edge_t outgoing = nextHalfEdge(incoming);
            incoming = delaunator.halfedges[outgoing];
        } while (incoming != (edge_t)-1 && incoming != start);

        return result;
    };

    auto triangleCenter = [pointsOfTriangle](v_double_pair_t points, delaunator::Delaunator& delaunator, edge_t tri_id) -> std::pair<double, double>
    {
        auto tri_points = pointsOfTriangle(delaunator, tri_id);
        std::vector<std::pair<double, double>> vertices{};
        std::transform(tri_points.begin(), tri_points.end(), std::back_inserter(vertices), [&points](edge_t tri_point) {
            return points[tri_point];
        });

        auto result = delaunator::circumcenter(
           vertices[0].first, vertices[0].second,
           vertices[1].first, vertices[1].second,
           vertices[2].first, vertices[2].second
        );
        return std::pair<double, double>(result.first, result.second);
    };

    auto forEachVoronoiEdge = [&](v_double_pair_t points, delaunator::Delaunator& delaunator, std::function<void(edge_t, double_pair_t, double_pair_t)> callback) {
        for (auto e = 0;  e < delaunator.triangles.size(); e++) {
            if (e < delaunator.halfedges[e]) {
                auto pt = triangleOfEdge(e);
                auto p = triangleCenter(points, delaunator, pt);
                auto qt = triangleOfEdge(delaunator.halfedges[e]);

                //this seems to be a bug, qt should never be higher than 10k
                if (qt > 10000) {
                    auto qwe = delaunator::INVALID_INDEX;
                    //__debugbreak();
                    continue;
                }
                auto q = triangleCenter(points, delaunator, qt);
                callback(e, p, q);
            }
        }
    };

even the simpler Triangle Edges draws incorrectly, with a ton of 'invalid' prints:

    auto forEachTriangleEdge = [&](delaunator::Delaunator& delaunator, std::function<void(edge_t, double_pair_t, double_pair_t)> callback) {
        for (unsigned e = 0;  e < delaunator.triangles.size(); e++) {
            auto halfedge = delaunator.halfedges[e];
            if (e < halfedge && halfedge != delaunator::INVALID_INDEX) {
                try{
                    auto p = double_pair_t{delaunator.coords.at(delaunator.triangles[e] * 2), delaunator.coords.at(delaunator.triangles[e] * 2 + 1)};
                    auto q = double_pair_t{delaunator.coords.at(delaunator.triangles[nextHalfEdge(e)] * 2), delaunator.coords.at(delaunator.triangles[nextHalfEdge(e)] * 2 + 1)};

                    callback(e, p, q);
                }
                catch (std::out_of_range& ex) { my_print(L"invalid"); }
            }
        }

It seems like I had an issue with the drawing library (https://github.com/ArashPartow/bitmap), so if I offset the black lines by some cartesian offsets that works. Not sure why. Would still love to see someone else's approach at porting the demo if they've got it.

tankorsmash commented 4 years ago

Figured out the issue with mine, I was missing a * 2 when looking into the coords which makes sense. I'll close this and leave the code for anyone else looking to have a demo port (after some tweaking to my pasted in code, I'm sure)

image

    auto nextHalfEdge = [](edge_t edge) { return (edge % 3 == 2) ? edge - 2 : edge + 1;  };

    auto triangleOfEdge = [](edge_t e) {
        return e / 3;
    };

    //returns 3 edge IDs
    auto edgesOfTriangle = [](edge_t t) -> std::vector<edge_t> {
        return std::vector<edge_t>{3* t, 3* t + 1, 3 * t + 2};
    };

    //finds 3 points for a given triangle id
    auto pointsOfTriangle = [edgesOfTriangle](const delaunator::Delaunator& delaunator, edge_t tri_id) ->std::vector<size_t> {
        auto edges = edgesOfTriangle(tri_id);

        std::vector<size_t> points;
        auto find_tri_for_edge = [delaunator](edge_t e) -> size_t{ return delaunator.triangles[e]; };
        std::transform(edges.begin(), edges.end(), std::back_inserter(points), find_tri_for_edge);

        return points;
    };

    auto edgesAroundPoint = [nextHalfEdge](const delaunator::Delaunator& delaunator, edge_t start) {
        std::vector<edge_t> result;

        edge_t incoming = start;
        do {
            result.push_back(incoming);
            const edge_t outgoing = nextHalfEdge(incoming);
            incoming = delaunator.halfedges[outgoing];
        } while (incoming != (edge_t)-1 && incoming != start);

        return result;
    };

    //circumcenter of triangle
    auto triangleCenter = [pointsOfTriangle](v_double_t points, delaunator::Delaunator& delaunator, edge_t tri_id) -> std::pair<double, double>
    {
        auto tri_points = pointsOfTriangle(delaunator, tri_id);
        std::vector<std::pair<double, double>> vertices{};
        std::transform(tri_points.begin(), tri_points.end(), std::back_inserter(vertices), [&points, &delaunator](edge_t tri_point) {
            return double_pair_t{delaunator.coords.at(tri_point*2), delaunator.coords.at(tri_point*2 + 1)}; //this is the * 2 that was missing
        });

        auto result = delaunator::circumcenter(
            vertices[0].first, vertices[0].second,
            vertices[1].first, vertices[1].second,
            vertices[2].first, vertices[2].second
        );
        return std::pair<double, double>(result.first, result.second);
    };

    auto forEachVoronoiEdge = [&](delaunator::Delaunator& delaunator, std::function<void(edge_t, double_pair_t, double_pair_t)> callback) {
        //forEachTriangleEdge
        //for (unsigned e = 0;  e < delaunator.triangles.size(); e++) {
        //    auto halfedge = delaunator.halfedges[e];
        //    if (e < halfedge && halfedge != delaunator::INVALID_INDEX) {
        //        try{
        //            auto p = double_pair_t{delaunator.coords.at(delaunator.triangles[e] * 2), delaunator.coords.at(delaunator.triangles[e] * 2 + 1)};
        //            auto q = double_pair_t{delaunator.coords.at(delaunator.triangles[nextHalfEdge(e)] * 2), delaunator.coords.at(delaunator.triangles[nextHalfEdge(e)] * 2 + 1)};
        //            callback(e, p, q);
        //        }
        //        catch (std::out_of_range& ex) { my_print(L"invalid"); }
        //    }
        //}

        ////forEachVoronoiEdge
        for (unsigned e = 0;  e < delaunator.triangles.size(); e++) {
            auto halfedge = delaunator.halfedges[e];
            if (e > halfedge && halfedge != delaunator::INVALID_INDEX) {
                edge_t pt = triangleOfEdge(e);
                double_pair_t p = triangleCenter(delaunator.coords, delaunator, pt); //now im passing in the coords from delaunator

                edge_t qt = triangleOfEdge(delaunator.halfedges[e]);
                double_pair_t q = triangleCenter(delaunator.coords, delaunator, qt);

                callback(e, p, q);
            }
        }
    };