2025-07-06 20:43:53 +03:00
# include "BrushTool.hpp"
# include <Helpers/ConvertVec.hpp>
# include "../TimePoint.hpp"
# include "../DrawComponents/DrawBrushStroke.hpp"
# include "../GUIStuff/GUIManager.hpp"
# include "../SharedTypes.hpp"
# include "../Server/CommandList.hpp"
# include "DrawingProgram.hpp"
# include "../MainProgram.hpp"
# 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 ) :
drawP ( initDrawP )
{
}
void BrushTool : : reset_tool ( ) {
commit_stroke ( ) ;
controls . isDrawing = false ;
}
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 ( ) ;
float averageScreenSize = ( drawP . world . main . window . size . x ( ) + drawP . world . main . window . size . y ( ) ) / 2.0f ;
float minVel = 0.2f * averageScreenSize ;
float maxVel = 3.5f * averageScreenSize ;
while ( ! controls . velocitySmoothingData . empty ( ) & & ( std : : chrono : : steady_clock : : now ( ) - controls . velocitySmoothingData . front ( ) . t ) > std : : chrono : : milliseconds ( 200 ) )
controls . velocitySmoothingData . pop_front ( ) ;
controls . velocitySmoothingData . emplace_back (
drawP . world . main . input . mouse . move . norm ( ) / drawP . world . main . deltaTime ,
std : : chrono : : steady_clock : : now ( )
) ;
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 ;
}
else if ( drawP . world . main . toolbar . velocityAffectsBrushWidth ) {
float averageVel = 0.0f ;
for ( auto & [ width , t ] : controls . velocitySmoothingData )
averageVel + = width ;
averageVel / = controls . velocitySmoothingData . size ( ) ;
controls . penWidth = std : : clamp < float > ( ( averageVel - minVel ) / ( maxVel - minVel ) , VEL_SMOOTH_MIN , VEL_SMOOTH_MAX ) ;
}
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-07-31 09:34:17 +03:00
controls . intermediateItem - > client_send_update ( drawP ) ;
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-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-07-31 09:34:17 +03:00
controls . intermediateItem - > client_send_update ( drawP ) ;
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 ) ;
}
}