bool BooleanTool::checkSegmentMatch( const PathObject* original, int coord_index, const ClipperLib::Path& polygon, ClipperLib::Path::size_type start_index, ClipperLib::Path::size_type end_index, bool& out_coords_increasing, bool& out_is_curve ) { const MapCoord& first = original->getCoordinate(coord_index); out_is_curve = first.isCurveStart(); auto other_index = (coord_index + (out_is_curve ? 3 : 1)) % original->getCoordinateCount(); const MapCoord& other = original->getCoordinate(other_index); bool found = true; if (first == polygon.at(start_index) && other == polygon.at(end_index)) { out_coords_increasing = true; } else if (first == polygon.at(end_index) && other == polygon.at(start_index)) { out_coords_increasing = false; } else { found = false; } return found; }
void BooleanTool::rebuildCoordinate( ClipperLib::Path::size_type index, const ClipperLib::Path& polygon, const PolyMap& polymap, PathObject* object, bool start_new_part) { MapCoord coord(0.001 * polygon.at(index).X, 0.001 * polygon.at(index).Y); if (polymap.contains(polygon.at(index))) { PathCoordInfo info = polymap.value(polygon.at(index)); MapCoord& original = info.first->path->getCoordinate(info.second->index); if (original.isDashPoint()) coord.setDashPoint(true); } object->addCoordinate(coord, start_new_part); }
void BooleanTool::rebuildTwoIndexSegment( ClipperLib::Path::size_type start_index, ClipperLib::Path::size_type end_index, bool sequence_increasing, const ClipperLib::Path& polygon, const PolyMap& polymap, PathObject* object) { Q_UNUSED(sequence_increasing); // only used in Q_ASSERT. PathCoordInfo start_info = polymap.value(polygon.at(start_index)); PathCoordInfo end_info = polymap.value(polygon.at(end_index)); PathObject* original = end_info.first->path; bool coords_increasing; bool is_curve; int coord_index; if (start_info.second->index == end_info.second->index) { coord_index = end_info.second->index; bool found = checkSegmentMatch(original, coord_index, polygon, start_index, end_index, coords_increasing, is_curve); if (!found) { object->getCoordinate(object->getCoordinateCount() - 1).setCurveStart(false); rebuildCoordinate(end_index, polygon, polymap, object); return; } Q_ASSERT(coords_increasing == sequence_increasing); } else { coord_index = end_info.second->index; bool found = checkSegmentMatch(original, coord_index, polygon, start_index, end_index, coords_increasing, is_curve); if (!found) { coord_index = start_info.second->index; found = checkSegmentMatch(original, coord_index, polygon, start_index, end_index, coords_increasing, is_curve); if (!found) { object->getCoordinate(object->getCoordinateCount() - 1).setCurveStart(false); rebuildCoordinate(end_index, polygon, polymap, object); return; } } } if (!is_curve) object->getCoordinate(object->getCoordinateCount() - 1).setCurveStart(false); if (coords_increasing) { object->addCoordinate(resetCoordinate(original->getCoordinate(coord_index + 1))); if (is_curve) { object->addCoordinate(original->getCoordinate(coord_index + 2)); object->addCoordinate(resetCoordinate(original->getCoordinate(coord_index + 3))); } } else { if (is_curve) { object->addCoordinate(resetCoordinate(original->getCoordinate(coord_index + 2))); object->addCoordinate(original->getCoordinate(coord_index + 1)); } object->addCoordinate(resetCoordinate(original->getCoordinate(coord_index + 0))); } }
void BooleanTool::rebuildSegment( ClipperLib::Path::size_type start_index, ClipperLib::Path::size_type end_index, bool sequence_increasing, const ClipperLib::Path& polygon, const PolyMap& polymap, PathObject* object) { auto num_points = polygon.size(); object->getCoordinate(object->getCoordinateCount() - 1).setCurveStart(true); if ((start_index + 1) % num_points == end_index) { // This could happen for a straight line or a very flat curve - take coords directly from original rebuildTwoIndexSegment(start_index, end_index, sequence_increasing, polygon, polymap, object); return; } // Get polygon point coordinates const auto& start_point = polygon.at(start_index); const auto& second_point = polygon.at((start_index + 1) % num_points); const auto& second_last_point = polygon.at((end_index - 1) % num_points); const auto& end_point = polygon.at(end_index); // Try to find the middle coordinates in the same part bool found = false; PathCoordInfo second_info{ nullptr, nullptr }; PathCoordInfo second_last_info{ nullptr, nullptr }; for (auto second_it = polymap.find(second_point); second_it != polymap.end(); ++second_it) { for (auto second_last_it = polymap.find(second_last_point); second_last_it != polymap.end() && second_last_it.key() == second_last_point; ++second_last_it) { if (second_it->first == second_last_it->first && second_it->second->index == second_last_it->second->index) { // Same part found = true; second_info = *second_it; second_last_info = *second_last_it; break; } } if (found) break; } if (!found) { // Need unambiguous path part information to find the original object with high probability qDebug() << "BooleanTool::rebuildSegment: cannot identify original object!"; rebuildSegmentFromPathOnly(start_point, second_point, second_last_point, end_point, object); return; } const PathPart* original_path = second_info.first; // Try to find the outer coordinates in the same part PathCoordInfo start_info{ nullptr, nullptr }; for (auto start_it = polymap.find(start_point); start_it != polymap.end() && start_it.key() == start_point; ++start_it) { if (start_it->first == original_path) { start_info = *start_it; break; } } Q_ASSERT(!start_info.first || start_info.first == second_info.first); PathCoordInfo end_info{ nullptr, nullptr }; for (auto end_it = polymap.find(end_point); end_it != polymap.end() && end_it.key() == end_point; ++end_it) { if (end_it->first == original_path) { end_info = *end_it; break; } } Q_ASSERT(!end_info.first || end_info.first == second_info.first); const PathObject* original = original_path->path; auto edge_start = second_info.second->index; if (edge_start == second_info.first->last_index) edge_start = second_info.first->first_index; // Find out start tangent auto start_param = 0.0; MapCoord start_coord = MapCoord(0.001 * start_point.X, 0.001 * start_point.Y); MapCoord start_tangent; MapCoord end_tangent; MapCoord end_coord; double start_error_sq, end_error_sq; // Maximum difference in mm from reconstructed start and end coords to the // intersection points returned by Clipper const double error_bound = 0.4; if (sequence_increasing) { if ( second_info.second->param == 0.0 || ( start_info.first && start_info.second->param == 0.0 && ( start_info.second->index == edge_start || (start_info.second->index == start_info.first->last_index && start_info.first->first_index == edge_start) ) ) ) { // Take coordinates directly start_tangent = original->getCoordinate(edge_start + 1); end_tangent = original->getCoordinate(edge_start + 2); start_error_sq = start_coord.distanceSquaredTo(original->getCoordinate(edge_start + 0)); if (start_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: start error too high in increasing direct case: " << sqrt(start_error_sq); } else { // Approximate coords const PathCoord* prev_coord = second_info.second - 1; auto dx = second_point.X - start_point.X; auto dy = second_point.Y - start_point.Y; auto point_dist = 0.001 * sqrt(dx*dx + dy*dy); auto delta_start_param = (second_info.second->param - prev_coord->param) * point_dist / qMax(1e-7f, (second_info.second->clen - prev_coord->clen)); start_param = qBound(0.0, second_info.second->param - delta_start_param, 1.0); MapCoordF unused, o2, o3, o4; PathCoord::splitBezierCurve(MapCoordF(original->getCoordinate(edge_start + 0)), MapCoordF(original->getCoordinate(edge_start + 1)), MapCoordF(original->getCoordinate(edge_start + 2)), MapCoordF(original->getCoordinate(edge_start + 3)), start_param, unused, unused, o2, o3, o4); start_tangent = MapCoord(o3); end_tangent = MapCoord(o4); start_error_sq = start_coord.distanceSquaredTo(MapCoord(o2)); if (start_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: start error too high in increasing general case: " << sqrt(start_error_sq); } // Find better end point approximation and its tangent if ( second_last_info.second->param == 0.0 || (end_info.first && end_info.second->param == 0.0 && ( end_info.second->index == edge_start+3 || (end_info.second->index == end_info.first->first_index && end_info.first->last_index == edge_start+3) ) ) ) { // Take coordinates directly end_coord = original->getCoordinate(edge_start + 3); auto test_x = end_point.X - end_coord.nativeX(); auto test_y = end_point.Y - end_coord.nativeY(); end_error_sq = 0.001 * sqrt(test_x*test_x + test_y*test_y); if (end_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: end error too high in increasing direct case: " << sqrt(end_error_sq); } else { // Approximate coords const PathCoord* next_coord = second_last_info.second + 1; auto next_coord_param = next_coord->param; if (next_coord_param == 0.0) next_coord_param = 1.0; auto dx = end_point.X - second_last_point.X; auto dy = end_point.Y - second_last_point.Y; auto point_dist = 0.001 * sqrt(dx*dx + dy*dy); auto delta_end_param = (next_coord_param - second_last_info.second->param) * point_dist / qMax(1e-7f, (next_coord->clen - second_last_info.second->clen)); auto end_param = (second_last_info.second->param + delta_end_param - start_param) / (1.0 - start_param); MapCoordF o0, o1, o2, unused; PathCoord::splitBezierCurve(MapCoordF(start_coord), MapCoordF(start_tangent), MapCoordF(end_tangent), MapCoordF(original->getCoordinate(edge_start + 3)), end_param, o0, o1, o2, unused, unused); start_tangent = MapCoord(o0); end_tangent = MapCoord(o1); end_coord = MapCoord(o2); auto test_x = end_point.X - end_coord.nativeX(); auto test_y = end_point.Y - end_coord.nativeY(); end_error_sq = 0.001 * sqrt(test_x*test_x + test_y*test_y); if (end_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: end error too high in increasing general case: " << sqrt(end_error_sq); } } else // if (!sequence_increasing) { if ( second_info.second->param == 0.0 || ( start_info.first && start_info.second->param == 0.0 && ( start_info.second->index == edge_start+3 || (start_info.second->index == start_info.first->first_index && start_info.first->last_index == edge_start+3) ) ) ) { // Take coordinates directly start_tangent = original->getCoordinate(edge_start + 2); end_tangent = original->getCoordinate(edge_start + 1); start_error_sq = start_coord.distanceSquaredTo(original->getCoordinate(edge_start + 3)); if (start_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: start error too high in decreasing direct case: " << sqrt(start_error_sq); } else { // Approximate coords const PathCoord* next_coord = second_info.second + 1; auto next_coord_param = next_coord->param; if (next_coord_param == 0.0) next_coord_param = 1.0; auto dx = second_point.X - start_point.X; auto dy = second_point.Y - start_point.Y; auto point_dist = 0.001 * sqrt(dx*dx + dy*dy); auto delta_start_param = (next_coord_param - second_info.second->param) * point_dist / qMax(1e-7f, (next_coord->clen - second_info.second->clen)); start_param = qBound(0.0, 1.0 - second_info.second->param + delta_start_param, 1.0); MapCoordF unused, o2, o3, o4; PathCoord::splitBezierCurve(MapCoordF(original->getCoordinate(edge_start + 3)), MapCoordF(original->getCoordinate(edge_start + 2)), MapCoordF(original->getCoordinate(edge_start + 1)), MapCoordF(original->getCoordinate(edge_start + 0)), start_param, unused, unused, o2, o3, o4); start_tangent = MapCoord(o3); end_tangent = MapCoord(o4); start_error_sq = start_coord.distanceSquaredTo(MapCoord(o2)); if (start_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: start error too high in decreasing general case: " << sqrt(start_error_sq); } // Find better end point approximation and its tangent if ( second_last_info.second->param == 0.0 || ( end_info.first && end_info.second->param == 0.0 && ( end_info.second->index == edge_start || (end_info.second->index == end_info.first->last_index && end_info.first->first_index == edge_start) ) ) ) { // Take coordinates directly end_coord = original->getCoordinate(edge_start + 0); auto test_x = end_point.X - end_coord.nativeX(); auto test_y = end_point.Y - end_coord.nativeY(); end_error_sq = 0.001 * sqrt(test_x*test_x + test_y*test_y); if (end_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: end error too high in decreasing direct case: " << sqrt(end_error_sq); } else { // Approximate coords const PathCoord* prev_coord = second_last_info.second - 1; auto dx = end_point.X - second_last_point.X; auto dy = end_point.Y - second_last_point.Y; auto point_dist = 0.001 * sqrt(dx*dx + dy*dy); auto delta_end_param = (second_last_info.second->param - prev_coord->param) * point_dist / qMax(1e-7f, (second_last_info.second->clen - prev_coord->clen)); auto end_param = (1.0 - second_last_info.second->param + delta_end_param) / (1 - start_param); MapCoordF o0, o1, o2, unused; PathCoord::splitBezierCurve(MapCoordF(start_coord), MapCoordF(start_tangent), MapCoordF(end_tangent), MapCoordF(original->getCoordinate(edge_start + 0)), end_param, o0, o1, o2, unused, unused); start_tangent = MapCoord(o0); end_tangent = MapCoord(o1); end_coord = MapCoord(o2); auto test_x = end_point.X - end_coord.nativeX(); auto test_y = end_point.Y - end_coord.nativeY(); end_error_sq = 0.001 * sqrt(test_x*test_x + test_y*test_y); if (end_error_sq > error_bound) qDebug() << "BooleanTool::rebuildSegment: end error too high in decreasing general case: " << sqrt(end_error_sq); } } if (start_error_sq <= error_bound && end_error_sq <= error_bound) { // Rebuild bezier curve using information from original curve object->addCoordinate(start_tangent); object->addCoordinate(end_tangent); object->addCoordinate(resetCoordinate(end_coord)); } else { // Rebuild bezier curve approximately using tangents derived from result polygon rebuildSegmentFromPathOnly(start_point, second_point, second_last_point, end_point, object); } }
void BooleanTool::polygonToPathPart(const ClipperLib::Path& polygon, const PolyMap& polymap, PathObject* object) { auto num_points = polygon.size(); if (num_points < 3) return; // Index of first used point in polygon auto part_start_index = 0u; auto cur_info = PathCoordInfo{ nullptr, nullptr }; // Check if we can find either an unknown intersection point // or a path coord with parameter 0. // This gives a starting point to search for curves to rebuild // (because we cannot start in the middle of a curve) for (; part_start_index < num_points; ++part_start_index) { auto current_point = polygon.at(part_start_index); if (!polymap.contains(current_point)) break; if (polymap.value(current_point).second->param == 0.0) { cur_info = polymap.value(current_point); break; } } if (part_start_index == num_points) { // Did not find a valid starting point. Return the part as a polygon. for (auto i = 0u; i < num_points; ++i) object->addCoordinate(MapCoord(0.001 * polygon.at(i).X, 0.001 * polygon.at(i).Y), (i == 0)); object->parts().back().setClosed(true, true); return; } // Add the first point to the object rebuildCoordinate(part_start_index, polygon, polymap, object, true); // Index of first segment point in polygon auto segment_start_index = part_start_index; bool have_sequence = false; bool sequence_increasing = false; bool stop_before = false; // Advance along the boundary and rebuild the curve for every sequence // of path coord pointers with the same path and index. auto i = part_start_index; do { ++i; if (i >= num_points) i = 0; PathCoordInfo new_info{ nullptr, nullptr }; auto new_point = polygon.at(i); if (polymap.contains(new_point)) new_info = polymap.value(new_point); if (cur_info.first && cur_info.first == new_info.first) { // Same original part auto cur_coord_index = cur_info.second->index; MapCoord& cur_coord = cur_info.first->path->getCoordinate(cur_coord_index); auto new_coord_index = new_info.second->index; MapCoord& new_coord = new_info.first->path->getCoordinate(new_coord_index); auto cur_coord_index_adjusted = cur_coord_index; if (cur_coord_index_adjusted == new_info.first->first_index) cur_coord_index_adjusted = new_info.first->last_index; auto new_coord_index_adjusted = new_coord_index; if (new_coord_index_adjusted == new_info.first->first_index) new_coord_index_adjusted = new_info.first->last_index; if (cur_coord_index == new_coord_index) { // Somewhere on a curve bool param_increasing = new_info.second->param > cur_info.second->param; if (!have_sequence) { have_sequence = true; sequence_increasing = param_increasing; } else if (have_sequence && sequence_increasing != param_increasing) { stop_before = true; } } else if (new_info.second->param == 0.0 && ( (cur_coord.isCurveStart() && new_coord_index_adjusted == cur_coord_index + 3) || (!cur_coord.isCurveStart() && new_coord_index_adjusted == cur_coord_index + 1) ) ) { // Original curve is from cur_coord_index to new_coord_index_adjusted. if (!have_sequence) { have_sequence = true; sequence_increasing = true; } else { stop_before = !sequence_increasing; } } else if (cur_info.second->param == 0.0 && ( (new_coord.isCurveStart() && new_coord_index + 3 == cur_coord_index_adjusted) || (!new_coord.isCurveStart() && new_coord_index + 1 == cur_coord_index_adjusted) ) ) { // Original curve is from new_coord_index to cur_coord_index_adjusted. if (!have_sequence) { have_sequence = true; sequence_increasing = false; } else { stop_before = sequence_increasing; } } else if ((segment_start_index + 1) % num_points != i) { // Not immediately after segment_start_index stop_before = true; } } if (i == part_start_index || stop_before || (new_info.second && new_info.second->param == 0.0) || (cur_info.first && (cur_info.first != new_info.first || cur_info.second->index != new_info.second->index) && i != (segment_start_index + 1) % num_points) || !new_info.first) { if (stop_before) { if (i == 0) i = num_points - 1; else --i; } if (have_sequence) // A sequence of at least two points belonging to the same curve rebuildSegment(segment_start_index, i, sequence_increasing, polygon, polymap, object); else // A single straight edge rebuildCoordinate(i, polygon, polymap, object); if (stop_before) { ++i; if (i >= num_points) i = 0; rebuildCoordinate(i, polygon, polymap, object); stop_before = false; } segment_start_index = i; have_sequence = false; } cur_info = new_info; } while (i != part_start_index); object->parts().back().connectEnds(); }