Decrease details in brush strokes when zooming out

This commit is contained in:
Yousef Khadadeh 2025-07-14 17:00:28 +03:00
parent 5dd7311c20
commit d4b70a2386
7 changed files with 96 additions and 31 deletions

View file

@ -12,6 +12,7 @@
#endif
#define AABB_PRECHECK_BVH_LEVELS 2
#define DEFAULT_SMOOTHNESS 5
void DrawBrushStroke::save(cereal::PortableBinaryOutputArchive& a) const {
a(d.points, d.color, d.hasRoundCaps);
@ -50,7 +51,7 @@ Vector2f two_dim_vec_slerp(const Vector2f& v1, const Vector2f& v2) {
}
void DrawBrushStroke::draw(SkCanvas* canvas, const DrawData& drawData) {
if(drawSetupData.shouldDraw) {
if(drawSetupData.shouldDraw && vertices) {
//bool deepPrecheck = false;
//for(auto& aabb : precheckAABBLevels) {
// if(SCollision::collide(drawData.cam.viewingAreaGenerousCollider, aabb)) {
@ -68,7 +69,10 @@ void DrawBrushStroke::draw(SkCanvas* canvas, const DrawData& drawData) {
canvas->saveLayerAlphaf(nullptr, d.color.w());
paint.setColor4f(SkColor4f{d.color.x(), d.color.y(), d.color.z(), 1.0f});
canvas_do_calculated_transform(canvas);
canvas->drawVertices(vertices, SkBlendMode::kClear, paint);
if(drawSetupData.mipmapLevel == 0)
canvas->drawVertices(vertices, SkBlendMode::kClear, paint);
else
canvas->drawVertices(verticesMipMap[drawSetupData.mipmapLevel - 1], SkBlendMode::kClear, paint);
//SkPaint p2(SkColor4f{1, 0, 0, 1});
//p2.setStroke(true);
//SkPaint p3(SkColor4f{0, 1, 0, 1});
@ -83,14 +87,38 @@ void DrawBrushStroke::draw(SkCanvas* canvas, const DrawData& drawData) {
}
void DrawBrushStroke::initialize_draw_data(DrawingProgram& drawP) {
std::vector<SkPoint> vertexData;
create_triangles([&](Vector2f a, Vector2f b, Vector2f c) {
vertexData.emplace_back(a.x(), a.y());
vertexData.emplace_back(b.x(), b.y());
vertexData.emplace_back(c.x(), c.y());
return false;
});
vertices_to_draw_data(vertexData);
if(!d.points.empty()) {
std::vector<SkPoint> vertexData;
std::vector<DrawBrushStrokePoint> points = smooth_points(0, d.points.size() - 1, DEFAULT_SMOOTHNESS);
auto triangleFunc = [&](Vector2f a, Vector2f b, Vector2f c) {
vertexData.emplace_back(a.x(), a.y());
vertexData.emplace_back(b.x(), b.y());
vertexData.emplace_back(c.x(), c.y());
return false;
};
create_triangles(triangleFunc, points, 0);
vertices_to_draw_data(vertices, vertexData);
vertexData.clear();
create_triangles(triangleFunc, points, 2);
vertices_to_draw_data(verticesMipMap[0], vertexData);
vertexData.clear();
create_triangles(triangleFunc, points, 4);
vertices_to_draw_data(verticesMipMap[1], vertexData);
vertexData.clear();
create_triangles(triangleFunc, points, 6);
vertices_to_draw_data(verticesMipMap[2], vertexData);
vertexData.clear();
create_triangles(triangleFunc, points, 10);
vertices_to_draw_data(verticesMipMap[3], vertexData);
create_triangles(triangleFunc, points, 30);
vertices_to_draw_data(verticesMipMap[4], vertexData);
}
}
std::vector<size_t> DrawBrushStroke::get_wedge_indices(const std::vector<DrawBrushStrokePoint>& points) {
@ -108,7 +136,7 @@ std::vector<size_t> DrawBrushStroke::get_wedge_indices(const std::vector<DrawBru
return toRet;
}
std::vector<DrawBrushStrokePoint> DrawBrushStroke::smooth_points(size_t beginIndex, size_t endIndex) {
std::vector<DrawBrushStrokePoint> DrawBrushStroke::smooth_points(size_t beginIndex, size_t endIndex, unsigned numOfDivisions) {
std::vector<DrawBrushStrokePoint>& points = d.points;
size_t pointsSize = endIndex - beginIndex + 1;
@ -134,8 +162,6 @@ std::vector<DrawBrushStrokePoint> DrawBrushStroke::smooth_points(size_t beginInd
toRet.emplace_back(p1);
const unsigned numOfDivisions = 5; // How smooth the stroke should be
float tMove = 1.0 / static_cast<float>(numOfDivisions);
for(unsigned d = 1; d < numOfDivisions; d++) {
float t = tMove * static_cast<float>(d);
@ -154,7 +180,7 @@ std::vector<DrawBrushStrokePoint> DrawBrushStroke::smooth_points(size_t beginInd
return toRet;
}
std::vector<DrawBrushStrokePoint> DrawBrushStroke::smooth_points_avg(size_t beginIndex, size_t endIndex) {
std::vector<DrawBrushStrokePoint> DrawBrushStroke::smooth_points_avg(size_t beginIndex, size_t endIndex, unsigned numOfDivisions) {
std::vector<DrawBrushStrokePoint>& points = d.points;
size_t pointsSize = endIndex - beginIndex + 1;
@ -167,8 +193,6 @@ std::vector<DrawBrushStrokePoint> DrawBrushStroke::smooth_points_avg(size_t begi
const DrawBrushStrokePoint& p1 = points[i];
const DrawBrushStrokePoint& p2 = points[i + 1];
const unsigned numOfDivisions = 5; // How smooth the stroke should be
float tMove = 1.0 / static_cast<float>(numOfDivisions);
for(unsigned d = 0; d < numOfDivisions; d++) {
float t = tMove * static_cast<float>(d);
@ -185,7 +209,7 @@ std::vector<DrawBrushStrokePoint> DrawBrushStroke::smooth_points_avg(size_t begi
return toRet;
}
void DrawBrushStroke::create_triangles(const std::function<bool(Vector2f, Vector2f, Vector2f)>& passTriangleFunc) {
void DrawBrushStroke::create_triangles(const std::function<bool(Vector2f, Vector2f, Vector2f)>& passTriangleFunc, const std::vector<DrawBrushStrokePoint>& points, unsigned skipVertexCount) {
const int ARC_SMOOTHNESS = 10;
const int CIRCLE_SMOOTHNESS = 20;
std::vector<DrawBrushStrokePoint>& pointsN = d.points;
@ -199,9 +223,9 @@ void DrawBrushStroke::create_triangles(const std::function<bool(Vector2f, Vector
if(pointsN.size() < 2)
return;
std::vector<DrawBrushStrokePoint> points = smooth_points(0, pointsN.size() - 1);
std::vector<size_t> wedgeIndices = get_wedge_indices(points);
for(size_t i = 0; i < wedgeIndices.size() - 1; i++) {
size_t pointsBegin = wedgeIndices[i];
size_t pointsEnd = wedgeIndices[i + 1];
@ -227,6 +251,10 @@ void DrawBrushStroke::create_triangles(const std::function<bool(Vector2f, Vector
}
for(size_t j = pointsBegin + 1; j < pointsEnd; j++) {
j += skipVertexCount;
if(j >= pointsEnd)
j = pointsEnd - 1;
Vector2f v1 = points[j - 1].pos - points[j].pos;
Vector2f v2 = points[j].pos - points[j + 1].pos;
Vector2f perp = perpendicular_vec2((v1 + v2).eval()).normalized();
@ -318,11 +346,14 @@ void DrawBrushStroke::create_triangles(const std::function<bool(Vector2f, Vector
void DrawBrushStroke::create_collider(bool colliderAllocated) {
using namespace SCollision;
if(d.points.empty())
return;
ColliderCollection<float> strokeObjects;
std::vector<DrawBrushStrokePoint> 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);
strokeObjects.recalculate_bounds();
collisionTree.clear();
collisionTree.calculate_bvh_recursive(strokeObjects);
@ -346,11 +377,11 @@ void DrawBrushStroke::add_precheck_aabb_level(size_t level, const std::vector<SC
}
}
void DrawBrushStroke::vertices_to_draw_data(const std::vector<SkPoint>& vertexData) {
void DrawBrushStroke::vertices_to_draw_data(sk_sp<SkVertices>& vertexDataPtr, const std::vector<SkPoint>& vertexData) {
if((d.points.size() == 1 && d.hasRoundCaps) || d.points.size() >= 2)
vertices = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode, vertexData.size(), vertexData.data(), nullptr, nullptr);
vertexDataPtr = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode, vertexData.size(), vertexData.data(), nullptr, nullptr);
else
vertices = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode, 0, nullptr, nullptr, nullptr);
vertexDataPtr = SkVertices::MakeCopy(SkVertices::kTriangles_VertexMode, 0, nullptr, nullptr, nullptr);
}
void DrawBrushStroke::update(DrawingProgram& drawP) {
@ -374,10 +405,11 @@ bool DrawBrushStroke::collides_within_coords(const SCollision::ColliderCollectio
return false;
bool toRet = false;
std::vector<DrawBrushStrokePoint> points = smooth_points(0, d.points.size() - 1, DEFAULT_SMOOTHNESS);
create_triangles([&](Vector2f a, Vector2f b, Vector2f c) {
toRet = SCollision::collide(checkAgainst, SCollision::Triangle<float>{a, b, c});
return toRet;
});
}, points, 0);
return toRet;
}

View file

@ -47,16 +47,17 @@ class DrawBrushStroke : public DrawComponent {
virtual SCollision::AABB<float> get_obj_coord_bounds() const override;
sk_sp<SkVertices> vertices;
std::array<sk_sp<SkVertices>, 5> verticesMipMap;
SCollision::BVHContainer<float> collisionTree;
private:
void add_precheck_aabb_level(size_t level, const std::vector<SCollision::BVHContainer<float>>& levelArray);
std::vector<SCollision::AABB<float>> precheckAABBLevels;
void create_triangles(const std::function<bool(Vector2f, Vector2f, Vector2f)>& passTriangleFunc);
void vertices_to_draw_data(const std::vector<SkPoint>& vertexData);
void create_triangles(const std::function<bool(Vector2f, Vector2f, Vector2f)>& passTriangleFunc, const std::vector<DrawBrushStrokePoint>& points, unsigned skipVertexCount);
void vertices_to_draw_data(sk_sp<SkVertices>& vertexDataPtr, const std::vector<SkPoint>& vertexData);
std::vector<size_t> get_wedge_indices(const std::vector<DrawBrushStrokePoint>& points);
std::vector<DrawBrushStrokePoint> smooth_points(size_t beginIndex, size_t endIndex);
std::vector<DrawBrushStrokePoint> smooth_points_avg(size_t beginIndex, size_t endIndex);
std::vector<DrawBrushStrokePoint> smooth_points(size_t beginIndex, size_t endIndex, unsigned numOfDivisions);
std::vector<DrawBrushStrokePoint> smooth_points_avg(size_t beginIndex, size_t endIndex, unsigned numOfDivisions);
#endif
};

View file

@ -188,6 +188,18 @@ void DrawComponent::calculate_draw_transform(const DrawData& drawData) {
if(drawSetupData.shouldDraw) {
drawSetupData.shouldDraw = updateDraw || !worldAABB || SCollision::collide(*worldAABB, drawData.cam.viewingAreaGenerousCollider);
if(drawSetupData.shouldDraw) {
if(drawData.mipMapLevelFive > coords.inverseScale)
drawSetupData.mipmapLevel = 5;
if(drawData.mipMapLevelFour > coords.inverseScale)
drawSetupData.mipmapLevel = 4;
else if(drawData.mipMapLevelThree > coords.inverseScale)
drawSetupData.mipmapLevel = 3;
else if(drawData.mipMapLevelTwo > coords.inverseScale)
drawSetupData.mipmapLevel = 2;
else if(drawData.mipMapLevelOne > coords.inverseScale)
drawSetupData.mipmapLevel = 1;
else
drawSetupData.mipmapLevel = 0;
drawSetupData.transformData.translation = -coords.to_space(drawData.cam.c.pos);
drawSetupData.transformData.rotation = (coords.rotation - drawData.cam.c.rotation) * 180.0 / std::numbers::pi;
drawSetupData.transformData.scale = static_cast<float>(coords.inverseScale / drawData.cam.c.inverseScale);

View file

@ -16,7 +16,12 @@
#define CLIENT_DRAWCOMP_DELAY_TIMER_DURATION 5.0f
#define DRAWCOMP_MAX_SHIFT_BEFORE_DISAPPEAR 20
#define DRAWCOMP_MIN_SHIFT_BEFORE_DISAPPEAR 9
#define DRAWCOMP_MIN_SHIFT_BEFORE_DISAPPEAR 11
#define DRAWCOMP_MIPMAP_LEVEL_ONE 1
#define DRAWCOMP_MIPMAP_LEVEL_TWO 2
#define DRAWCOMP_MIPMAP_LEVEL_THREE 3
#define DRAWCOMP_MIPMAP_LEVEL_FOUR 4
#define DRAWCOMP_MIPMAP_LEVEL_FIVE 5
class DrawingProgram;
@ -40,6 +45,7 @@ class DrawComponent {
float scale;
} transformData;
bool shouldDraw = true;
unsigned mipmapLevel = 0;
} drawSetupData;
void canvas_do_calculated_transform(SkCanvas* canvas);

View file

@ -16,4 +16,9 @@ struct DrawData {
bool clampDrawBetween = true;
WorldScalar clampDrawMinimum;
WorldScalar clampDrawMaximum;
WorldScalar mipMapLevelOne;
WorldScalar mipMapLevelTwo;
WorldScalar mipMapLevelThree;
WorldScalar mipMapLevelFour;
WorldScalar mipMapLevelFive;
};

View file

@ -173,9 +173,11 @@ void BrushTool::gui_toolbox() {
void BrushTool::commit_stroke() {
if(controls.intermediateItem) {
controls.intermediateItem->client_send_update_final(drawP, controls.intermediateID);
controls.intermediateItem->final_update(drawP);
controls.intermediateItem = nullptr;
if(!controls.intermediateItem->d.points.empty()) {
controls.intermediateItem->client_send_update_final(drawP, controls.intermediateID);
controls.intermediateItem->final_update(drawP);
controls.intermediateItem = nullptr;
}
}
}

View file

@ -320,8 +320,15 @@ WorldScalar World::calculate_zoom_from_uniform_zoom(WorldScalar uniformZoom, Wor
void World::draw(SkCanvas* canvas) {
drawData.clampDrawBetween = true;
drawData.clampDrawMinimum = drawData.cam.c.inverseScale >> DRAWCOMP_MIN_SHIFT_BEFORE_DISAPPEAR;
drawData.mipMapLevelOne = drawData.cam.c.inverseScale >> DRAWCOMP_MIPMAP_LEVEL_ONE;
drawData.mipMapLevelTwo = drawData.cam.c.inverseScale >> DRAWCOMP_MIPMAP_LEVEL_TWO;
drawData.mipMapLevelThree = drawData.cam.c.inverseScale >> DRAWCOMP_MIPMAP_LEVEL_THREE;
drawData.mipMapLevelFour = drawData.cam.c.inverseScale >> DRAWCOMP_MIPMAP_LEVEL_FOUR;
drawData.mipMapLevelFive = drawData.cam.c.inverseScale >> DRAWCOMP_MIPMAP_LEVEL_FOUR;
drawData.clampDrawMaximum = drawData.cam.c.inverseScale << DRAWCOMP_MAX_SHIFT_BEFORE_DISAPPEAR;
drawProg.draw(canvas, drawData);
for(auto& [id, data] : clients) {