- spline moves using math functions (Lerp, BezierPoint, BezierInterpolate)
- Pick path : point, angle, tangent
- Get path length

Code: Select all
;Refs: http://stackoverflow.com/questions/4089443/find-the-tangent-of-a-point-on-a-cubic-bezier-curve-on-an-iphone
;Refs: http://stackoverflow.com/questions/2898089/c-sharp-drawing-arc-with-3-points
;Refs: http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order
DeclareModule PathMoves
Global.d _PathX, _PathY, _PathAngle, _PathCenterX, _PathCenterY, _PathRadiusX, _PathRadiusY, _PathEndAngle, _PathStartAngle
Macro PickPathX() : _PathX : EndMacro
Macro PickPathY() : _PathY : EndMacro
Macro PickPathAngle() : _PathAngle : EndMacro
Macro PickPathTangentX() : Cos(_PathAngle) : EndMacro
Macro PickPathTangentY() : Sin(_PathAngle) : EndMacro
Macro GetPathCenterX() : _PathCenterX : EndMacro
Macro GetPathCenterY() : _PathCenterY : EndMacro
Macro GetPathRadius() : _PathRadiusX : EndMacro
Macro GetPathRadiusX() : _PathRadiusX : EndMacro
Macro GetPathRadiusY() : _PathRadiusY : EndMacro
Macro GetPathEndAngle() : _PathEndAngle : EndMacro
Macro GetPathStartAngle() : _PathStartAngle : EndMacro
Declare.d LoopFraction(DurationMilliseconds.d=1000)
Declare.d ClampFraction(Fraction.d)
Declare.d Lerp(p1.d, p2.d, Fraction.d)
Declare.d BezierPoint(p0.d, p1.d, p2.d, p3.d, Fraction.d)
Declare.d BezierTangent(p0.d, p1.d, p2.d, p3.d, Fraction.d)
Declare.d BezierInterpolate(p0.d, p1.d, p2.d, p3.d, Fraction.d)
Declare PickPathLine(Fraction.d, x1.d, y1.d, x2.d, y2.d, PickPathTangent=#True)
Declare PickPathCurve(Fraction.d, x0.d, y0.d, x1.d, y1.d, x2.d, y2.d, x3.d, y3.d, PickPathTangent=#True)
Declare PickPathCircle(Fraction.d, x.d, y.d, Radius.d, StartAngle.d=0, EndAngle.d=360, IsCounterClockwise=#False, PickPathTangent=#True)
Declare PickPathEllipse(Fraction.d, x.d, y.d, RadiusX.d, RadiusY.d, StartAngle.d=0, EndAngle.d=360, IsCounterClockwise=#False, PickPathTangent=#True)
Declare.d PathCurveLength(x0.d, y0.d, x1.d, y1.d, x2.d, y2.d, x3.d, y3.d, PrecisionSteps=256)
Declare.d PathLineLength(x1.d, y1.d, x2.d, y2.d)
Declare.d PathCircleLength(Radius.d, StartAngle.d=0, EndAngle.d=360, IsCounterClockwise=#False)
Declare.d PathEllipseLength(RadiusX.d, RadiusY.d, StartAngle.d=0, EndAngle.d=360, IsCounterClockwise=#False, PrecisionSteps=256)
Declare.d PathSweepAngle(StartAngle.d=0, EndAngle.d=360, IsCounterClockwise=#False)
Declare.i ConvertPointsToPathCircle(x0.d, y0.d, x1.d, y1.d, x2.d, y2.d)
Declare.i ConvertPointsToPathEllipse(x0.d, y0.d, x1.d, y1.d, x2.d, y2.d)
Declare.i IsPolygonClockwise(x0.d, y0.d, x1.d, y1.d, x2.d, y2.d, x3.d, y3.d)
EndDeclareModule
Module PathMoves
EnableExplicit
Procedure.i IsPolygonClockwise(x0.d, y0.d, x1.d, y1.d, x2.d, y2.d, x3.d, y3.d)
Protected.d sumlEdges=(x1-x0)*(y1 + y0) + (x2-x1)*(y2 + y1) + (x3-x2)*(y3 + y2) + (x0-x3)*(y0 + y3)
If sumlEdges<0 : ProcedureReturn #True : EndIf
EndProcedure
Procedure.d LoopFraction(DurationMilliseconds.d=1000)
;looped value [0..1]
ProcedureReturn Mod(ElapsedMilliseconds() / DurationMilliseconds, 1.0)
EndProcedure
Procedure.d ClampFraction(Fraction.d)
;clamped value [0..1]
If Fraction<0 : Fraction=0 : EndIf
If Fraction>1 : Fraction=1 : EndIf
ProcedureReturn Fraction
EndProcedure
Procedure.d Lerp(p1.d, p2.d, Fraction.d)
ProcedureReturn p1 + (p2-p1)*Fraction
EndProcedure
Procedure.d BezierInterpolate(p0.d, p1.d, p2.d, p3.d, Fraction.d)
Protected.d pa, pb, pc, l, r
pa=Lerp(p0, p1, Fraction)
pb=Lerp(p1, p2, Fraction)
pc=Lerp(p2, p3, Fraction)
l=Lerp(pa, pb, Fraction) ;left
r=Lerp(pb, pc, Fraction) ;right
ProcedureReturn Lerp(l, r, Fraction)
EndProcedure
Procedure.d BezierPoint(p0.d, p1.d, p2.d, p3.d, Fraction.d)
Protected.d C1, C2, C3, C4
C1=(p3 - (3.0 * p2) + (3.0 * p1) - p0)
C2=((3.0 * p2) - (6.0 * p1) + (3.0 * p0))
C3=((3.0 * p1) - (3.0 * p0))
C4=(p0)
ProcedureReturn (C1*Pow(Fraction, 3) + C2*Pow(Fraction, 2) + C3*Fraction + C4)
EndProcedure
Procedure.d BezierTangent(p0.d, p1.d, p2.d, p3.d, Fraction.d)
Protected.d C1, C2, C3, C4
C1=(p3 - (3.0 * p2) + (3.0 * p1) - p0)
C2=((3.0 * p2) - (6.0 * p1) + (3.0 * p0))
C3=((3.0 * p1) - (3.0 * p0))
ProcedureReturn ((3.0 * C1 * Pow(Fraction, 2)) + (2.0 * C2 * Fraction) + C3) ;derivative of BezierPoint polynomial
EndProcedure
; Procedure CreateTrackPath()
; EndProcedure
;
; Procedure EndTrackPath()
; EndProcedure
;
; Procedure TrackPathLine(x.d, y.d)
; EndProcedure
;
; Procedure TrackPathCurve(x1.d, y1.d, x2.d, y2.d, x3.d, y3.d)
; EndProcedure
;
; Procedure TrackPathCircle(x.d, y.d, Radius.d, StartAngle.d=0, EndAngle.d=360, IsCounterClockwise=#False)
; EndProcedure
Procedure PickPathLine(Fraction.d, x1.d, y1.d, x2.d, y2.d, PickPathTangent=#True)
_PathX=Lerp(x1, x2, Fraction)
_PathY=Lerp(y1, y2, Fraction)
If PickPathTangent : _PathAngle=ATan2(x2-x1, y2-y1) : EndIf
EndProcedure
Procedure PickPathCurve(Fraction.d, x0.d, y0.d, x1.d, y1.d, x2.d, y2.d, x3.d, y3.d, PickPathTangent=#True)
_PathX=BezierPoint(x0, x1, x2, x3, Fraction)
_PathY=BezierPoint(y0, y1, y2, y3, Fraction)
If PickPathTangent : _PathAngle=ATan2(BezierTangent(x0, x1, x2, x3, Fraction), BezierTangent(y0, y1, y2, y3, Fraction)) : EndIf
EndProcedure
Procedure PickPathCircle(Fraction.d, x.d, y.d, Radius.d, StartAngle.d=0, EndAngle.d=360, IsCounterClockwise=#False, PickPathTangent=#True)
Protected.d sweepAngle=PathSweepAngle(StartAngle, EndAngle, IsCounterClockwise), da=Radian(StartAngle + sweepAngle*Fraction)
_PathX=x + Radius*Cos(da)
_PathY=y + Radius*Sin(da)
If PickPathTangent : _PathAngle=da + Sign(sweepAngle)*#PI / 2 : EndIf
EndProcedure
Procedure PickPathEllipse(Fraction.d, x.d, y.d, RadiusX.d, RadiusY.d, StartAngle.d=0, EndAngle.d=360, IsCounterClockwise=#False, PickPathTangent=#True)
Protected.d sweepAngle=PathSweepAngle(StartAngle, EndAngle, IsCounterClockwise), da=ATan2(RadiusY, RadiusX*Tan(Radian(StartAngle + sweepAngle*Fraction)))
_PathX=x + RadiusX*Cos(da)
_PathY=y + RadiusY*Sin(da)
If PickPathTangent : _PathAngle=ATan2(-RadiusX*Sin(da),RadiusY*Cos(da)) : EndIf
EndProcedure
Procedure.d PathCurveLength(x0.d, y0.d, x1.d, y1.d, x2.d, y2.d, x3.d, y3.d, PrecisionSteps=256)
Protected.d dist=0, sx=x0, sy=y0, px, py, dx, dy, Fraction
Protected i
For i=1 To PrecisionSteps
Fraction=i / PrecisionSteps ;precision of calculation
px=BezierPoint(x0, x1, x2, x3, Fraction) : dx=px-sx : sx=px
py=BezierPoint(y0, y1, y2, y3, Fraction) : dy=py-sy : sy=py
dist + Sqr(dx*dx + dy*dy)
Next
ProcedureReturn dist
EndProcedure
Procedure.d PathLineLength(x1.d, y1.d, x2.d, y2.d)
Protected.d dx=(x2-x1), dy=(y2-y1)
ProcedureReturn Sqr(dx*dx + dy*dy)
EndProcedure
Procedure.d PathCircleLength(Radius.d, StartAngle.d=0, EndAngle.d=360, IsCounterClockwise=#False)
Protected.d perimeter=2 * #PI * Radius
ProcedureReturn perimeter * Abs(PathSweepAngle(StartAngle, EndAngle, IsCounterClockwise) / 360)
EndProcedure
Procedure.d PathEllipseLength(RadiusX.d, RadiusY.d, StartAngle.d=0, EndAngle.d=360, IsCounterClockwise=#False, PrecisionSteps=256)
Protected.d dist=0, sx, sy, px, py, dx, dy, Fraction, sweepAngle=PathSweepAngle(StartAngle, EndAngle, IsCounterClockwise)
Protected i
For i=0 To PrecisionSteps
Fraction=i / PrecisionSteps ;precision of calculation
Protected.d da=ATan2(RadiusY, RadiusX*Tan(Radian(StartAngle + sweepAngle * Fraction)))
px=RadiusX*Cos(da) : dx=px-sx : sx=px
py=RadiusY*Sin(da) : dy=py-sy : sy=py
If i>0 : dist + Sqr(dx*dx + dy*dy) : EndIf
Next
ProcedureReturn dist
EndProcedure
Procedure.d PathSweepAngle(StartAngle.d=0, EndAngle.d=360, IsCounterClockwise=#False)
StartAngle=Mod(StartAngle, 360) + 360*Bool(StartAngle<0)
EndAngle=Mod(EndAngle, 360) + 360*Bool(EndAngle<0)
Protected SweepAngle.d=EndAngle - StartAngle
If EndAngle<=StartAngle : SweepAngle + 360 : EndIf
If IsCounterClockwise : SweepAngle - 360 : EndIf
ProcedureReturn SweepAngle
EndProcedure
Procedure.i ConvertPointsToPathCircle(x0.d, y0.d, x1.d, y1.d, x2.d, y2.d)
Protected.d d, m1, m2, nx, ny, dx, dy
d=2 * (x0 - x2) * (y2 - y1) + 2 * (x1 - x2) * (y0 - y2)
If d=0 : ProcedureReturn #False : EndIf ;convertion failed if all 3 points are aligned
m1=(Pow(x0, 2) - Pow(x2, 2) + Pow(y0, 2) - Pow(y2, 2))
m2=(Pow(x2, 2) - Pow(x1, 2) + Pow(y2, 2) - Pow(y1, 2))
nx=m1 * (y2 - y1) + m2 * (y2 - y0)
ny=m1 * (x1 - x2) + m2 * (x0 - x2)
_PathCenterX=nx / d
_PathCenterY=ny / d
dx=x0-_PathCenterX : dy=y0-_PathCenterY : _PathStartAngle=Degree(ATan2(dx, dy))
dx=x2-_PathCenterX : dy=y2-_PathCenterY : _PathEndAngle=Degree(ATan2(dx, dy))
_PathRadiusX=Sqr(dx * dx + dy * dy)
_PathRadiusY=_PathRadiusX
Protected IsCounterClockwise=Bool(Not IsPolygonClockwise(x0, y0, x1, y1, x2, y2, x2, y2))
ProcedureReturn IsCounterClockwise
EndProcedure
Procedure.i ConvertPointsToPathEllipse(x0.d, y0.d, x1.d, y1.d, x2.d, y2.d)
_PathCenterX=(x0 + x2) / 2
_PathCenterY=(y0 + y2) / 2
_PathRadiusX=Sqr(Pow(x0-_PathCenterX, 2) + Pow(y0-_PathCenterY, 2))
Protected a0.f=ATan2(x0-_PathCenterX, y0-_PathCenterY)
Protected a2.f=ATan2(x2-_PathCenterX, y2-_PathCenterY)
_PathStartAngle=Degree(a0)
_PathEndAngle=Degree(a2)
Debug ""+_PathStartAngle + " " + _PathEndAngle
Protected IsCounterClockwise=Bool(Not IsPolygonClockwise(x0, y0, x1, y1, x2, y2, x2, y2))
ProcedureReturn IsCounterClockwise
EndProcedure
EndModule
CompilerIf #PB_Compiler_IsMainFile
; ********************
; EXAMPLE
; ********************
UseModule PathMoves
Procedure DrawCursor()
SaveVectorState()
RotateCoordinates(PickPathX(), PickPathY(), Degree(PickPathAngle()))
AddPathCircle(PickPathX(), PickPathY(), 6, 30, 330)
AddPathCircle(PickPathX() + 10, PickPathY(), 3, 270, 90, #PB_Path_Connected)
ClosePath()
VectorSourceColor(RGBA(255, 255, 255, 255))
StrokePath(5, #PB_Path_Preserve)
FillPath(#PB_Path_Preserve)
VectorSourceColor(RGBA(155, 100, 250, 255))
StrokePath(3)
RestoreVectorState()
EndProcedure
Procedure DrawCurves()
Protected.d offset=-ElapsedMilliseconds() / 1000
Protected.d fraction=LoopFraction(5000) ;loop between 0 and 1 in 5 seconds
If StartVectorDrawing(CanvasVectorOutput(0))
; --------------------------------------------
; enable 2X zoom
ScaleCoordinates(2, 2)
Protected.i mx=WindowMouseX(0) / 2
Protected.i my=WindowMouseY(0) / 2
; --------------------------------------------
; draw white background
VectorSourceColor(RGBA(255, 255, 255, 255))
FillVectorOutput()
; --------------------------------------------
; draw shaped path
MovePathCursor(55, 25)
AddPathLine(5, 105)
AddPathCurve(200, 120, -60, 230, 190, 170)
Protected.i IsCounterClockwise=ConvertPointsToPathCircle(190, 170, mx, my, 55, 25)
Protected.d cx=GetPathCenterX(), cy=GetPathCenterY(), r=GetPathRadius(), a1=GetPathStartAngle(), a2=GetPathEndAngle()
AddPathCircle(cx, cy, r, a1, a2, #PB_Path_Connected | #PB_Path_CounterClockwise*IsCounterClockwise)
VectorSourceColor(RGBA(255, 100, 50, 255))
Dim pattern.d(3) : pattern(0)=25 : pattern(1)=10 : pattern(2)=0 : pattern(3)=10
CustomDashPath(5, pattern(), #PB_Path_RoundCorner | #PB_Path_RoundEnd, 20.0*offset)
; --------------------------------------------
; draw center and anchor of circle arc
AddPathCircle(mx, my, 4)
AddPathCircle(cx, cy, 3)
VectorSourceColor(RGBA(55, 10, 0, 255))
StrokePath(3)
; --------------------------------------------
; draw ellipse arc
AddPathEllipse(50, 100, 130, 70, -45, 45)
VectorSourceColor(RGBA(15, 110, 120, 255))
StrokePath(3)
; --------------------------------------------
; draw curve pointer
PickPathCircle(fraction, cx, cy, r, a1, a2, IsCounterClockwise)
DrawCursor()
PickPathCurve(fraction, 5, 105, 200, 120, -60, 230, 190, 170)
DrawCursor()
PickPathLine(fraction, 55, 25, 5, 105)
DrawCursor()
PickPathEllipse(fraction, 50, 100, 130, 70, -45, 45)
DrawCursor()
StopVectorDrawing()
EndIf
EndProcedure
If OpenWindow(0, 0, 0, 500, 500, "Animated Vector Drawing", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
CanvasGadget(0, 0, 0, WindowWidth(0), WindowHeight(0))
AddWindowTimer(0, 100, 1000 / 30)
BindEvent(#PB_Event_Timer, @DrawCurves())
Repeat: Until WaitWindowEvent()=#PB_Event_CloseWindow
EndIf
CompilerEndIf