Fixed conic verb bug in eraser
This commit is contained in:
parent
c3fa1deee9
commit
54fb48cec7
7 changed files with 133 additions and 63 deletions
|
|
@ -19,6 +19,8 @@
|
|||
#include "BrushComponentCode.hpp"
|
||||
#include <Helpers/ConvertVec.hpp>
|
||||
#include <Helpers/MathExtras.hpp>
|
||||
#include <clipper2/clipper.h>
|
||||
#include <include/core/SkPathTypes.h>
|
||||
#include "../TimePoint.hpp"
|
||||
#include "../DrawingProgram/DrawingProgram.hpp"
|
||||
#include "../World.hpp"
|
||||
|
|
@ -31,6 +33,110 @@
|
|||
|
||||
namespace BrushComponentCode {
|
||||
|
||||
void skpath_to_clipper2_pathsd(Clipper2Lib::PathsD& clipperPath, const SkPath& skPath) {
|
||||
SkPath::Iter iter(skPath, false);
|
||||
|
||||
for(;;) {
|
||||
std::optional<SkPath::IterRec> rec = iter.next();
|
||||
if(!rec.has_value())
|
||||
break;
|
||||
|
||||
switch(rec->fVerb) {
|
||||
case SkPathVerb::kClose:
|
||||
break;
|
||||
case SkPathVerb::kLine:
|
||||
clipperPath.back().emplace_back(rec->fPoints[1].x(), rec->fPoints[1].y());
|
||||
break;
|
||||
case SkPathVerb::kMove:
|
||||
clipperPath.emplace_back();
|
||||
clipperPath.back().emplace_back(rec->fPoints[0].x(), rec->fPoints[0].y());
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("[skpath_to_clipper2_pathsd] Illegal verb " + std::to_string(static_cast<unsigned>(rec->fVerb)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool clipper2_polygon_to_skpath_builder(SkPathBuilder& skPathBuilder, const Clipper2Lib::PathD& clipperPath) {
|
||||
if(clipperPath.empty())
|
||||
return false;
|
||||
skPathBuilder.moveTo(clipperPath.front().x, clipperPath.front().y);
|
||||
for(size_t i = 1; i < clipperPath.size(); i++)
|
||||
skPathBuilder.lineTo(clipperPath[i].x, clipperPath[i].y);
|
||||
skPathBuilder.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
void sort_clipper_polytreed_into_skpaths(std::vector<SkPath>& paths, const Clipper2Lib::PolyTreeD& tree) {
|
||||
using namespace Clipper2Lib;
|
||||
for(auto& treeChild : tree) {
|
||||
SkPathBuilder newPath;
|
||||
if(!clipper2_polygon_to_skpath_builder(newPath, treeChild->Polygon()))
|
||||
continue;
|
||||
for(auto& child : *treeChild) {
|
||||
if(child->IsHole()) {
|
||||
clipper2_polygon_to_skpath_builder(newPath, child->Polygon());
|
||||
for(auto& childChild : *child) {
|
||||
if(childChild->IsHole())
|
||||
continue;
|
||||
else
|
||||
sort_clipper_polytreed_into_skpaths(paths, *child);
|
||||
}
|
||||
}
|
||||
else
|
||||
sort_clipper_polytreed_into_skpaths(paths, *child);
|
||||
}
|
||||
paths.emplace_back(newPath.detach());
|
||||
}
|
||||
}
|
||||
|
||||
void sort_clipper_polytreed_into_skpath_builder(SkPathBuilder& skPathBuilder, const Clipper2Lib::PolyTreeD& tree) {
|
||||
using namespace Clipper2Lib;
|
||||
for(auto& treeChild : tree) {
|
||||
if(!clipper2_polygon_to_skpath_builder(skPathBuilder, treeChild->Polygon()))
|
||||
continue;
|
||||
for(auto& child : *treeChild) {
|
||||
if(child->IsHole()) {
|
||||
clipper2_polygon_to_skpath_builder(skPathBuilder, child->Polygon());
|
||||
for(auto& childChild : *child) {
|
||||
if(childChild->IsHole())
|
||||
continue;
|
||||
else
|
||||
sort_clipper_polytreed_into_skpath_builder(skPathBuilder, *child);
|
||||
}
|
||||
}
|
||||
else
|
||||
sort_clipper_polytreed_into_skpath_builder(skPathBuilder, *child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void clipper2_polygons_to_skpath_builder(SkPathBuilder& skPathBuilder, const Clipper2Lib::PathsD& clipperPath) {
|
||||
for(auto& p : clipperPath)
|
||||
clipper2_polygon_to_skpath_builder(skPathBuilder, p);
|
||||
}
|
||||
|
||||
std::optional<SkPath> skpath_simplify_only_lines(const SkPath& skPath) {
|
||||
using namespace Clipper2Lib;
|
||||
PathsD clippingSubjects;
|
||||
skpath_to_clipper2_pathsd(clippingSubjects, skPath);
|
||||
ClipperD clipper;
|
||||
PathsD solutionPaths;
|
||||
|
||||
clipper.AddSubject(clippingSubjects);
|
||||
clipper.AddClip(clippingSubjects);
|
||||
if(!clipper.Execute(ClipType::Union, skPath.getFillType() == SkPathFillType::kEvenOdd ? FillRule::EvenOdd : FillRule::NonZero, solutionPaths))
|
||||
return std::nullopt;
|
||||
|
||||
SimplifyPaths(solutionPaths, 0.01);
|
||||
|
||||
SkPathBuilder newPath;
|
||||
newPath.setFillType(skPath.getFillType());
|
||||
clipper2_polygons_to_skpath_builder(newPath, solutionPaths);
|
||||
return newPath.detach();
|
||||
}
|
||||
|
||||
SkPath brush_stroke_to_skpath(const std::vector<BrushPoint>& brushPoints, bool hasRoundCaps) {
|
||||
std::vector<BrushPoint> points = smooth_points(brushPoints, 0, brushPoints.size() - 1, DEFAULT_SMOOTHNESS);
|
||||
return create_triangles(brushPoints, points, hasRoundCaps);
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
#include "../CoordSpaceHelper.hpp"
|
||||
#include <include/core/SkPathBuilder.h>
|
||||
#include "../InputManager.hpp"
|
||||
#include <clipper2/clipper.h>
|
||||
|
||||
class DrawingProgram;
|
||||
|
||||
|
|
@ -31,6 +32,7 @@ struct MeshShapeData {
|
|||
};
|
||||
|
||||
namespace BrushComponentCode {
|
||||
constexpr float CLIPPER_SIMPLIFY_EPSILON = 0.1;
|
||||
struct BrushPoint {
|
||||
Vector2f pos;
|
||||
float width;
|
||||
|
|
@ -47,6 +49,12 @@ namespace BrushComponentCode {
|
|||
CoordSpaceHelper coords;
|
||||
};
|
||||
|
||||
void skpath_to_clipper2_pathsd(Clipper2Lib::PathsD& clipperPath, const SkPath& skPath);
|
||||
bool clipper2_polygon_to_skpath_builder(SkPathBuilder& skPathBuilder, const Clipper2Lib::PathD& clipperPath);
|
||||
void sort_clipper_polytreed_into_skpaths(std::vector<SkPath>& paths, const Clipper2Lib::PolyTreeD& tree);
|
||||
void clipper2_polygons_to_skpath_builder(SkPathBuilder& skPathBuilder, const Clipper2Lib::PathsD& clipperPath);
|
||||
std::optional<SkPath> skpath_simplify_only_lines(const SkPath& skPath);
|
||||
|
||||
SkPath brush_stroke_to_skpath(const std::vector<BrushPoint>& brushPoints, bool hasRoundCaps);
|
||||
SkPath create_triangles(const std::vector<BrushPoint>& regularPoints, const std::vector<BrushPoint>& smoothedPoints, bool hasRoundCaps);
|
||||
std::vector<size_t> get_wedge_indices(const std::vector<BrushPoint>& points);
|
||||
|
|
|
|||
|
|
@ -75,6 +75,9 @@ bool CanvasComponent::can_erase_detail() const {
|
|||
return false;
|
||||
}
|
||||
|
||||
void CanvasComponent::simplify_paths() {
|
||||
}
|
||||
|
||||
std::vector<CanvasComponentContainer*> CanvasComponent::attempt_split(DrawingProgram& drawP) const { return {}; }
|
||||
|
||||
CanvasComponentEraseDetailResult CanvasComponent::erase_detail(const SkPath& eraseAgainst) {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ class CanvasComponent {
|
|||
virtual void remap_resource_ids(const std::unordered_map<NetworkingObjects::NetObjID, NetworkingObjects::NetObjID>& resourceOldToNewMap);
|
||||
virtual void change_stroke_color(const Vector4f& newStrokeColor);
|
||||
virtual std::vector<CanvasComponentContainer*> attempt_split(DrawingProgram& drawP) const;
|
||||
virtual void simplify_paths();
|
||||
virtual std::optional<Vector4f> get_stroke_color() const;
|
||||
|
||||
virtual void set_data_from(const CanvasComponent& other) = 0;
|
||||
|
|
|
|||
|
|
@ -272,43 +272,6 @@ bool MeshCanvasComponent::can_erase_detail() const {
|
|||
return true;
|
||||
}
|
||||
|
||||
void sort_tree_into_sk_paths(std::vector<SkPath>& paths, const Clipper2Lib::PolyTreeD& tree) {
|
||||
using namespace Clipper2Lib;
|
||||
for(auto& treeChild : tree) {
|
||||
SkPathBuilder newPath;
|
||||
{
|
||||
const PathD& poly = treeChild->Polygon();
|
||||
if(poly.empty()) {
|
||||
continue;
|
||||
}
|
||||
newPath.moveTo(poly.front().x, poly.front().y);
|
||||
for(size_t i = 1; i < poly.size(); i++)
|
||||
newPath.lineTo(poly[i].x, poly[i].y);
|
||||
newPath.close();
|
||||
}
|
||||
for(auto& child : *treeChild) {
|
||||
if(child->IsHole()) {
|
||||
const PathD& poly = child->Polygon();
|
||||
newPath.moveTo(poly.front().x, poly.front().y);
|
||||
for(size_t i = 1; i < poly.size(); i++)
|
||||
newPath.lineTo(poly[i].x, poly[i].y);
|
||||
newPath.close();
|
||||
for(auto& childChild : *child) {
|
||||
if(childChild->IsHole())
|
||||
continue;
|
||||
else {
|
||||
sort_tree_into_sk_paths(paths, *child);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
sort_tree_into_sk_paths(paths, *child);
|
||||
}
|
||||
}
|
||||
paths.emplace_back(newPath.detach());
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<CanvasComponentContainer*> MeshCanvasComponent::attempt_split(DrawingProgram& drawP) const {
|
||||
using namespace Clipper2Lib;
|
||||
|
||||
|
|
@ -316,32 +279,9 @@ std::vector<CanvasComponentContainer*> MeshCanvasComponent::attempt_split(Drawin
|
|||
|
||||
PathsD clippingSubjects;
|
||||
|
||||
SkPath::Iter iter(d.meshPath, false);
|
||||
BrushComponentCode::skpath_to_clipper2_pathsd(clippingSubjects, d.meshPath);
|
||||
|
||||
size_t moveCount = 0;
|
||||
for(;;) {
|
||||
std::optional<SkPath::IterRec> rec = iter.next();
|
||||
if(!rec.has_value())
|
||||
break;
|
||||
|
||||
switch(rec->fVerb) {
|
||||
case SkPathVerb::kClose:
|
||||
break;
|
||||
case SkPathVerb::kLine:
|
||||
clippingSubjects.back().emplace_back(rec->fPoints[1].x(), rec->fPoints[1].y());
|
||||
break;
|
||||
case SkPathVerb::kMove:
|
||||
clippingSubjects.emplace_back();
|
||||
clippingSubjects.back().emplace_back(rec->fPoints[0].x(), rec->fPoints[0].y());
|
||||
moveCount++;
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("[attempt_split] Illegal verb " + std::to_string(static_cast<unsigned>(rec->fVerb)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(moveCount <= 1)
|
||||
if(clippingSubjects.size() <= 1)
|
||||
return {};
|
||||
|
||||
ClipperD clipper;
|
||||
|
|
@ -353,7 +293,7 @@ std::vector<CanvasComponentContainer*> MeshCanvasComponent::attempt_split(Drawin
|
|||
|
||||
std::vector<SkPath> splitPaths;
|
||||
|
||||
sort_tree_into_sk_paths(splitPaths, solutionTree);
|
||||
BrushComponentCode::sort_clipper_polytreed_into_skpaths(splitPaths, solutionTree);
|
||||
|
||||
if(splitPaths.size() <= 1)
|
||||
return toRet;
|
||||
|
|
@ -364,6 +304,7 @@ std::vector<CanvasComponentContainer*> MeshCanvasComponent::attempt_split(Drawin
|
|||
mesh.d.color = d.color;
|
||||
mesh.d.meshPath = p;
|
||||
newComp->coords = compContainer->coords;
|
||||
mesh.simplify_paths();
|
||||
toRet.emplace_back(newComp);
|
||||
}
|
||||
|
||||
|
|
@ -376,6 +317,7 @@ CanvasComponentEraseDetailResult MeshCanvasComponent::erase_detail(const SkPath&
|
|||
return CanvasComponentEraseDetailResult::NO_CHANGE;
|
||||
std::optional<SkPath> newPath = Op(d.meshPath, eraseAgainst, SkPathOp::kDifference_SkPathOp);
|
||||
if(newPath.has_value()) {
|
||||
auto oldPath = d.meshPath;
|
||||
d.meshPath = newPath.value();
|
||||
if(d.meshPath.isEmpty())
|
||||
return CanvasComponentEraseDetailResult::REMOVED;
|
||||
|
|
@ -384,6 +326,12 @@ CanvasComponentEraseDetailResult MeshCanvasComponent::erase_detail(const SkPath&
|
|||
return CanvasComponentEraseDetailResult::NO_CHANGE;
|
||||
}
|
||||
|
||||
void MeshCanvasComponent::simplify_paths() {
|
||||
auto newPath = Simplify(d.meshPath);
|
||||
if(newPath.has_value())
|
||||
d.meshPath = newPath.value();
|
||||
}
|
||||
|
||||
SCollision::AABB<float> MeshCanvasComponent::get_obj_coord_bounds() const {
|
||||
return d.meshPath.getBounds();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ class MeshCanvasComponent : public CanvasComponent {
|
|||
virtual std::vector<CanvasComponentContainer*> attempt_split(DrawingProgram& drawP) const override;
|
||||
virtual std::optional<Vector4f> get_stroke_color() const override;
|
||||
virtual void set_data_from(const CanvasComponent& other) override;
|
||||
virtual void simplify_paths() override;
|
||||
|
||||
struct Data {
|
||||
SkPath meshPath;
|
||||
|
|
|
|||
|
|
@ -86,6 +86,8 @@ void EraserTool::input_mouse_button_on_canvas_callback(const InputManager::Mouse
|
|||
}
|
||||
|
||||
BrushComponentCode::mouse_button(drawP, genData, drawP.world.drawData.cam.c, button, relativeWidthResult.first.value());
|
||||
// NOTE: Must set erase path when isErasing is set to true to make sure that the Circle path from not erasing part doesn't reach the erase_on_path code
|
||||
erasePath = BrushComponentCode::brush_stroke_to_skpath(genData.brushPoints, drawP.world.main.toolConfig.brush.hasRoundCaps);
|
||||
isErasing = true;
|
||||
}
|
||||
else if(!button.down && isErasing) {
|
||||
|
|
@ -232,6 +234,7 @@ void EraserTool::commit_erase() {
|
|||
drawP.layerMan.erase_component_container(erasedComponents, !placedNewObject);
|
||||
bool first = !objectsToErase && !placedNewObject;
|
||||
for(auto& [comp, oldData] : updatedComponents) {
|
||||
comp->obj->get_comp().simplify_paths();
|
||||
comp->obj->commit_update_dont_invalidate_cache(drawP);
|
||||
comp->obj->send_comp_update(drawP, true);
|
||||
if(first) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue