From fd1f76520b93116e8efc1154fb6b30c62aa09566 Mon Sep 17 00:00:00 2001 From: Yousef Khadadeh Date: Sat, 7 Feb 2026 16:03:02 +0300 Subject: [PATCH] Brush strokes now have accurate_draw --- include/Helpers/MathExtras.hpp | 74 ++++++++++++++++- include/Helpers/SCollision.hpp | 7 ++ .../BrushStrokeCanvasComponent.cpp | 80 ++++++++++++++++++- .../BrushStrokeCanvasComponent.hpp | 1 + src/CanvasComponents/CanvasComponent.cpp | 4 + src/CanvasComponents/CanvasComponent.hpp | 2 + .../CanvasComponentContainer.cpp | 12 +-- .../CanvasComponentContainer.hpp | 1 + 8 files changed, 172 insertions(+), 9 deletions(-) diff --git a/include/Helpers/MathExtras.hpp b/include/Helpers/MathExtras.hpp index 6981a17..946fc98 100644 --- a/include/Helpers/MathExtras.hpp +++ b/include/Helpers/MathExtras.hpp @@ -142,16 +142,24 @@ template bool is_collision_line_segment_line_segment(const Matrix std::optional collision_line_segment_line_segment_pos(const Matrix& a, const Matrix& b, const Matrix& c, const Matrix& d) { +template std::optional> collision_line_segment_line_segment_pos(const Matrix& a, const Matrix& b, const Matrix& c, const Matrix& d) { T oa = orient_vals_vec(c, d, a); T ob = orient_vals_vec(c, d, b); T oc = orient_vals_vec(a, b, c); T od = orient_vals_vec(a, b, d); - if(oa * ob < 0 && oc * od < 0) - return std::optional((a * ob - b * oa) / (ob - oa)); + if(oa * ob < T(0) && oc * od < T(0)) + return std::optional>((a * ob - b * oa) / (ob - oa)); return std::nullopt; } +template Matrix collision_line_segment_line_segment_guaranteed(const Matrix& a, const Matrix& b, const Matrix& c, const Matrix& d) { + T oa = orient_vals_vec(c, d, a); + T ob = orient_vals_vec(c, d, b); + T oc = orient_vals_vec(a, b, c); + T od = orient_vals_vec(a, b, d); + return Matrix((a * ob - b * oa) / (ob - oa)); +} + template bool is_collision_aabb_line_segment(const AlignedBox& aabb, const Matrix& lineP1, const Matrix& lineP2) { return aabb.contains(lineP1) || aabb.contains(lineP2) || @@ -161,10 +169,31 @@ template bool is_collision_aabb_line_segment(const AlignedBox is_collision_line_segment_line_segment(lineP1, lineP2, aabb.corner(AlignedBox::TopLeft), aabb.corner(AlignedBox::BottomLeft)); } +template std::vector> collision_aabb_small_line_segment_large(const AlignedBox& aabb, const Matrix& lineP1, const Matrix& lineP2) { + std::vector> toRet; + auto optCollision = collision_line_segment_line_segment_pos(lineP1, lineP2, aabb.corner(AlignedBox::TopLeft), aabb.corner(AlignedBox::TopRight)); + if(optCollision.has_value()) + toRet.emplace_back(optCollision.value()); + optCollision = collision_line_segment_line_segment_pos(lineP1, lineP2, aabb.corner(AlignedBox::TopRight), aabb.corner(AlignedBox::BottomRight)); + if(optCollision.has_value()) + toRet.emplace_back(optCollision.value()); + optCollision = collision_line_segment_line_segment_pos(lineP1, lineP2, aabb.corner(AlignedBox::BottomLeft), aabb.corner(AlignedBox::BottomRight)); + if(optCollision.has_value()) + toRet.emplace_back(optCollision.value()); + optCollision = collision_line_segment_line_segment_pos(lineP1, lineP2, aabb.corner(AlignedBox::TopLeft), aabb.corner(AlignedBox::BottomLeft)); + if(optCollision.has_value()) + toRet.emplace_back(optCollision.value()); + return toRet; +} + template T vec_distance(const Vector& v1, const Vector& v2) { return (v1 - v2).norm(); } +template T vec_distance_sqrd(const Vector& v1, const Vector& v2) { + return (v1 - v2).squaredNorm(); +} + template T vec_length(const Vector& v) { return v.norm(); } @@ -284,3 +313,42 @@ template Vector ensure_points_have_distance(const Vector void clip_triangle_against_axis(std::vector, 3>>& triangleList, const std::array, 3>& t, const std::array, 2>& axisLineSegment, const std::function&)>& isInClippingAreaFunc) { + std::array inClippingArea = { + isInClippingAreaFunc(t[0]), + isInClippingAreaFunc(t[1]), + isInClippingAreaFunc(t[2]) + }; + + auto oneVertexInClippingArea = [&](const Vector& a, const Vector& b, const Vector& c) { + Vector bPrime = collision_line_segment_line_segment_guaranteed(a, b, axisLineSegment[0], axisLineSegment[1]); + Vector cPrime = collision_line_segment_line_segment_guaranteed(a, c, axisLineSegment[0], axisLineSegment[1]); + triangleList.emplace_back(std::array, 3>{a, bPrime, cPrime}); + }; + + auto twoVerticesInClippingArea = [&](const Vector& a, const Vector& b, const Vector& c) { + Vector aPrime = collision_line_segment_line_segment_guaranteed(a, c, axisLineSegment[0], axisLineSegment[1]); + Vector bPrime = collision_line_segment_line_segment_guaranteed(b, c, axisLineSegment[0], axisLineSegment[1]); + triangleList.emplace_back(std::array, 3>{a, b, aPrime}); + triangleList.emplace_back(std::array, 3>{aPrime, b, bPrime}); + }; + + if(inClippingArea[0] && inClippingArea[1] && inClippingArea[2]) + triangleList.emplace_back(t); + else if(!inClippingArea[0] && !inClippingArea[1] && !inClippingArea[2]) + return; + else if(inClippingArea[0] && !inClippingArea[1] && !inClippingArea[2]) + oneVertexInClippingArea(t[0], t[1], t[2]); + else if(!inClippingArea[0] && inClippingArea[1] && !inClippingArea[2]) + oneVertexInClippingArea(t[1], t[0], t[2]); + else if(!inClippingArea[0] && !inClippingArea[1] && inClippingArea[2]) + oneVertexInClippingArea(t[2], t[0], t[1]); + else if(inClippingArea[0] && inClippingArea[1] && !inClippingArea[2]) + twoVerticesInClippingArea(t[0], t[1], t[2]); + else if(inClippingArea[0] && !inClippingArea[1] && inClippingArea[2]) + twoVerticesInClippingArea(t[0], t[2], t[1]); + else if(!inClippingArea[0] && inClippingArea[1] && inClippingArea[2]) + twoVerticesInClippingArea(t[1], t[2], t[0]); +} diff --git a/include/Helpers/SCollision.hpp b/include/Helpers/SCollision.hpp index 9f19269..2a0591c 100644 --- a/include/Helpers/SCollision.hpp +++ b/include/Helpers/SCollision.hpp @@ -48,6 +48,12 @@ namespace SCollision { bool fully_contains_aabb(const AABB& other) const { return other.min.x() >= min.x() && other.min.y() >= min.y() && other.max.x() <= max.x() && other.max.y() <= max.y(); } + Vector clamp_point_to_aabb(const Vector& p) const { + return { + std::clamp(p.x(), min.x(), max.x()), + std::clamp(p.y(), min.y(), max.y()) + }; + } template AABB transform(const std::function(const Vector&)>& transformFunc, const std::function& transformLinearFunc) const { Vector aabb1 = transformFunc(min); Vector aabb2 = transformFunc(max); @@ -321,6 +327,7 @@ namespace SCollision { return (u >= T(0)) && (v >= T(0)) && (u + v < T(1)); } + template bool collide(const Triangle& t, const Vector& p) { return collide(p, t); } diff --git a/src/CanvasComponents/BrushStrokeCanvasComponent.cpp b/src/CanvasComponents/BrushStrokeCanvasComponent.cpp index 6c09d7c..f2d920d 100644 --- a/src/CanvasComponents/BrushStrokeCanvasComponent.cpp +++ b/src/CanvasComponents/BrushStrokeCanvasComponent.cpp @@ -3,12 +3,14 @@ #include #include #include +#include "Eigen/Core" #include "Helpers/MathExtras.hpp" #include "../TimePoint.hpp" #include #include #include "../DrawCollision.hpp" #include "CanvasComponentContainer.hpp" +#include "Helpers/SCollision.hpp" #define AABB_PRECHECK_BVH_LEVELS 2 #define DEFAULT_SMOOTHNESS 5 @@ -57,6 +59,82 @@ void BrushStrokeCanvasComponent::draw(SkCanvas* canvas, const DrawData& drawData canvas->drawPath(*brushPath, paint); else canvas->drawPath(*brushPathLOD[mipmapLevel - 1], paint); + + //SkPaint paint2; + //paint2.setColor4f(SkColor4f{1.0f, 0.0f, 0.0f, 1.0f}); + //paint2.setStroke(true); + //SCollision::ColliderCollection strokeObjects; + //std::vector points = smooth_points(0, d.points->size() - 1, DEFAULT_SMOOTHNESS); + //create_triangles([&](Vector2f a, Vector2f b, Vector2f c) { + // strokeObjects.triangle.emplace_back(a, b, c); + // return false; + //}, points, 0, nullptr); + //strokeObjects.recalculate_bounds(); + //draw_collider(canvas, drawData, strokeObjects, paint2); +} + +bool BrushStrokeCanvasComponent::accurate_draw(SkCanvas* canvas, const DrawData& drawData, const CoordSpaceHelper& coords) const { + std::vector points = smooth_points(0, d.points->size() - 1, DEFAULT_SMOOTHNESS); + + std::vector pathsToDraw; + + auto triangleFunc = [&](Vector2f a, Vector2f b, Vector2f c) { + SCollision::Triangle worldTri = { + coords.from_space(a), + coords.from_space(b), + coords.from_space(c) + }; + + auto clipListFunc = [](const std::vector>& clipList, const std::array& axisLineSegment, const std::function& isInClippingAreaFunc) { + std::vector> resultList; + for(auto& t : clipList) + clip_triangle_against_axis(resultList, t, axisLineSegment, isInClippingAreaFunc); + return resultList; + }; + + if(SCollision::collide(worldTri.bounds, drawData.cam.viewingAreaGenerousCollider)) { + std::vector> clipList = {worldTri.p}; + clipList = clipListFunc(clipList, {drawData.cam.viewingAreaGenerousCollider.min, drawData.cam.viewingAreaGenerousCollider.top_right()}, [&](const WorldVec& p) { + return p.y() > drawData.cam.viewingAreaGenerousCollider.min.y(); + }); + clipList = clipListFunc(clipList, {drawData.cam.viewingAreaGenerousCollider.max, drawData.cam.viewingAreaGenerousCollider.bottom_left()}, [&](const WorldVec& p) { + return p.y() < drawData.cam.viewingAreaGenerousCollider.max.y(); + }); + clipList = clipListFunc(clipList, {drawData.cam.viewingAreaGenerousCollider.min, drawData.cam.viewingAreaGenerousCollider.bottom_left()}, [&](const WorldVec& p) { + return p.x() > drawData.cam.viewingAreaGenerousCollider.min.x(); + }); + clipList = clipListFunc(clipList, {drawData.cam.viewingAreaGenerousCollider.max, drawData.cam.viewingAreaGenerousCollider.top_right()}, [&](const WorldVec& p) { + return p.x() < drawData.cam.viewingAreaGenerousCollider.max.x(); + }); + for(auto& tri : clipList) { + auto& c = drawData.cam.c; + SkPathBuilder pathBuilder; + std::vector triSkPoints = {convert_vec2(c.to_space(tri[0])), convert_vec2(c.to_space(tri[1])), convert_vec2(c.to_space(tri[2]))}; + pathBuilder.addPolygon({triSkPoints.data(), triSkPoints.size()}, true); + pathsToDraw.emplace_back(pathBuilder.detach()); + } + } + return false; + }; + + create_triangles(triangleFunc, points, 0, nullptr); + + if(d.color.w() == 1.0f) { + SkPaint paint; + paint.setColor4f(SkColor4f{d.color.x(), d.color.y(), d.color.z(), d.color.w()}); + for(const SkPath& p : pathsToDraw) + canvas->drawPath(p, paint); + } + else { + canvas->saveLayerAlphaf(nullptr, d.color.w()); + SkPaint paint; + paint.setColor4f(SkColor4f{d.color.x(), d.color.y(), d.color.z(), 1.0f}); + for(const SkPath& p : pathsToDraw) + canvas->drawPath(p, paint); + canvas->restore(); + } + + return true; } void BrushStrokeCanvasComponent::set_data_from(const CanvasComponent& other) { @@ -351,7 +429,7 @@ void BrushStrokeCanvasComponent::create_triangles(const std::functionaddPolygon({topPoints.data(), topPoints.size()}, false); + bPathBuilder->addPolygon({topPoints.data(), topPoints.size()}, false); } } diff --git a/src/CanvasComponents/BrushStrokeCanvasComponent.hpp b/src/CanvasComponents/BrushStrokeCanvasComponent.hpp index 9ca3a26..4c9dbb9 100644 --- a/src/CanvasComponents/BrushStrokeCanvasComponent.hpp +++ b/src/CanvasComponents/BrushStrokeCanvasComponent.hpp @@ -35,6 +35,7 @@ class BrushStrokeCanvasComponent : public CanvasComponent { virtual void set_data_from(const CanvasComponent& other) override; private: virtual void draw(SkCanvas* canvas, const DrawData& drawData) const override; + virtual bool accurate_draw(SkCanvas* canvas, const DrawData& drawData, const CoordSpaceHelper& coords) const override; virtual void initialize_draw_data(DrawingProgram& drawP) override; virtual bool collides_within_coords(const SCollision::ColliderCollection& checkAgainst) const override; void create_collider(); diff --git a/src/CanvasComponents/CanvasComponent.cpp b/src/CanvasComponents/CanvasComponent.cpp index c34fd62..bc27df4 100644 --- a/src/CanvasComponents/CanvasComponent.cpp +++ b/src/CanvasComponents/CanvasComponent.cpp @@ -37,4 +37,8 @@ std::optional CanvasComponent::get_stroke_color() const { return std::nullopt; } +bool CanvasComponent::accurate_draw(SkCanvas* canvas, const DrawData& drawData, const CoordSpaceHelper& coords) const { + return false; +} + CanvasComponent::~CanvasComponent() {} diff --git a/src/CanvasComponents/CanvasComponent.hpp b/src/CanvasComponents/CanvasComponent.hpp index 08a2942..4d82041 100644 --- a/src/CanvasComponents/CanvasComponent.hpp +++ b/src/CanvasComponents/CanvasComponent.hpp @@ -30,6 +30,8 @@ class CanvasComponent { friend class CanvasComponentContainer; friend class CanvasComponentAllocator; + virtual bool accurate_draw(SkCanvas* canvas, const DrawData& drawData, const CoordSpaceHelper& coords) const; + virtual void draw(SkCanvas* canvas, const DrawData& drawData) const = 0; virtual void initialize_draw_data(DrawingProgram& drawP) = 0; virtual bool collides_within_coords(const SCollision::ColliderCollection& checkAgainst) const = 0; diff --git a/src/CanvasComponents/CanvasComponentContainer.cpp b/src/CanvasComponents/CanvasComponentContainer.cpp index f138a3c..535eab4 100644 --- a/src/CanvasComponents/CanvasComponentContainer.cpp +++ b/src/CanvasComponents/CanvasComponentContainer.cpp @@ -93,10 +93,12 @@ void CanvasComponentContainer::draw(SkCanvas* canvas, const DrawData& drawData) } void CanvasComponentContainer::draw_with_transform(SkCanvas* canvas, const DrawData& drawData, const TransformDrawData& transformDrawData) const { - canvas->save(); - canvas_do_transform(canvas, transformDrawData); - get_comp().draw(canvas, drawData); - canvas->restore(); + if(transformDrawData.scale < COMP_MAX_BEFORE_STOP_SCALING || !get_comp().accurate_draw(canvas, drawData, coords)) { + canvas->save(); + canvas_do_transform(canvas, transformDrawData); + get_comp().draw(canvas, drawData); + canvas->restore(); + } } void CanvasComponentContainer::commit_update(DrawingProgram& drawP) { @@ -163,7 +165,7 @@ CanvasComponentContainer::TransformDrawData CanvasComponentContainer::calculate_ TransformDrawData toRet; toRet.translation = -coords.to_space(drawData.cam.c.pos); toRet.rotation = (coords.rotation - drawData.cam.c.rotation) * 180.0 / std::numbers::pi; - toRet.scale = std::min(static_cast(coords.inverseScale / drawData.cam.c.inverseScale), static_cast(1 << COMP_MAX_SHIFT_BEFORE_STOP_SCALING)); + toRet.scale = std::min(static_cast(coords.inverseScale / drawData.cam.c.inverseScale), COMP_MAX_BEFORE_STOP_SCALING); return toRet; } diff --git a/src/CanvasComponents/CanvasComponentContainer.hpp b/src/CanvasComponents/CanvasComponentContainer.hpp index 7f19e81..53a8ac0 100644 --- a/src/CanvasComponents/CanvasComponentContainer.hpp +++ b/src/CanvasComponents/CanvasComponentContainer.hpp @@ -22,6 +22,7 @@ class CanvasComponentContainer { constexpr static int COMP_MAX_SHIFT_BEFORE_STOP_COLLISIONS = 14; constexpr static int COMP_MAX_SHIFT_BEFORE_STOP_SCALING = 14; + constexpr static float COMP_MAX_BEFORE_STOP_SCALING = 1 << COMP_MAX_SHIFT_BEFORE_STOP_SCALING; constexpr static int COMP_MIN_SHIFT_BEFORE_DISAPPEAR = 11; constexpr static int COMP_COLLIDE_MIN_SHIFT_TINY = 9; constexpr static int COMP_MIPMAP_LEVEL_ONE = 2;