Brush strokes now have accurate_draw
This commit is contained in:
parent
d648350c6a
commit
fd1f76520b
8 changed files with 172 additions and 9 deletions
|
|
@ -142,16 +142,24 @@ template <typename T> bool is_collision_line_segment_line_segment(const Matrix<T
|
|||
return (oa * ob < T(0) && oc * od < T(0));
|
||||
}
|
||||
|
||||
template <typename T> std::optional<Vector2d> collision_line_segment_line_segment_pos(const Matrix<T, 2, 1>& a, const Matrix<T, 2, 1>& b, const Matrix<T, 2, 1>& c, const Matrix<T, 2, 1>& d) {
|
||||
template <typename T> std::optional<Matrix<T, 2, 1>> collision_line_segment_line_segment_pos(const Matrix<T, 2, 1>& a, const Matrix<T, 2, 1>& b, const Matrix<T, 2, 1>& c, const Matrix<T, 2, 1>& 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<Vector2d>((a * ob - b * oa) / (ob - oa));
|
||||
if(oa * ob < T(0) && oc * od < T(0))
|
||||
return std::optional<Matrix<T, 2, 1>>((a * ob - b * oa) / (ob - oa));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
template <typename T> Matrix<T, 2, 1> collision_line_segment_line_segment_guaranteed(const Matrix<T, 2, 1>& a, const Matrix<T, 2, 1>& b, const Matrix<T, 2, 1>& c, const Matrix<T, 2, 1>& 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<T, 2, 1>((a * ob - b * oa) / (ob - oa));
|
||||
}
|
||||
|
||||
template <typename T> bool is_collision_aabb_line_segment(const AlignedBox<T, 2>& aabb, const Matrix<T, 2, 1>& lineP1, const Matrix<T, 2, 1>& lineP2) {
|
||||
return aabb.contains(lineP1) ||
|
||||
aabb.contains(lineP2) ||
|
||||
|
|
@ -161,10 +169,31 @@ template <typename T> bool is_collision_aabb_line_segment(const AlignedBox<T, 2>
|
|||
is_collision_line_segment_line_segment(lineP1, lineP2, aabb.corner(AlignedBox<T, 2>::TopLeft), aabb.corner(AlignedBox<T, 2>::BottomLeft));
|
||||
}
|
||||
|
||||
template <typename T> std::vector<Matrix<T, 2, 1>> collision_aabb_small_line_segment_large(const AlignedBox<T, 2>& aabb, const Matrix<T, 2, 1>& lineP1, const Matrix<T, 2, 1>& lineP2) {
|
||||
std::vector<Matrix<T, 2, 1>> toRet;
|
||||
auto optCollision = collision_line_segment_line_segment_pos(lineP1, lineP2, aabb.corner(AlignedBox<T, 2>::TopLeft), aabb.corner(AlignedBox<T, 2>::TopRight));
|
||||
if(optCollision.has_value())
|
||||
toRet.emplace_back(optCollision.value());
|
||||
optCollision = collision_line_segment_line_segment_pos(lineP1, lineP2, aabb.corner(AlignedBox<T, 2>::TopRight), aabb.corner(AlignedBox<T, 2>::BottomRight));
|
||||
if(optCollision.has_value())
|
||||
toRet.emplace_back(optCollision.value());
|
||||
optCollision = collision_line_segment_line_segment_pos(lineP1, lineP2, aabb.corner(AlignedBox<T, 2>::BottomLeft), aabb.corner(AlignedBox<T, 2>::BottomRight));
|
||||
if(optCollision.has_value())
|
||||
toRet.emplace_back(optCollision.value());
|
||||
optCollision = collision_line_segment_line_segment_pos(lineP1, lineP2, aabb.corner(AlignedBox<T, 2>::TopLeft), aabb.corner(AlignedBox<T, 2>::BottomLeft));
|
||||
if(optCollision.has_value())
|
||||
toRet.emplace_back(optCollision.value());
|
||||
return toRet;
|
||||
}
|
||||
|
||||
template <typename T> T vec_distance(const Vector<T, 2>& v1, const Vector<T, 2>& v2) {
|
||||
return (v1 - v2).norm();
|
||||
}
|
||||
|
||||
template <typename T> T vec_distance_sqrd(const Vector<T, 2>& v1, const Vector<T, 2>& v2) {
|
||||
return (v1 - v2).squaredNorm();
|
||||
}
|
||||
|
||||
template <typename T> T vec_length(const Vector<T, 2>& v) {
|
||||
return v.norm();
|
||||
}
|
||||
|
|
@ -284,3 +313,42 @@ template <typename T> Vector<T, 2> ensure_points_have_distance(const Vector<T, 2
|
|||
toRet.y() = a.y() + d;
|
||||
return toRet;
|
||||
}
|
||||
|
||||
// https://www.gabrielgambetta.com/computer-graphics-from-scratch/11-clipping.html
|
||||
template <typename T> void clip_triangle_against_axis(std::vector<std::array<Vector<T, 2>, 3>>& triangleList, const std::array<Vector<T, 2>, 3>& t, const std::array<Vector<T, 2>, 2>& axisLineSegment, const std::function<bool(const Vector<T, 2>&)>& isInClippingAreaFunc) {
|
||||
std::array<bool, 3> inClippingArea = {
|
||||
isInClippingAreaFunc(t[0]),
|
||||
isInClippingAreaFunc(t[1]),
|
||||
isInClippingAreaFunc(t[2])
|
||||
};
|
||||
|
||||
auto oneVertexInClippingArea = [&](const Vector<T, 2>& a, const Vector<T, 2>& b, const Vector<T, 2>& c) {
|
||||
Vector<T, 2> bPrime = collision_line_segment_line_segment_guaranteed(a, b, axisLineSegment[0], axisLineSegment[1]);
|
||||
Vector<T, 2> cPrime = collision_line_segment_line_segment_guaranteed(a, c, axisLineSegment[0], axisLineSegment[1]);
|
||||
triangleList.emplace_back(std::array<Vector<T, 2>, 3>{a, bPrime, cPrime});
|
||||
};
|
||||
|
||||
auto twoVerticesInClippingArea = [&](const Vector<T, 2>& a, const Vector<T, 2>& b, const Vector<T, 2>& c) {
|
||||
Vector<T, 2> aPrime = collision_line_segment_line_segment_guaranteed(a, c, axisLineSegment[0], axisLineSegment[1]);
|
||||
Vector<T, 2> bPrime = collision_line_segment_line_segment_guaranteed(b, c, axisLineSegment[0], axisLineSegment[1]);
|
||||
triangleList.emplace_back(std::array<Vector<T, 2>, 3>{a, b, aPrime});
|
||||
triangleList.emplace_back(std::array<Vector<T, 2>, 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]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,12 @@ namespace SCollision {
|
|||
bool fully_contains_aabb(const AABB<T>& other) const {
|
||||
return other.min.x() >= min.x() && other.min.y() >= min.y() && other.max.x() <= max.x() && other.max.y() <= max.y();
|
||||
}
|
||||
Vector<T, 2> clamp_point_to_aabb(const Vector<T, 2>& p) const {
|
||||
return {
|
||||
std::clamp(p.x(), min.x(), max.x()),
|
||||
std::clamp(p.y(), min.y(), max.y())
|
||||
};
|
||||
}
|
||||
template <typename S> AABB<S> transform(const std::function<Vector<S, 2>(const Vector<T, 2>&)>& transformFunc, const std::function<S(const T&)>& transformLinearFunc) const {
|
||||
Vector<S, 2> aabb1 = transformFunc(min);
|
||||
Vector<S, 2> aabb2 = transformFunc(max);
|
||||
|
|
@ -321,6 +327,7 @@ namespace SCollision {
|
|||
|
||||
return (u >= T(0)) && (v >= T(0)) && (u + v < T(1));
|
||||
}
|
||||
|
||||
template <typename T> bool collide(const Triangle<T>& t, const Vector<T, 2>& p) {
|
||||
return collide(p, t);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@
|
|||
#include <cereal/types/vector.hpp>
|
||||
#include <limits>
|
||||
#include <Eigen/Geometry>
|
||||
#include "Eigen/Core"
|
||||
#include "Helpers/MathExtras.hpp"
|
||||
#include "../TimePoint.hpp"
|
||||
#include <include/core/SkVertices.h>
|
||||
#include <include/pathops/SkPathOps.h>
|
||||
#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<float> strokeObjects;
|
||||
//std::vector<BrushStrokeCanvasComponentPoint> 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<BrushStrokeCanvasComponentPoint> points = smooth_points(0, d.points->size() - 1, DEFAULT_SMOOTHNESS);
|
||||
|
||||
std::vector<SkPath> 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<std::array<WorldVec, 3>>& clipList, const std::array<WorldVec, 2>& axisLineSegment, const std::function<bool(const WorldVec&)>& isInClippingAreaFunc) {
|
||||
std::vector<std::array<WorldVec, 3>> 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<std::array<WorldVec, 3>> 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<SkPoint> triSkPoints = {convert_vec2<SkPoint>(c.to_space(tri[0])), convert_vec2<SkPoint>(c.to_space(tri[1])), convert_vec2<SkPoint>(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::function<bool(Vecto
|
|||
|
||||
if(bPathBuilder && !topPoints.empty()) {
|
||||
topPoints.insert(topPoints.begin(), bottomPoints.rbegin(), bottomPoints.rend());
|
||||
bPathBuilder->addPolygon({topPoints.data(), topPoints.size()}, false);
|
||||
bPathBuilder->addPolygon({topPoints.data(), topPoints.size()}, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<float>& checkAgainst) const override;
|
||||
void create_collider();
|
||||
|
|
|
|||
|
|
@ -37,4 +37,8 @@ std::optional<Vector4f> 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() {}
|
||||
|
|
|
|||
|
|
@ -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<float>& checkAgainst) const = 0;
|
||||
|
|
|
|||
|
|
@ -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<float>(coords.inverseScale / drawData.cam.c.inverseScale), static_cast<float>(1 << COMP_MAX_SHIFT_BEFORE_STOP_SCALING));
|
||||
toRet.scale = std::min(static_cast<float>(coords.inverseScale / drawData.cam.c.inverseScale), COMP_MAX_BEFORE_STOP_SCALING);
|
||||
return toRet;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue