Make eraser pressure sensitive

This commit is contained in:
Yousef Khadadeh 2026-05-25 14:46:22 +03:00
parent ad6f942a34
commit c12cd14f6e
9 changed files with 244 additions and 176 deletions

View file

@ -20,10 +20,14 @@
#include <Helpers/ConvertVec.hpp>
#include <Helpers/MathExtras.hpp>
#include "../TimePoint.hpp"
#include <clipper2/clipper.core.h>
#include <clipper2/clipper.h>
#include "../DrawingProgram/DrawingProgram.hpp"
#include "../World.hpp"
#include "../MainProgram.hpp"
#define DEFAULT_SMOOTHNESS 3
#define MINIMUM_DISTANCE_FROM_FIRST_POINT 3.0f
#define MINIMUM_DISTANCE_TO_NEXT_POINT 0.002f
#define DRAW_MINIMUM_LIMIT 1.0f
namespace BrushComponentCode {
@ -256,4 +260,125 @@ SkPath create_triangles(const std::vector<BrushPoint>& regularPoints, const std:
return pathBuilder.detach();
}
void smooth_out_points(std::vector<BrushPoint>& brushPoints, float smoothFactor) {
if(brushPoints.size() >= 2) {
brushPoints.back().width = std::max(brushPoints[brushPoints.size() - 1].width, brushPoints[brushPoints.size() - 2].width * smoothFactor);
for(size_t i = brushPoints.size() - 1; i > 0; i--) {
if(brushPoints[i].width * smoothFactor > brushPoints[i - 1].width)
brushPoints[i - 1].width = brushPoints[i].width * smoothFactor;
else
break;
}
}
}
void fix_tip(std::vector<BrushPoint>& brushPoints) {
if(brushPoints.size() >= 2)
brushPoints[brushPoints.size() - 2].width = brushPoints[brushPoints.size() - 1].width = std::max(brushPoints[brushPoints.size() - 1].width, brushPoints[brushPoints.size() - 2].width);
}
void mouse_button(DrawingProgram& drawP, BrushStrokeGenerationData& genData, const CoordSpaceHelper& strokeCoordSpace, const InputManager::MouseButtonCallbackArgs& button, float brushSize) {
if(button.deviceType == InputManager::MouseDeviceType::PEN && drawP.world.main.conf.tabletOptions.pressureAffectsBrushWidth) {
genData.penWidth = drawP.world.main.input.pen.pressure;
if(genData.penWidth != 0.0f) {
float brushMinSize = drawP.world.main.conf.tabletOptions.brushMinimumSize;
genData.penWidth = brushMinSize + genData.penWidth * (1.0f - brushMinSize);
}
}
else
genData.penWidth = 1.0f;
float width = brushSize * genData.penWidth;
genData.coords = strokeCoordSpace;
genData.brushPoints.clear();
BrushComponentCode::BrushPoint p;
p.pos = button.pos;
p.width = width;
genData.prevPointUnaltered = p.pos;
genData.brushPoints.emplace_back(p);
genData.addedTemporaryPoint = false;
}
void mouse_motion(DrawingProgram& drawP, BrushStrokeGenerationData& genData, const Vector2f& motionPos, float brushSize) {
BrushComponentCode::BrushPoint p;
p.pos = genData.coords.to_space(drawP.world.drawData.cam.c.from_space(motionPos));
p.width = brushSize * genData.penWidth;
// Temporary point is a point that follows the cursor until it is placed
if(!genData.addedTemporaryPoint) {
// Temporary point is close enough to be placed
if(extensive_point_checking(genData.brushPoints, p.pos, genData.brushPoints.size() == 1 ? MINIMUM_DISTANCE_FROM_FIRST_POINT : MINIMUM_DISTANCE_TO_NEXT_POINT)) {
genData.brushPoints.emplace_back(p);
genData.addedTemporaryPoint = true;
}
else
// Temporary point not close enough yet, so change the width of the previous point based on the current pen width until it can be placed
genData.brushPoints.back().width = std::max(genData.brushPoints.back().width, p.width);
}
if(genData.addedTemporaryPoint) {
const BrushComponentCode::BrushPoint& prevP = genData.brushPoints[genData.brushPoints.size() - 2];
float distToPrev = (p.pos - prevP.pos).norm();
// If the cursor isnt too close to the previous points, move the temporary point to the cursor and change the width of the temporary point to the current pen width
if(extensive_point_checking_back(genData.brushPoints, p.pos)) {
genData.brushPoints.back().pos = p.pos;
genData.brushPoints.back().width = std::max(genData.brushPoints.back().width, p.width);
}
float maxWidthToCompareTo = std::max(genData.brushPoints.back().width, p.width);
maxWidthToCompareTo = std::max(maxWidthToCompareTo, 5.0f);
if(genData.brushPoints.size() >= 2)
maxWidthToCompareTo = std::max(genData.brushPoints[genData.brushPoints.size() - 2].width, maxWidthToCompareTo);
if(genData.brushPoints.size() >= 3)
maxWidthToCompareTo = std::max(genData.brushPoints[genData.brushPoints.size() - 3].width, maxWidthToCompareTo);
// If the temporary point (cursor) is far enough from the point before it, make the temporary point permanent
if(distToPrev >= maxWidthToCompareTo * DRAW_MINIMUM_LIMIT) {
genData.brushPoints.back().pos = p.pos;
genData.brushPoints.back().width = std::max(genData.brushPoints.back().width, p.width);
genData.addedTemporaryPoint = false;
// Midway interpolation moves the point before the last one to a position that leads to a smoother line
if(genData.brushPoints.size() != 2) // Don't interpolate the first point
genData.brushPoints[genData.brushPoints.size() - 2].pos = (genData.prevPointUnaltered + p.pos) * 0.5;
genData.prevPointUnaltered = p.pos;
}
}
smooth_out_points(genData.brushPoints, drawP.world.main.conf.tabletOptions.brushPressureSmoothingFactor);
}
void pen_pressure(DrawingProgram& drawP, BrushStrokeGenerationData& genData, float brushSize) {
if(drawP.world.main.conf.tabletOptions.pressureAffectsBrushWidth) {
if(genData.penWidth != 0.0f) {
float brushMinSize = drawP.world.main.conf.tabletOptions.brushMinimumSize;
genData.penWidth = brushMinSize + genData.penWidth * (1.0f - brushMinSize);
float width = brushSize * genData.penWidth;
genData.brushPoints.back().width = std::max(genData.brushPoints.back().width, width);
smooth_out_points(genData.brushPoints, drawP.world.main.conf.tabletOptions.brushPressureSmoothingFactor);
}
}
}
bool extensive_point_checking(const std::vector<BrushPoint>& points, const Vector2f& newPoint, float minimumDistance) {
if(points.size() >= 1 && (newPoint - points[points.size() - 1].pos).norm() < minimumDistance)
return false;
if(points.size() >= 2 && (newPoint - points[points.size() - 2].pos).norm() < minimumDistance)
return false;
if(points.size() >= 3 && (newPoint - points[points.size() - 3].pos).norm() < minimumDistance)
return false;
return true;
}
bool extensive_point_checking_back(const std::vector<BrushPoint>& points, const Vector2f& newPoint) {
if(points.size() >= 2 && (newPoint - points[points.size() - 2].pos).norm() < MINIMUM_DISTANCE_TO_NEXT_POINT)
return false;
if(points.size() >= 3 && (newPoint - points[points.size() - 3].pos).norm() < MINIMUM_DISTANCE_TO_NEXT_POINT)
return false;
if(points.size() >= 4 && (newPoint - points[points.size() - 4].pos).norm() < MINIMUM_DISTANCE_TO_NEXT_POINT)
return false;
return true;
}
}

