#pragma once #ifdef DEBUG #include // Example debug print for backtrace - only works on IOS #include #include // // void* callstack[128]; // int i, frames = backtrace(callstack, 128); // char** strs = backtrace_symbols(callstack, frames); // for (i = 0; i < frames; ++i) { // printf("%s\n", strs[i]); // } // free(strs); #endif #include #include #include #include #include #include namespace mapbox { namespace geometry { namespace wagyu { template void set_hole_state(active_bound_list_itr& bnd, active_bound_list& active_bounds, ring_manager& rings) { auto bnd2 = active_bound_list_rev_itr(bnd); bound_ptr bndTmp = nullptr; // Find first non line ring to the left of current bound. while (bnd2 != active_bounds.rend()) { if ((*bnd2)->ring && (*bnd2)->winding_delta != 0) { if (!bndTmp) { bndTmp = (*bnd2); } else if (bndTmp->ring == (*bnd2)->ring) { bndTmp = nullptr; } } ++bnd2; } if (!bndTmp) { (*bnd)->ring->parent = nullptr; rings.children.push_back((*bnd)->ring); } else { (*bnd)->ring->parent = bndTmp->ring; bndTmp->ring->children.push_back((*bnd)->ring); } } template void set_hole_state(active_bound_list_rev_itr& bnd, active_bound_list& active_bounds, ring_manager& rings) { auto bnd2 = std::next(bnd); bound_ptr bndTmp = nullptr; // Find first non line ring to the left of current bound. while (bnd2 != active_bounds.rend()) { if ((*bnd2)->ring && (*bnd2)->winding_delta != 0) { if (!bndTmp) { bndTmp = (*bnd2); } else if (bndTmp->ring == (*bnd2)->ring) { bndTmp = nullptr; } } ++bnd2; } if (!bndTmp) { (*bnd)->ring->parent = nullptr; rings.children.push_back((*bnd)->ring); } else { (*bnd)->ring->parent = bndTmp->ring; bndTmp->ring->children.push_back((*bnd)->ring); } } template void update_current_hp_itr(T scanline_y, ring_manager& rings) { while (rings.current_hp_itr->y > scanline_y) { ++rings.current_hp_itr; } } template struct hot_pixel_sorter { inline bool operator()(mapbox::geometry::point const& pt1, mapbox::geometry::point const& pt2) { if (pt1.y == pt2.y) { return pt1.x < pt2.x; } else { return pt1.y > pt2.y; } } }; // Due to the nature of floating point calculations // and the high likely hood of values around X.5, we // need to fudge what is X.5 some for our rounding. const double rounding_offset = 1e-12; const double rounding_offset_y = 5e-13; template T round_towards_min(double val) { // 0.5 rounds to 0 // 0.0 rounds to 0 // -0.5 rounds to -1 return static_cast(std::ceil(val - 0.5 + rounding_offset)); } template T round_towards_max(double val) { // 0.5 rounds to 1 // 0.0 rounds to 0 // -0.5 rounds to 0 return static_cast(std::floor(val + 0.5 + rounding_offset)); } template inline T get_edge_min_x(edge const& edge, const T current_y) { if (is_horizontal(edge)) { if (edge.bot.x < edge.top.x) { return edge.bot.x; } else { return edge.top.x; } } else if (edge.dx > 0.0) { if (current_y == edge.top.y) { return edge.top.x; } else { double lower_range_y = static_cast(current_y - edge.bot.y) - 0.5; double return_val = static_cast(edge.bot.x) + edge.dx * lower_range_y; T value = round_towards_min(return_val); return value; } } else { if (current_y == edge.bot.y) { return edge.bot.x; } else { double return_val = static_cast(edge.bot.x) + edge.dx * (static_cast(current_y - edge.bot.y) + 0.5 - rounding_offset_y); T value = round_towards_min(return_val); return value; } } } template inline T get_edge_max_x(edge const& edge, const T current_y) { if (is_horizontal(edge)) { if (edge.bot.x > edge.top.x) { return edge.bot.x; } else { return edge.top.x; } } else if (edge.dx < 0.0) { if (current_y == edge.top.y) { return edge.top.x; } else { double lower_range_y = static_cast(current_y - edge.bot.y) - 0.5; double return_val = static_cast(edge.bot.x) + edge.dx * lower_range_y; T value = round_towards_max(return_val); return value; } } else { if (current_y == edge.bot.y) { return edge.bot.x; } else { double return_val = static_cast(edge.bot.x) + edge.dx * (static_cast(current_y - edge.bot.y) + 0.5 - rounding_offset_y); T value = round_towards_max(return_val); return value; } } } template void hot_pixel_set_left_to_right(T y, T start_x, T end_x, bound& bnd, ring_manager& rings, hot_pixel_itr& itr, hot_pixel_itr& end, bool add_end_point) { T x_min = get_edge_min_x(*(bnd.current_edge), y); x_min = std::max(x_min, start_x); T x_max = get_edge_max_x(*(bnd.current_edge), y); x_max = std::min(x_max, end_x); for (; itr != end; ++itr) { if (itr->x < x_min) { continue; } if (itr->x > x_max) { break; } if (!add_end_point && itr->x == end_x) { continue; } point_ptr op = bnd.ring->points; bool to_front = (bnd.side == edge_left); if (to_front && (*itr == *op)) { continue; } else if (!to_front && (*itr == *op->prev)) { continue; } point_ptr new_point = create_new_point(bnd.ring, *itr, op, rings); if (to_front) { bnd.ring->points = new_point; } } } template void hot_pixel_set_right_to_left(T y, T start_x, T end_x, bound& bnd, ring_manager& rings, hot_pixel_rev_itr& itr, hot_pixel_rev_itr& end, bool add_end_point) { T x_min = get_edge_min_x(*(bnd.current_edge), y); x_min = std::max(x_min, end_x); T x_max = get_edge_max_x(*(bnd.current_edge), y); x_max = std::min(x_max, start_x); for (; itr != end; ++itr) { if (itr->x > x_max) { continue; } if (itr->x < x_min) { break; } if (!add_end_point && itr->x == end_x) { continue; } point_ptr op = bnd.ring->points; bool to_front = (bnd.side == edge_left); if (to_front && (*itr == *op)) { continue; } else if (!to_front && (*itr == *op->prev)) { continue; } point_ptr new_point = create_new_point(bnd.ring, *itr, op, rings); if (to_front) { bnd.ring->points = new_point; } } } template void sort_hot_pixels(ring_manager& rings) { std::sort(rings.hot_pixels.begin(), rings.hot_pixels.end(), hot_pixel_sorter()); auto last = std::unique(rings.hot_pixels.begin(), rings.hot_pixels.end()); rings.hot_pixels.erase(last, rings.hot_pixels.end()); } template void insert_hot_pixels_in_path(bound& bnd, mapbox::geometry::point const& end_pt, ring_manager& rings, bool add_end_point) { if (end_pt == bnd.last_point) { return; } T start_y = bnd.last_point.y; T start_x = bnd.last_point.x; T end_y = end_pt.y; T end_x = end_pt.x; auto itr = rings.current_hp_itr; while (itr->y <= start_y && itr != rings.hot_pixels.begin()) { --itr; } if (start_x > end_x) { for (; itr != rings.hot_pixels.end();) { if (itr->y > start_y) { ++itr; continue; } if (itr->y < end_y) { break; } T y = itr->y; auto last_itr = hot_pixel_rev_itr(itr); while (itr != rings.hot_pixels.end() && itr->y == y) { ++itr; } auto first_itr = hot_pixel_rev_itr(itr); bool add_end_point_itr = (y != end_pt.y || add_end_point); hot_pixel_set_right_to_left(y, start_x, end_x, bnd, rings, first_itr, last_itr, add_end_point_itr); } } else { for (; itr != rings.hot_pixels.end();) { if (itr->y > start_y) { ++itr; continue; } if (itr->y < end_y) { break; } T y = itr->y; auto first_itr = itr; while (itr != rings.hot_pixels.end() && itr->y == y) { ++itr; } auto last_itr = itr; bool add_end_point_itr = (y != end_pt.y || add_end_point); hot_pixel_set_left_to_right(y, start_x, end_x, bnd, rings, first_itr, last_itr, add_end_point_itr); } } bnd.last_point = end_pt; } template void add_to_hot_pixels(mapbox::geometry::point const& pt, ring_manager& rings) { rings.hot_pixels.push_back(pt); } template void add_first_point(active_bound_list_itr& bnd, active_bound_list& active_bounds, mapbox::geometry::point const& pt, ring_manager& rings) { ring_ptr r = create_new_ring(rings); (*bnd)->ring = r; r->points = create_new_point(r, pt, rings); set_hole_state(bnd, active_bounds, rings); (*bnd)->last_point = pt; } template void add_first_point(active_bound_list_rev_itr& bnd, active_bound_list& active_bounds, mapbox::geometry::point const& pt, ring_manager& rings) { ring_ptr r = create_new_ring(rings); // no ring currently set! (*bnd)->ring = r; r->points = create_new_point(r, pt, rings); set_hole_state(bnd, active_bounds, rings); (*bnd)->last_point = pt; } template void add_point_to_ring(bound& bnd, mapbox::geometry::point const& pt, ring_manager& rings) { assert(bnd.ring); // Handle hot pixels insert_hot_pixels_in_path(bnd, pt, rings, false); // bnd.ring->points is the 'Left-most' point & bnd.ring->points->prev is the // 'Right-most' point_ptr op = bnd.ring->points; bool to_front = (bnd.side == edge_left); if (to_front && (pt == *op)) { return; } else if (!to_front && (pt == *op->prev)) { return; } point_ptr new_point = create_new_point(bnd.ring, pt, bnd.ring->points, rings); if (to_front) { bnd.ring->points = new_point; } } template void add_point(active_bound_list_itr& bnd, active_bound_list& active_bounds, mapbox::geometry::point const& pt, ring_manager& rings) { if (!(*bnd)->ring) { add_first_point(bnd, active_bounds, pt, rings); } else { add_point_to_ring(*(*bnd), pt, rings); } } template void add_point(active_bound_list_rev_itr& bnd, active_bound_list& active_bounds, mapbox::geometry::point const& pt, ring_manager& rings) { if (!(*bnd)->ring) { add_first_point(bnd, active_bounds, pt, rings); } else { add_point_to_ring(*(*bnd), pt, rings); } } template void add_local_minimum_point(active_bound_list_itr b1, active_bound_list_itr b2, active_bound_list& active_bounds, mapbox::geometry::point const& pt, ring_manager& rings) { active_bound_list_itr b; active_bound_list_rev_itr prev_bound; active_bound_list_rev_itr prev_b1(b1); active_bound_list_rev_itr prev_b2(b2); if (is_horizontal(*((*b2)->current_edge)) || ((*b1)->current_edge->dx > (*b2)->current_edge->dx)) { add_point(b1, active_bounds, pt, rings); (*b2)->last_point = pt; (*b2)->ring = (*b1)->ring; (*b1)->side = edge_left; (*b2)->side = edge_right; b = b1; if (prev_b1 != active_bounds.rend() && std::prev(b) == b2) { prev_bound = prev_b2; } else { prev_bound = prev_b1; } } else { add_point(b2, active_bounds, pt, rings); (*b1)->last_point = pt; (*b1)->ring = (*b2)->ring; (*b1)->side = edge_right; (*b2)->side = edge_left; b = b2; if (prev_b2 != active_bounds.rend() && std::prev(b) == b1) { prev_bound = prev_b1; } else { prev_bound = prev_b2; } } } template inline double get_dx(point const& pt1, point const& pt2) { if (pt1.y == pt2.y) { return std::numeric_limits::infinity(); } else { return static_cast(pt2.x - pt2.x) / static_cast(pt2.y - pt1.y); } } template bool first_is_bottom_point(const_point_ptr btmPt1, const_point_ptr btmPt2) { point_ptr p = btmPt1->prev; while ((*p == *btmPt1) && (p != btmPt1)) { p = p->prev; } double dx1p = std::fabs(get_dx(*btmPt1, *p)); p = btmPt1->next; while ((*p == *btmPt1) && (p != btmPt1)) { p = p->next; } double dx1n = std::fabs(get_dx(*btmPt1, *p)); p = btmPt2->prev; while ((*p == *btmPt2) && (p != btmPt2)) { p = p->prev; } double dx2p = std::fabs(get_dx(*btmPt2, *p)); p = btmPt2->next; while ((*p == *btmPt2) && (p != btmPt2)) { p = p->next; } double dx2n = std::fabs(get_dx(*btmPt2, *p)); if (values_are_equal(std::max(dx1p, dx1n), std::max(dx2p, dx2n)) && values_are_equal(std::min(dx1p, dx1n), std::min(dx2p, dx2n))) { std::size_t s = 0; return area_from_point(btmPt1, s) > 0.0; // if otherwise identical use orientation } else { return (greater_than_or_equal(dx1p, dx2p) && greater_than_or_equal(dx1p, dx2n)) || (greater_than_or_equal(dx1n, dx2p) && greater_than_or_equal(dx1n, dx2n)); } } template point_ptr get_bottom_point(point_ptr pp) { point_ptr dups = nullptr; point_ptr p = pp->next; while (p != pp) { if (p->y > pp->y) { pp = p; dups = nullptr; } else if (p->y == pp->y && p->x <= pp->x) { if (p->x < pp->x) { dups = nullptr; pp = p; } else { if (p->next != pp && p->prev != pp) { dups = p; } } } p = p->next; } if (dups) { // there appears to be at least 2 vertices at bottom_point so ... while (dups != p) { if (!first_is_bottom_point(p, dups)) { pp = dups; } dups = dups->next; while (*dups != *pp) { dups = dups->next; } } } return pp; } template ring_ptr get_lower_most_ring(ring_ptr outRec1, ring_ptr outRec2) { // work out which polygon fragment has the correct hole state ... if (!outRec1->bottom_point) { outRec1->bottom_point = get_bottom_point(outRec1->points); } if (!outRec2->bottom_point) { outRec2->bottom_point = get_bottom_point(outRec2->points); } point_ptr OutPt1 = outRec1->bottom_point; point_ptr OutPt2 = outRec2->bottom_point; if (OutPt1->y > OutPt2->y) { return outRec1; } else if (OutPt1->y < OutPt2->y) { return outRec2; } else if (OutPt1->x < OutPt2->x) { return outRec1; } else if (OutPt1->x > OutPt2->x) { return outRec2; } else if (OutPt1->next == OutPt1) { return outRec2; } else if (OutPt2->next == OutPt2) { return outRec1; } else if (first_is_bottom_point(OutPt1, OutPt2)) { return outRec1; } else { return outRec2; } } template bool ring1_child_below_ring2(ring_ptr ring1, ring_ptr ring2) { do { ring1 = ring1->parent; if (ring1 == ring2) { return true; } } while (ring1); return false; } template void update_points_ring(ring_ptr ring) { point_ptr op = ring->points; do { op->ring = ring; op = op->prev; } while (op != ring->points); } template void append_ring(active_bound_list_itr& b1, active_bound_list_itr& b2, active_bound_list& active_bounds, ring_manager& manager) { // get the start and ends of both output polygons ... ring_ptr outRec1 = (*b1)->ring; ring_ptr outRec2 = (*b2)->ring; ring_ptr keep_ring; bound_ptr keep_bound; ring_ptr remove_ring; bound_ptr remove_bound; if (ring1_child_below_ring2(outRec1, outRec2)) { keep_ring = outRec2; keep_bound = *b2; remove_ring = outRec1; remove_bound = *b1; } else if (ring1_child_below_ring2(outRec2, outRec1)) { keep_ring = outRec1; keep_bound = *b1; remove_ring = outRec2; remove_bound = *b2; } else if (outRec1 == get_lower_most_ring(outRec1, outRec2)) { keep_ring = outRec1; keep_bound = *b1; remove_ring = outRec2; remove_bound = *b2; } else { keep_ring = outRec2; keep_bound = *b2; remove_ring = outRec1; remove_bound = *b1; } // get the start and ends of both output polygons and // join b2 poly onto b1 poly and delete pointers to b2 ... point_ptr p1_lft = keep_ring->points; point_ptr p1_rt = p1_lft->prev; point_ptr p2_lft = remove_ring->points; point_ptr p2_rt = p2_lft->prev; // join b2 poly onto b1 poly and delete pointers to b2 ... if (keep_bound->side == edge_left) { if (remove_bound->side == edge_left) { // z y x a b c reverse_ring(p2_lft); p2_lft->next = p1_lft; p1_lft->prev = p2_lft; p1_rt->next = p2_rt; p2_rt->prev = p1_rt; keep_ring->points = p2_rt; } else { // x y z a b c p2_rt->next = p1_lft; p1_lft->prev = p2_rt; p2_lft->prev = p1_rt; p1_rt->next = p2_lft; keep_ring->points = p2_lft; } } else { if (remove_bound->side == edge_right) { // a b c z y x reverse_ring(p2_lft); p1_rt->next = p2_rt; p2_rt->prev = p1_rt; p2_lft->next = p1_lft; p1_lft->prev = p2_lft; } else { // a b c x y z p1_rt->next = p2_lft; p2_lft->prev = p1_rt; p1_lft->prev = p2_rt; p2_rt->next = p1_lft; } } keep_ring->bottom_point = nullptr; bool keep_is_hole = ring_is_hole(keep_ring); bool remove_is_hole = ring_is_hole(remove_ring); remove_ring->points = nullptr; remove_ring->bottom_point = nullptr; if (keep_is_hole != remove_is_hole) { ring1_replaces_ring2(keep_ring->parent, remove_ring, manager); } else { ring1_replaces_ring2(keep_ring, remove_ring, manager); } update_points_ring(keep_ring); // nb: safe because we only get here via AddLocalMaxPoly keep_bound->ring = nullptr; remove_bound->ring = nullptr; for (auto& b : active_bounds) { if (b->ring == remove_ring) { b->ring = keep_ring; b->side = keep_bound->side; break; // Not sure why there is a break here but was transfered logic from angus } } } template void add_local_maximum_point(active_bound_list_itr& b1, active_bound_list_itr& b2, mapbox::geometry::point const& pt, ring_manager& rings, active_bound_list& active_bounds) { insert_hot_pixels_in_path(*(*b2), pt, rings, false); add_point(b1, active_bounds, pt, rings); if ((*b1)->ring == (*b2)->ring) { (*b1)->ring = nullptr; (*b2)->ring = nullptr; // I am not certain that order is important here? } else if ((*b1)->ring->ring_index < (*b2)->ring->ring_index) { append_ring(b1, b2, active_bounds, rings); } else { append_ring(b2, b1, active_bounds, rings); } } enum point_in_polygon_result : std::int8_t { point_on_polygon = -1, point_inside_polygon = 0, point_outside_polygon = 1 }; template point_in_polygon_result point_in_polygon(point const& pt, point_ptr op) { // returns 0 if false, +1 if true, -1 if pt ON polygon boundary point_in_polygon_result result = point_outside_polygon; point_ptr startOp = op; do { if (op->next->y == pt.y) { if ((op->next->x == pt.x) || (op->y == pt.y && ((op->next->x > pt.x) == (op->x < pt.x)))) { return point_on_polygon; } } if ((op->y < pt.y) != (op->next->y < pt.y)) { if (op->x >= pt.x) { if (op->next->x > pt.x) { // Switch between point outside polygon and point inside // polygon if (result == point_outside_polygon) { result = point_inside_polygon; } else { result = point_outside_polygon; } } else { double d = static_cast(op->x - pt.x) * static_cast(op->next->y - pt.y) - static_cast(op->next->x - pt.x) * static_cast(op->y - pt.y); if (value_is_zero(d)) { return point_on_polygon; } if ((d > 0) == (op->next->y > op->y)) { // Switch between point outside polygon and point inside // polygon if (result == point_outside_polygon) { result = point_inside_polygon; } else { result = point_outside_polygon; } } } } else { if (op->next->x > pt.x) { double d = static_cast(op->x - pt.x) * static_cast(op->next->y - pt.y) - static_cast(op->next->x - pt.x) * static_cast(op->y - pt.y); if (value_is_zero(d)) { return point_on_polygon; } if ((d > 0) == (op->next->y > op->y)) { // Switch between point outside polygon and point inside // polygon if (result == point_outside_polygon) { result = point_inside_polygon; } else { result = point_outside_polygon; } } } } } op = op->next; } while (startOp != op); return result; } template point_in_polygon_result point_in_polygon(mapbox::geometry::point const& pt, point_ptr op) { // returns 0 if false, +1 if true, -1 if pt ON polygon boundary point_in_polygon_result result = point_outside_polygon; point_ptr startOp = op; do { double op_x = static_cast(op->x); double op_y = static_cast(op->y); double op_next_x = static_cast(op->next->x); double op_next_y = static_cast(op->next->y); if (values_are_equal(op_next_y, pt.y)) { if (values_are_equal(op_next_x, pt.x) || (values_are_equal(op_y, pt.y) && ((op_next_x > pt.x) == (op_x < pt.x)))) { return point_on_polygon; } } if ((op_y < pt.y) != (op_next_y < pt.y)) { if (greater_than_or_equal(op_x, pt.x)) { if (op_next_x > pt.x) { // Switch between point outside polygon and point inside // polygon if (result == point_outside_polygon) { result = point_inside_polygon; } else { result = point_outside_polygon; } } else { double d = (op_x - pt.x) * (op_next_y - pt.y) - (op_next_x - pt.x) * (op_y - pt.y); if (value_is_zero(d)) { return point_on_polygon; } if ((d > 0.0) == (op_next_y > op->y)) { // Switch between point outside polygon and point inside // polygon if (result == point_outside_polygon) { result = point_inside_polygon; } else { result = point_outside_polygon; } } } } else { if (op_next_x > pt.x) { double d = (op_x - pt.x) * (op_next_y - pt.y) - (op_next_x - pt.x) * (op_y - pt.y); if (value_is_zero(d)) { return point_on_polygon; } if ((d > 0.0) == (op_next_y > op->y)) { // Switch between point outside polygon and point inside // polygon if (result == point_outside_polygon) { result = point_inside_polygon; } else { result = point_outside_polygon; } } } } } op = op->next; } while (startOp != op); return result; } template point_in_polygon_result inside_or_outside_special(point_ptr first_pt, point_ptr other_poly) { if (value_is_zero(area(first_pt->ring))) { return point_inside_polygon; } if (value_is_zero(area(other_poly->ring))) { return point_outside_polygon; } point_ptr pt = first_pt; do { if (*pt == *(pt->prev) || *pt == *(pt->next) || *(pt->next) == *(pt->prev) || slopes_equal(*(pt->prev), *pt, *(pt->next))) { pt = pt->next; continue; } double dx = ((pt->prev->x - pt->x) / 3.0) + ((pt->next->x - pt->x) / 3.0); double dy = ((pt->prev->y - pt->y) / 3.0) + ((pt->next->y - pt->y) / 3.0); mapbox::geometry::point offset_pt(pt->x + dx, pt->y + dy); point_in_polygon_result res = point_in_polygon(offset_pt, pt); if (res != point_inside_polygon) { offset_pt.x = pt->x - dx; offset_pt.y = pt->y - dy; res = point_in_polygon(offset_pt, pt); if (res != point_inside_polygon) { pt = pt->next; continue; } } res = point_in_polygon(offset_pt, other_poly); if (res == point_on_polygon) { pt = pt->next; continue; } return res; } while (pt != first_pt); return point_inside_polygon; } template bool poly2_contains_poly1(ring_ptr ring1, ring_ptr ring2) { point_ptr outpt1 = ring1->points->next; point_ptr outpt2 = ring2->points->next; point_ptr op = outpt1; do { // nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon point_in_polygon_result res = point_in_polygon(*op, outpt2); if (res != point_on_polygon) { return res == point_inside_polygon; } op = op->next; } while (op != outpt1); point_in_polygon_result res = inside_or_outside_special(outpt1, outpt2); return res == point_inside_polygon; } template void dispose_out_points(point_ptr& pp) { if (pp == nullptr) { return; } pp->prev->next = nullptr; while (pp) { point_ptr tmpPp = pp; pp = pp->next; tmpPp->next = tmpPp; tmpPp->prev = tmpPp; tmpPp->ring = nullptr; } } } } }