2025-07-06 20:43:53 +03:00
# include "DrawCamera.hpp"
2025-12-26 10:40:58 +03:00
# include "DrawingProgram/Tools/DrawingProgramToolBase.hpp"
2025-07-06 20:43:53 +03:00
# include "Helpers/FixedPoint.hpp"
2026-03-05 12:03:10 +03:00
# include "Helpers/MathExtras.hpp"
2025-07-06 20:43:53 +03:00
# include "MainProgram.hpp"
# include "World.hpp"
# include "InputManager.hpp"
2026-03-05 12:03:10 +03:00
# include <Helpers/Logger.hpp>
2025-07-06 20:43:53 +03:00
DrawCamera : : DrawCamera ( ) :
2025-10-02 11:55:50 +03:00
c ( { 0 , 0 } , WorldScalar ( 1000000 ) , 0.0 )
2025-07-06 20:43:53 +03:00
{ }
void DrawCamera : : set_based_on_properties ( World & w , const WorldVec & newPos , const WorldScalar & newZoom , double newRotate ) {
c . inverseScale = newZoom ;
c . pos = newPos ;
c . set_rotation ( newRotate ) ;
set_viewing_area ( w . main . window . size . cast < float > ( ) ) ;
}
void DrawCamera : : set_based_on_center ( World & w , const WorldVec & newPos , const WorldScalar & newZoom , double newRotate ) {
c . inverseScale = newZoom ;
c . set_rotation ( newRotate ) ;
c . pos = newPos - c . dir_from_space ( w . main . window . size . cast < float > ( ) * 0.5f ) ;
set_viewing_area ( w . main . window . size . cast < float > ( ) ) ;
}
void DrawCamera : : set_viewing_area ( Vector2f viewingAreaNew ) {
if ( viewingArea ! = viewingAreaNew ) {
viewingArea = viewingAreaNew ;
}
float maxDim = std : : max ( viewingAreaNew . x ( ) , viewingAreaNew . y ( ) ) ;
2025-07-14 17:09:34 +03:00
WorldScalar a = WorldScalar ( maxDim * 0.708f ) * c . inverseScale ;
2025-07-06 20:43:53 +03:00
WorldVec center = c . from_space ( viewingAreaNew * 0.5f ) ;
viewingAreaGenerousCollider = SCollision : : AABB < WorldScalar > ( center - WorldVec { a , a } , center + WorldVec { a , a } ) ;
}
void DrawCamera : : smooth_move_to ( World & w , const CoordSpaceHelper & newCoords , Vector2f windowSize , bool instantJump ) {
smoothMove . start = c ;
smoothMove . end = newCoords ;
smoothMove . endWindowSize = windowSize ;
smoothMove . startCenter = c . pos + c . dir_from_space ( w . main . window . size . cast < float > ( ) * 0.5f ) ;
smoothMove . endCenter = newCoords . pos + newCoords . dir_from_space ( windowSize * 0.5f ) ;
float a1 ( w . main . window . size . x ( ) / windowSize . x ( ) ) ;
float a2 ( w . main . window . size . y ( ) / windowSize . y ( ) ) ;
smoothMove . endUniformZoom = std : : max ( smoothMove . end . inverseScale . multiply_double ( ( a1 < a2 ) ? a1 : a2 ) , WorldScalar ( 1 ) ) ;
smoothMove . occurring = true ;
2026-03-31 13:17:58 +03:00
smoothMove . moveTime = ( instantJump | | w . main . conf . jumpTransitionTime < = 0.01f ) ? w . main . conf . jumpTransitionTime : 0.0f ;
2025-07-06 20:43:53 +03:00
}
2025-09-30 11:45:03 +03:00
void DrawCamera : : scale_up ( World & w , const WorldScalar & scaleUpAmount ) {
smoothMove . occurring = false ;
c . scale_about ( { 0 , 0 } , scaleUpAmount , true ) ;
startZoomVal * = scaleUpAmount ;
startZoomMousePos * = scaleUpAmount ;
startZoomCameraPos * = scaleUpAmount ;
2026-03-06 14:12:13 +03:00
touchInitialC . scale_about ( { 0 , 0 } , scaleUpAmount , true ) ;
2025-09-30 11:45:03 +03:00
set_viewing_area ( w . main . window . size . cast < float > ( ) ) ;
}
2025-07-06 20:43:53 +03:00
void DrawCamera : : update_main ( World & w ) {
if ( smoothMove . occurring ) {
2026-03-31 13:17:58 +03:00
BezierEasing zoomAnim { w . main . conf . jumpTransitionEasing } ;
float smoothTime = smooth_two_way_time ( smoothMove . moveTime , w . main . deltaTime , true , w . main . conf . jumpTransitionTime ) ;
2025-07-06 20:43:53 +03:00
float lerpTime ;
float rotationLerpTime = zoomAnim ( smoothTime ) ;
lerpTime = zoomAnim ( smoothTime ) ;
WorldVec mVec = smoothMove . startCenter - smoothMove . endCenter ;
WorldScalar startLog2 = FixedPoint : : log2 ( smoothMove . start . inverseScale ) ;
WorldScalar endLog2 = FixedPoint : : log2 ( smoothMove . endUniformZoom ) ;
WorldVec windowCenter = smoothMove . endCenter ;
if ( FixedPoint : : abs ( startLog2 - endLog2 ) < WorldScalar ( 3 ) ) {
c . inverseScale = FixedPoint : : lerp_double ( smoothMove . start . inverseScale , smoothMove . endUniformZoom , lerpTime ) ;
if ( lerpTime ! = 1.0 )
windowCenter + = WorldVec { mVec . x ( ) / WorldScalar ( 1.0 / ( 1.0 - lerpTime ) ) , mVec . y ( ) / WorldScalar ( 1.0 / ( 1.0 - lerpTime ) ) } ;
c . pos = windowCenter - ( w . main . window . size . cast < float > ( ) * 0.5f ) . cast < WorldScalar > ( ) * c . inverseScale ;
}
else {
c . inverseScale = FixedPoint : : exp2 ( FixedPoint : : lerp_double ( startLog2 , endLog2 , lerpTime ) ) ;
if ( smoothMove . start . inverseScale < smoothMove . endUniformZoom ) {
if ( smoothMove . start . inverseScale > c . inverseScale )
c . inverseScale = smoothMove . start . inverseScale ;
else if ( smoothMove . endUniformZoom < c . inverseScale )
c . inverseScale = smoothMove . endUniformZoom ;
}
else {
if ( smoothMove . start . inverseScale < c . inverseScale )
c . inverseScale = smoothMove . start . inverseScale ;
else if ( smoothMove . endUniformZoom > c . inverseScale )
c . inverseScale = smoothMove . endUniformZoom ;
}
WorldScalar denominator = ( smoothMove . endUniformZoom - smoothMove . start . inverseScale ) ;
windowCenter + = WorldVec { mVec . x ( ) - ( ( mVec . x ( ) * c . inverseScale ) / denominator ) + ( ( mVec . x ( ) * smoothMove . start . inverseScale ) / denominator ) ,
mVec . y ( ) - ( ( mVec . y ( ) * c . inverseScale ) / denominator ) + ( ( mVec . y ( ) * smoothMove . start . inverseScale ) / denominator ) } ;
c . pos = windowCenter - ( w . main . window . size . cast < float > ( ) * 0.5f ) . cast < WorldScalar > ( ) * c . inverseScale ;
}
c . set_rotation ( 0.0 ) ;
double midRotate = Rotation2D ( smoothMove . start . rotation ) . slerp ( rotationLerpTime , Rotation2D ( smoothMove . end . rotation ) ) . smallestPositiveAngle ( ) ;
c . rotate_about ( windowCenter , midRotate ) ;
if ( smoothTime > = 1.0f ) {
c . inverseScale = smoothMove . endUniformZoom ;
c . pos = smoothMove . endCenter - ( w . main . window . size . cast < float > ( ) * 0.5f ) . cast < WorldScalar > ( ) * c . inverseScale ;
c . set_rotation ( 0.0 ) ;
c . rotate_about ( smoothMove . endCenter , smoothMove . end . rotation ) ;
smoothMove . occurring = false ;
}
}
else {
2026-03-03 22:31:48 +03:00
if ( w . main . input . key ( InputManager : : KEY_CAMERA_ROTATE_COUNTERCLOCKWISE ) . held & & ! w . main . input . text . is_accepting_input ( ) ) {
2026-03-02 23:09:48 +03:00
c . rotate_about ( c . from_space ( w . main . window . size . cast < float > ( ) * 0.5f ) , - w . main . deltaTime ) ;
2026-03-03 22:31:48 +03:00
InputManager : : MouseMotionCallbackArgs motion {
. pos = w . main . input . mouse . pos ,
. move = { 0 , 0 }
} ;
w . drawProg . drawTool - > input_mouse_motion_callback ( motion ) ;
2026-04-02 10:15:22 +03:00
w . main . g . gui . set_to_layout ( ) ;
2026-03-03 22:31:48 +03:00
}
if ( w . main . input . key ( InputManager : : KEY_CAMERA_ROTATE_CLOCKWISE ) . held & & ! w . main . input . text . is_accepting_input ( ) ) {
2026-03-02 23:09:48 +03:00
c . rotate_about ( c . from_space ( w . main . window . size . cast < float > ( ) * 0.5f ) , w . main . deltaTime ) ;
2026-03-03 22:31:48 +03:00
InputManager : : MouseMotionCallbackArgs motion {
. pos = w . main . input . mouse . pos ,
. move = { 0 , 0 }
} ;
w . drawProg . drawTool - > input_mouse_motion_callback ( motion ) ;
2026-04-02 10:15:22 +03:00
w . main . g . gui . set_to_layout ( ) ;
2026-03-03 22:31:48 +03:00
}
2026-03-02 23:09:48 +03:00
}
set_viewing_area ( w . main . window . size . cast < float > ( ) ) ;
if ( c . inverseScale < WorldScalar ( 1 ) )
w . scale_up_step ( ) ;
}
void DrawCamera : : input_key_callback ( const InputManager : : KeyCallbackArgs & key ) {
}
void DrawCamera : : input_mouse_button_on_canvas_callback ( World & w , const InputManager : : MouseButtonCallbackArgs & button ) {
2026-03-31 18:41:19 +03:00
if ( ! smoothMove . occurring & & ! isTouchTransforming & & ! w . main . g . gui . cursor_obstructed ( ) ) {
2026-03-31 13:17:58 +03:00
bool newIsAccurateZooming = ( w . drawProg . controls . middleClickHeld & & w . main . input . pen . isDown & & w . main . conf . tabletOptions . zoomWhilePenDownAndButtonHeld ) | | // Hold middle click (pen button assigned to middle click) while pen is down
2026-01-16 17:42:00 +03:00
( w . drawProg . controls . middleClickHeld & & w . main . input . key ( InputManager : : KEY_GENERIC_LCTRL ) . held ) | | // Hold middle click/pen button while holding control
( w . drawProg . controls . leftClickHeld & & w . drawProg . drawTool - > get_type ( ) = = DrawingProgramToolType : : ZOOM ) ; // Hold left click while on zoom tool
2025-09-28 11:59:08 +03:00
if ( newIsAccurateZooming & & ! isAccurateZooming ) {
2026-03-02 23:09:48 +03:00
startZoomMousePos = c . from_space ( button . pos ) ;
2025-07-06 20:43:53 +03:00
startZoomVal = c . inverseScale ;
startZoomCameraPos = c . pos ;
}
2025-09-28 11:59:08 +03:00
isAccurateZooming = newIsAccurateZooming ;
2026-03-02 23:09:48 +03:00
}
else
isAccurateZooming = false ;
}
void DrawCamera : : input_mouse_motion_callback ( World & w , const InputManager : : MouseMotionCallbackArgs & motion ) {
2026-03-05 12:03:10 +03:00
if ( ! smoothMove . occurring & & ! isTouchTransforming ) {
2025-10-04 09:10:09 +03:00
if ( isAccurateZooming & & startZoomVal ! = WorldScalar ( 0 ) ) {
2026-03-31 13:17:58 +03:00
WorldScalar zoomFactor ( std : : pow ( 1.0 + w . main . conf . dragZoomSpeed , w . main . conf . flipZoomToolDirection ? motion . move . y ( ) : - motion . move . y ( ) ) ) ;
2025-10-25 17:31:55 +03:00
if ( zoomFactor < WorldScalar ( 0.000001 ) )
zoomFactor = WorldScalar ( 0.000001 ) ;
2025-07-06 20:43:53 +03:00
c . scale ( zoomFactor ) ;
2025-10-04 09:25:44 +03:00
if ( c . inverseScale < WorldScalar ( 0.0001 ) )
c . inverseScale = WorldScalar ( 0.0001 ) ;
else {
WorldVec mVec = startZoomCameraPos - startZoomMousePos ;
WorldScalar mX = static_cast < WorldScalar > ( WorldMultiplier ( c . inverseScale ) / WorldMultiplier ( startZoomVal ) ) ;
c . pos = startZoomMousePos + mVec * mX ;
}
2025-07-06 20:43:53 +03:00
}
2025-09-28 15:00:19 +03:00
else if ( w . drawProg . controls . middleClickHeld | | ( w . drawProg . controls . leftClickHeld & & w . drawProg . drawTool - > get_type ( ) = = DrawingProgramToolType : : PAN ) )
2026-03-02 23:09:48 +03:00
c . pos - = c . dir_from_space ( motion . move ) ;
}
}
2025-07-06 20:43:53 +03:00
2026-03-02 23:09:48 +03:00
void DrawCamera : : input_mouse_wheel_callback ( World & w , const InputManager : : MouseWheelCallbackArgs & wheel ) {
2026-03-31 18:41:19 +03:00
if ( ! smoothMove . occurring & & ! isTouchTransforming & & wheel . tickAmount . y ( ) & & ! w . main . g . gui . cursor_obstructed ( ) ) {
2026-03-02 23:09:48 +03:00
WorldVec mouseWorldPos = c . from_space ( wheel . mousePos ) ;
2026-03-31 13:17:58 +03:00
WorldScalar zoomFactor ( 1.0 + w . main . conf . scrollZoomSpeed ) ;
2025-10-25 17:31:55 +03:00
2026-03-02 23:09:48 +03:00
if ( zoomFactor < WorldScalar ( 0.000001 ) )
zoomFactor = WorldScalar ( 0.000001 ) ;
2025-10-25 17:31:55 +03:00
2026-03-02 23:09:48 +03:00
if ( zoomFactor ! = WorldScalar ( 0 ) ) {
if ( wheel . tickAmount . y ( ) < 0.0 )
zoomFactor = WorldScalar ( 1 ) / zoomFactor ;
2025-10-04 09:29:29 +03:00
2026-03-02 23:09:48 +03:00
for ( int i = 0 ; i < std : : abs ( wheel . tickAmount . y ( ) ) ; i + + ) {
2025-10-04 09:29:29 +03:00
c . scale ( zoomFactor ) ;
if ( c . inverseScale < WorldScalar ( 0.0001 ) )
c . inverseScale = WorldScalar ( 0.0001 ) ;
else {
2026-03-02 23:09:48 +03:00
WorldVec mVec = c . pos - mouseWorldPos ;
c . pos = mouseWorldPos + mVec / zoomFactor ;
2025-10-04 09:29:29 +03:00
}
2025-10-04 09:25:44 +03:00
}
2025-07-06 20:43:53 +03:00
}
}
}
2026-01-01 22:46:02 +03:00
2026-03-05 21:09:30 +03:00
void DrawCamera : : input_multi_finger_touch_callback ( World & w , const InputManager : : MultiFingerTouchCallbackArgs & touch ) {
2026-03-05 12:03:10 +03:00
if ( ! smoothMove . occurring & & ! isAccurateZooming & & ! isTouchTransforming & & touch . down ) {
touchInitialPositions = touch . pos ;
touchInitialC = c ;
isTouchTransforming = true ;
}
else
isTouchTransforming = false ;
}
2026-03-05 21:09:30 +03:00
void DrawCamera : : input_multi_finger_motion_callback ( World & w , const InputManager : : MultiFingerMotionCallbackArgs & motion ) {
2026-03-05 12:03:10 +03:00
if ( ! smoothMove . occurring & & ! isAccurateZooming & & isTouchTransforming ) {
c = touchInitialC ;
Vector2f initialCenter = ( touchInitialPositions [ 0 ] + touchInitialPositions [ 1 ] ) * 0.5f ;
Vector2f newCenter = ( motion . pos [ 0 ] + motion . pos [ 1 ] ) * 0.5f ;
c . pos - = c . dir_from_space ( newCenter - initialCenter ) ;
WorldVec newCenterWorld = c . from_space ( newCenter ) ;
float initialDistance = vec_distance ( touchInitialPositions [ 0 ] , touchInitialPositions [ 1 ] ) ;
float newDistance = vec_distance ( motion . pos [ 0 ] , motion . pos [ 1 ] ) ;
float scaleAmount = newDistance / initialDistance ;
c . scale_about_double ( newCenterWorld , scaleAmount ) ;
Vector2f initialDiff = touchInitialPositions [ 0 ] - initialCenter ;
float initialAngle = std : : atan2 ( initialDiff . y ( ) , initialDiff . x ( ) ) + std : : numbers : : pi ;
Vector2f newDiff = motion . pos [ 0 ] - newCenter ;
float newAngle = std : : atan2 ( newDiff . y ( ) , newDiff . x ( ) ) + std : : numbers : : pi ;
float rotateAngle = initialAngle - newAngle ;
rotateAngle = std : : fmod ( rotateAngle + std : : numbers : : pi , std : : numbers : : pi * 2.0f ) - std : : numbers : : pi ;
c . rotate_about ( newCenterWorld , rotateAngle ) ;
}
}
2026-01-01 22:46:02 +03:00
void DrawCamera : : save_file ( cereal : : PortableBinaryOutputArchive & a , const World & w ) const {
a ( c , w . main . window . size . cast < float > ( ) . eval ( ) ) ;
}
void DrawCamera : : load_file ( cereal : : PortableBinaryInputArchive & a , VersionNumber version , World & w ) {
CoordSpaceHelper coordsToJumpTo ;
Vector2f windowSizeToJumpTo ;
a ( coordsToJumpTo , windowSizeToJumpTo ) ;
smooth_move_to ( w , coordsToJumpTo , windowSizeToJumpTo , true ) ;
}