2025-07-06 20:43:53 +03:00
# include "BrushTool.hpp"
# include <Helpers/ConvertVec.hpp>
# include "../DrawComponents/DrawBrushStroke.hpp"
# include "../GUIStuff/GUIManager.hpp"
# include "DrawingProgram.hpp"
# include "../MainProgram.hpp"
2025-08-02 20:10:33 +03:00
# include "DrawingProgramToolBase.hpp"
2025-07-06 20:43:53 +03:00
# include <chrono>
# define VEL_SMOOTH_MIN 0.6
# define VEL_SMOOTH_MAX 1.0
# define MINIMUM_DISTANCE_TO_NEXT_POINT 0.002f
BrushTool : : BrushTool ( DrawingProgram & initDrawP ) :
2025-08-02 20:10:33 +03:00
DrawingProgramToolBase ( initDrawP )
{ }
DrawingProgramToolType BrushTool : : get_type ( ) {
return DrawingProgramToolType : : BRUSH ;
2025-07-06 20:43:53 +03:00
}
2025-08-02 20:39:21 +03:00
void BrushTool : : switch_tool ( DrawingProgramToolType newTool ) {
2025-07-06 20:43:53 +03:00
commit_stroke ( ) ;
}
bool BrushTool : : extensive_point_checking ( const Vector2f & newPoint ) {
2025-07-29 14:11:36 +03:00
auto & points = controls . intermediateItem - > d - > points ;
2025-07-06 20:43:53 +03:00
if ( points . size ( ) > = 1 & & ( newPoint - points [ points . size ( ) - 1 ] . pos ) . norm ( ) < MINIMUM_DISTANCE_TO_NEXT_POINT )
return false ;
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 ;
return true ;
}
bool BrushTool : : extensive_point_checking_back ( const Vector2f & newPoint ) {
2025-07-29 14:11:36 +03:00
auto & points = controls . intermediateItem - > d - > points ;
2025-07-06 20:43:53 +03:00
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 ;
}
void BrushTool : : tool_update ( ) {
if ( drawP . controls . cursorHoveringOverCanvas )
drawP . world . main . input . hideCursor = true ;
if ( ! controls . isDrawing )
controls . penSmoothingData . clear ( ) ;
if ( drawP . world . main . input . pen . isDown & & drawP . controls . leftClickHeld & & drawP . world . main . toolbar . tabletOptions . pressureAffectsBrushWidth ) {
while ( ! controls . penSmoothingData . empty ( ) & & ( std : : chrono : : steady_clock : : now ( ) - controls . penSmoothingData . front ( ) . t ) > std : : chrono : : milliseconds ( static_cast < int > ( drawP . world . main . toolbar . tabletOptions . smoothingSamplingTime * 1000.0f ) ) )
controls . penSmoothingData . pop_front ( ) ;
controls . penSmoothingData . emplace_back (
drawP . world . main . input . pen . pressure ,
std : : chrono : : steady_clock : : now ( )
) ;
float averagePressure = 0.0f ;
for ( auto & [ width , t ] : controls . penSmoothingData )
averagePressure + = width ;
averagePressure / = controls . penSmoothingData . size ( ) ;
controls . penWidth = averagePressure ;
2025-11-30 16:19:36 +03:00
if ( controls . penWidth ! = 0.0f ) {
float brushMinSize = drawP . world . main . toolbar . tabletOptions . brushMinimumSize ;
controls . penWidth = brushMinSize + controls . penWidth * ( 1.0f - brushMinSize ) ;
}
2025-07-06 20:43:53 +03:00
}
else
controls . penWidth = 1.0f ;
bool isPenDown = false ;
if ( drawP . world . main . input . pen . isDown & & drawP . controls . leftClickHeld )
isPenDown = controls . penWidth ! = 0.0 ;
else if ( drawP . controls . leftClickHeld )
isPenDown = true ;
if ( isPenDown ) {
if ( ! controls . isDrawing ) {
controls . isDrawing = true ;
controls . intermediateItem = std : : make_shared < DrawBrushStroke > ( ) ;
DrawBrushStrokePoint p ;
p . pos = drawP . world . main . input . mouse . pos ;
p . width = drawP . controls . relativeWidth * controls . penWidth ;
controls . prevPointUnaltered = p . pos ;
2025-07-29 14:11:36 +03:00
controls . intermediateItem - > d - > points . emplace_back ( p ) ;
controls . intermediateItem - > d - > color = drawP . controls . foregroundColor ;
controls . intermediateItem - > d - > hasRoundCaps = controls . hasRoundCaps ;
2025-07-06 20:43:53 +03:00
controls . intermediateItem - > coords = drawP . world . drawData . cam . c ;
controls . intermediateItem - > lastUpdateTime = std : : chrono : : steady_clock : : now ( ) ;
uint64_t placement = drawP . components . client_list ( ) . size ( ) ;
2025-07-27 16:30:45 +03:00
auto objAdd = drawP . components . client_insert ( placement , controls . intermediateItem ) ;
2025-07-31 09:34:17 +03:00
controls . intermediateItem - > commit_update ( drawP ) ;
2025-07-21 12:26:07 +03:00
controls . intermediateItem - > client_send_place ( drawP ) ;
2025-07-06 20:43:53 +03:00
// NOTE: The data isnt finallized at this point, so a BVH isn't generated for an object that you undo while youre drawing (this isnt just for brush strokes)
// The fix for now is to generate a BVH for an object when you redo it
2025-07-27 16:30:45 +03:00
drawP . add_undo_place_component ( objAdd ) ;
2025-07-06 20:43:53 +03:00
controls . addedTemporaryPoint = false ;
}
else {
controls . intermediateItem - > lastUpdateTime = std : : chrono : : steady_clock : : now ( ) ;
DrawBrushStrokePoint p ;
p . pos = controls . intermediateItem - > coords . get_mouse_pos ( drawP . world ) ;
p . width = drawP . controls . relativeWidth * controls . penWidth ;
if ( ! controls . addedTemporaryPoint ) {
if ( extensive_point_checking ( p . pos ) ) {
2025-07-29 14:11:36 +03:00
controls . intermediateItem - > d - > points . emplace_back ( p ) ;
2025-07-06 20:43:53 +03:00
controls . addedTemporaryPoint = true ;
}
else
2025-07-29 14:11:36 +03:00
controls . intermediateItem - > d - > points . back ( ) . width = std : : max ( controls . intermediateItem - > d - > points . back ( ) . width , p . width ) ;
2025-07-06 20:43:53 +03:00
}
if ( controls . addedTemporaryPoint ) {
2025-07-29 14:11:36 +03:00
const DrawBrushStrokePoint & prevP = controls . intermediateItem - > d - > points [ controls . intermediateItem - > d - > points . size ( ) - 2 ] ;
2025-07-06 20:43:53 +03:00
float distToPrev = ( p . pos - prevP . pos ) . norm ( ) ;
if ( extensive_point_checking_back ( p . pos ) ) {
2025-07-29 14:11:36 +03:00
controls . intermediateItem - > d - > points . back ( ) . pos = p . pos ;
controls . intermediateItem - > d - > points . back ( ) . width = std : : max ( controls . intermediateItem - > d - > points . back ( ) . width , p . width ) ;
controls . intermediateItem - > d - > points [ controls . intermediateItem - > d - > points . size ( ) - 2 ] . width = std : : max ( controls . intermediateItem - > d - > points [ controls . intermediateItem - > d - > points . size ( ) - 2 ] . width , p . width ) ;
2025-07-06 20:43:53 +03:00
}
if ( ( ! controls . drawingMinimumRelativeToSize & & distToPrev > = 10.0 ) | | ( controls . drawingMinimumRelativeToSize & & distToPrev > = drawP . controls . relativeWidth * DRAW_MINIMUM_LIMIT ) ) {
2025-07-29 14:11:36 +03:00
controls . intermediateItem - > d - > points . back ( ) = p ;
2025-07-06 20:43:53 +03:00
controls . addedTemporaryPoint = false ;
2025-10-01 08:59:44 +03:00
controls . intermediateItem - > client_send_update ( drawP , false ) ;
2025-07-06 20:43:53 +03:00
if ( controls . midwayInterpolation ) {
2025-07-29 14:11:36 +03:00
if ( controls . intermediateItem - > d - > points . size ( ) ! = 2 ) // Don't interpolate the first point
controls . intermediateItem - > d - > points [ controls . intermediateItem - > d - > points . size ( ) - 2 ] . pos = ( controls . prevPointUnaltered + p . pos ) * 0.5 ;
2025-07-06 20:43:53 +03:00
controls . prevPointUnaltered = p . pos ;
}
}
2025-07-31 09:34:17 +03:00
controls . intermediateItem - > commit_update ( drawP ) ;
2025-07-06 20:43:53 +03:00
}
}
}
else if ( controls . isDrawing ) {
controls . isDrawing = false ;
commit_stroke ( ) ;
}
}
void BrushTool : : gui_toolbox ( ) {
Toolbar & t = drawP . world . main . toolbar ;
t . gui . push_id ( " brush tool " ) ;
t . gui . text_label_centered ( " Brush " ) ;
t . gui . checkbox_field ( " hasroundcaps " , " Round Caps " , & controls . hasRoundCaps ) ;
t . gui . slider_scalar_field ( " relwidth " , " Size " , & drawP . controls . relativeWidth , 3.0f , 40.0f ) ;
t . gui . pop_id ( ) ;
}
2025-12-01 17:33:14 +03:00
bool BrushTool : : right_click_popup_gui ( Vector2f popupPos ) {
Toolbar & t = drawP . world . main . toolbar ;
t . paint_popup ( popupPos ) ;
return true ;
}
2025-07-27 16:53:22 +03:00
bool BrushTool : : prevent_undo_or_redo ( ) {
return controls . intermediateItem ! = nullptr ;
}
2025-07-06 20:43:53 +03:00
void BrushTool : : commit_stroke ( ) {
2025-07-23 17:21:56 +03:00
if ( controls . intermediateItem & & controls . intermediateItem - > collabListInfo . lock ( ) ) {
2025-07-29 14:11:36 +03:00
if ( ! controls . intermediateItem - > d - > points . empty ( ) ) {
2025-10-01 08:59:44 +03:00
controls . intermediateItem - > client_send_update ( drawP , true ) ;
2025-07-31 09:34:17 +03:00
controls . intermediateItem - > commit_update ( drawP ) ;
2025-07-14 17:00:28 +03:00
}
2025-07-06 20:43:53 +03:00
}
2025-07-23 17:21:56 +03:00
controls . intermediateItem = nullptr ;
2025-07-06 20:43:53 +03:00
}
void BrushTool : : draw ( SkCanvas * canvas , const DrawData & drawData ) {
if ( ! drawData . main - > toolbar . io - > hoverObstructed ) {
SkPaint linePaint ;
2025-08-01 10:58:45 +03:00
linePaint . setColor4f ( drawP . world . canvasTheme . toolFrontColor ) ;
2025-07-06 20:43:53 +03:00
linePaint . setStroke ( true ) ;
linePaint . setStrokeWidth ( 0.0f ) ;
float width ;
if ( controls . isDrawing )
width = drawP . controls . relativeWidth * controls . penWidth * 0.5f ;
else
width = drawP . controls . relativeWidth * 0.5f ;
Vector2f pos = drawData . main - > input . mouse . pos ;
canvas - > drawCircle ( pos . x ( ) , pos . y ( ) , width , linePaint ) ;
}
}