View file

@ -19,6 +19,9 @@
#pragma once
#include "../CoordSpaceHelper.hpp"
#include <include/core/SkPathBuilder.h>
#include "../InputManager.hpp"
class DrawingProgram;
struct MeshShapeData {
std::vector<std::vector<Vector2f>> contours;
@ -36,8 +39,24 @@ namespace BrushComponentCode {
}
};
struct BrushStrokeGenerationData {
bool addedTemporaryPoint = false;
std::vector<BrushComponentCode::BrushPoint> brushPoints;
Vector2f prevPointUnaltered = {0, 0};
float penWidth = 1.0f;
CoordSpaceHelper coords;
};
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);
std::vector<BrushPoint> smooth_points(const std::vector<BrushPoint>& points, size_t beginIndex, size_t endIndex, unsigned numOfDivisions);
void smooth_out_points(std::vector<BrushPoint>& brushPoints, float smoothFactor);
void fix_tip(std::vector<BrushPoint>& brushPoints);
void mouse_button(DrawingProgram& drawP, BrushStrokeGenerationData& genData, const CoordSpaceHelper& strokeCoordSpace, const InputManager::MouseButtonCallbackArgs& button, float brushSize);
void mouse_motion(DrawingProgram& drawP, BrushStrokeGenerationData& genData, const Vector2f& motionPos, float brushSize);
void pen_pressure(DrawingProgram& drawP, BrushStrokeGenerationData& genData, float brushSize);
bool extensive_point_checking(const std::vector<BrushPoint>& points, const Vector2f& newPoint, float minimumDistance);
bool extensive_point_checking_back(const std::vector<BrushPoint>& points, const Vector2f& newPoint);
}