Jump to content


Photo

Mouse Splines (Cubic Bézier Path)


  • Please log in to reply
7 replies to this topic

#1 VxE

VxE
  • Fellows
  • 3511 posts

Posted 07 August 2010 - 08:36 PM

Bezier_MouseMove( px1, py1, px2, py2, px3, py3, Segments=5, Rel=0, Speed=2 ) { ; -------------------
; Function by [VxE]. Successively moves the mouse from its current position to the point (px3,py3)
; through (N-1) intermediate points which lie on the cubic bezier curve described by the points
; (mx,my), (px1,py1), (px2,py2) and (px3,py3). 'Rel' should be 'true' if the points are relative to
; the mouse's current position. 'Speed' is used directly as the 'Speed' parameter of MouseMove.
; More info about Bézier curves @ http://en.wikipedia.org/wiki/B%C3%A9zier_curve
; Code Source: http://www.autohotkey.com/forum/viewtopic.php?p=374641#374641
	MouseGetPos, px0, py0
	If Rel
		px1 += px0, px2 += px0, px3 += px0, py1 += py0, py2 += py0, py3 += py0
	Loop % Segments - 1
	{
		u := 1 - t := A_Index / Segments
		bx := Round( px0 * u**3 + 3 * px1 * t * u**2 + 3 * px2 * u * t**2 + px3 * t**3 )
		by := Round( py0 * u**3 + 3 * py1 * t * u**2 + 3 * py2 * u * t**2 + py3 * t**3 )
		MouseMove, bx, by, Speed
	}
	MouseMove, px3, py3, Speed
} ; Bezier_MouseMove( px1, py1, px2, py2, px3, py3, Segments=5, Rel=0, Speed=2 ) -------------------

#IfWinActive, Paint ; change this to whichever paint program you want to test in

~*b::
	Click Down
	Bezier_MouseMove( 100, 300, 200, -300, 300, 0, 24, 1, 1 )
	Click Up
Return

#IfWinActive

esc::exitapp
Pretty straightforward, not much to explain. Need more complexity, just use multiple calls.

Oh, and the function to simply list the intermediate points is here:
2D_Bezier( px0, py0, px1, py1, px2, py2, px3, py3, Segments ) { ; ----------------------------------
; Function by [VxE]. Returns a newline-separated list of (N-1) points which lie on the cubic bézier
; curve described by the four input points. NOTE: the returned points will be in FLOATING POINT
; format if, and only if, px0 contains a decimal. Otherwise, the returned points will be rounded to
; integers. More info about Bézier curves @ http://en.wikipedia.org/wiki/B%C3%A9zier_curve
; NOTE: the points (px0, py0) and (px3,py3) are always omitted from the returned list.
; Code Source: http://www.autohotkey.com/forum/viewtopic.php?p=374641#374641
	UseFloat := InStr( px0, "." )
	Loop % Segments - 1
	{
		u := 1 - t := A_Index / Segments
		bx := px0 * u**3 + 3 * px1 * t * u**2 + 3 * px2 * u * t**2 + px3 * t**3
		by := py0 * u**3 + 3 * py1 * t * u**2 + 3 * py2 * u * t**2 + py3 * t**3
		PointsList .= "`n" . ( UseFloat ? bx . "," . by : Round( bx ) . "," . Round( by ) )
	}
	Return SubStr( PointsList, 2 )
} ; 2D_Bezier( px0, py0, px1, py1, px2, py2, px3, py3, Segments ) ----------------------------------


#2 MasterFocus

MasterFocus
  • Moderators
  • 4132 posts

Posted 09 August 2010 - 06:56 AM

Very nice!
You should also adapt this and upload it here: <!-- m -->http://rosettacode.o...<!-- m -->

#3 evandevon

evandevon
  • Members
  • 86 posts

Posted 16 February 2011 - 05:18 AM

Thanks Vxe! I've been looking for something like this for ages, it taught me enough to make one that does CatMull Rom splines in which the curve goes through all the points including the control points for a 4 point interpolated curve. Basically just input the Catmull equation into your code and changed some names etc.
Here it is:
;Positions
X1 = 100
Y1 = 20

X2 = 100
Y2 = 50

X3 = 0
Y3 = 50

CatMull_MouseMove( px1, py1, px2, py2, px3, py3, Segments=5, Rel=0, Speed=2 ) 
{
; Function by [evandevon]. Moves the mouse through 4 points (without control point "gaps"). Inspired by VXe's 
;cubic bezier curve function (with some borrowed code).
   MouseGetPos, px0, py0 
   If Rel 
      px1 += px0, px2 += px0, px3 += px0, py1 += py0, py2 += py0, py3 += py0 
   Loop % Segments - 1 
   { 
	;CatMull Rom Spline - Working
	  u := 1 - t := A_Index / Segments 
	  cmx := Round(0.5*((2*px1) + (-px0+px2)*t + (2*px0 - 5*px1 +4*px2 - px3)*t**2 + (-px0 + 3*px1 - 3*px2 + px3)*t**3) )
	  cmy := Round(0.5*((2*py1) + (-py0+py2)*t + (2*py0 - 5*py1 +4*py2 - py3)*t**2 + (-py0 + 3*py1 - 3*py2 + py3)*t**3) )
	  
	  MouseMove, cmx, cmy, Speed,
	  
   } 
   MouseMove, px3, py3, Speed 
} ; CatMull_MouseMove( px1, py1, px2, py2, px3, py3, Segments=5, Rel=0, Speed=2 ) ------------------- 

#IfWinActive, Untitled - Paint ; change this to whichever paint program you want to test in 

~*c:: 

	MouseGetPos,Original_X,Original_Y
	
	Loop,3   ;This gives you a few dots to prove that the moues is moving through ALL the points
	{
		mousemove, X%A_Index%,Y%A_Index%,,r
		Send,{LButton Down}
		Send,{LButton Up}
		MouseMove,%Original_X%,%Original_Y%,,
		sleep,330
	}
			
   Click Down 
  CatMull_MouseMove( X1, Y1, X2, Y2, X3, Y3, 200, True, 1 ) 
   Click Up 
Return 

#IfWinActive 

esc::exitapp 




#4 Wicked

Wicked
  • Members
  • 480 posts

Posted 16 July 2012 - 05:08 AM

Firstly, I hope you don't mind me altering your functions at all. I don't by any means keep mine tidy, but using yours I came up with mousemove():
mousemove(px3,py3,px4=0,py4=0,relative=0,speed=250){
   if(isnan(px3)||isnan(py3)||isnan(px4)||isnan(py4)||isnan(relative)||isnan(speed))
      return
   batchlines:=a_batchlines
   setbatchlines,-1
   varsetcapacity(p0,8),dllcall("GetCursorPos","Uint",&p0),px0:=numget(p0,0,"Int"),py0:=numget(p0,4,"Int")
   px3:=px4!=0 ? nrand(px3,px4):px3,py3:=py4!=0 ? nrand(py3,py4):py3
   if relative
      px3:=px0+px3,py3:=py0+py3
   px1:=nrand(px0,px3),py1:=nrand(py0,py3),px2:=nrand(px1,px3),py2:=nrand(py1,py3)
   segments:=(segments:=sqrt(abs(px3-px0)**2+abs(py3-py0)**2))>speed ? speed:segments
   p:=bezier(px0,py0,px1,py1,px2,py2,px3,py3,segments) 
   start:=a_tickcount
   loop, % segments-1
   {
      dllcall("SetCursorPos",int,p.x[a_index-1],int,p.y[a_index-1])
      dllcall("Sleep",UInt,(delay:=(speed-(a_tickcount-start))/(segments-a_index))>=1 ? delay:1)
   }
   dllcall("SetCursorPos",int,px3,int,py3)
   setbatchlines,% batchlines
}

bezier(px0,py0,px1,py1,px2,py2,px3,py3,segments) {
   o:=[x,y]
   loop,% segments-1
      o.x[a_index-1]:=px0*(u:=1-t:=a_Index/segments)**3+3*px1*t*u**2+3*px2*u*t**2+px3*t**3,o.y[a_index-1]:=py0*u**3+3*py1*t*u**2+3*py2*u*t**2+py3*t**3
   return o
}

isnan(x){
   if x is number
      return false
   return true
}

nrand(x,y){
   f:=a_formatfloat
   setformat,float,0.6
   loop 12
      n+=rand(0.0,1)
   setformat,float,% f
   return (z:=((y>x ? y:x)+(x<y ? x:y))/2+((n-6)*((y>x ? y:x)-(x<y ? x:y)))/6)<(y>x ? y:x) ? z<(x<y ? x:y) ? (x<y ? x:y):z:(y>x ? y:x)
}

rand(x,y){
   random,v,% x,% y
   return v
}

dllcall-SetCursosPos runs a lot smoother then using mousemove and dllcall-sleep runs a lot smoother and reliably then sleep. It also allowed me to specify an estimated time to complete the entire action which, IMO anyways, is a bit more convenient then mouse speed.

Parameters:
px3 - The desired x coordinate (screen).
py3 - The desired y coordinate (screen).
px4 - If not 0, will select a random coordinate between px3 and px4.
py4 - If not 0, will select a random coordinate between py3 and py4.
relative - If true, coordinates based on current mouse position.
speed - Total time (in milliseconds) to complete the move.



Hope you find it at least semi-useful. I was very impressed with how well it flowed and how smooth the mouse moved. It generates the number of segments by distance from px0,py0 to px3,py3.

:).

#5 VxE

VxE
  • Fellows
  • 3511 posts

Posted 16 July 2012 - 06:05 AM

Firstly, I hope you don't mind...

Not at all. That others learn from the code and adapt it to their needs is the whole point.

dllcall-SetCursosPos runs a lot smoother then using mousemove and dllcall-sleep runs a lot smoother and reliably then sleep. It also allowed me to specify an estimated time to complete the entire action which, IMO anyways, is a bit more convenient then mouse speed.

Interesting. The biggest issue (that I have) with dll-sleep is that is allows no interrupt from timers, gui events, or the like. However, it seems to me that you're not going far enough. If you want real-time velocity, just recalculate the spline coordinates using the elapsed time directly. That way is much simpler than generating a list of coordinates based on an arbitrary number of segments. Here's what I mean:
Bezier_MouseMove2( X1, Y1, X2, Y2, X3, Y3, TimeMS = 200, Relative = 0 ) { ; Function by [VxE].
; Successively moves the mouse from its current position to the point (X3,Y3) along the cubic bézier spline
; described by the points (MouseX,MouseX), (X1,Y1), (X2,Y2) and (X3,Y3). 'Rel' should be 'true' if the points
; are relative to the mouse's current position. TimeMS dictates the speed; the mouse should arrive at (X3,Y3)
; after 'TimeMS' milliseconds have elapsed. See > @ http://en.wikipedia.org/wiki/B%C3%A9zier_curve
; Code Source: http://www.autohotkey.com/forum/viewtopic.php?p=374641#374641
	MouseGetPos, X0, Y0
	If (Relative)
		X1 += X0, X2 += X0, X3 += X0, Y1 += Y0, Y2 += Y0, Y3 += Y0
	TE := ( TI := A_TickCount ) + ( TimeMS := Abs( TimeMS ) )
	Loop
		If ( A_TickCount < TE )
		{
			t := ( A_TickCount - TI ) / TimeMS
			u := ( TE - A_TickCount ) / TimeMS
			bx := Round( X0 * u**3 + 3 * X1 * t * u**2 + 3 * X2 * u * t**2 + X3 * t**3 )
			by := Round( Y0 * u**3 + 3 * Y1 * t * u**2 + 3 * Y2 * u * t**2 + Y3 * t**3 )
			MouseMove, bx, by, 0
			Sleep 1
		}
		Else Break
	MouseMove, X3, Y3, 0
} ; END - Bezier_MouseMove2( X1, Y1, X2, Y2, X3, Y3, TimeMS = 200, Relative = 0 ) 

#IfWinActive, Paint ; change this to whichever paint program you want to test in

~*b::
   Click Down
   Bezier_MouseMove2( 100, 300, 200, -300, 300, 0, 2000, 1 ) ; take about 2000 milliseconds to move the mouse
   Click Up
Return

#IfWinActive

esc::exitapp


#6 Wicked

Wicked
  • Members
  • 480 posts

Posted 16 July 2012 - 11:37 PM

That's a very neat way of doing it! Thanks!

#7 Wicked

Wicked
  • Members
  • 480 posts

Posted 22 September 2012 - 08:52 AM

I edited your code, [VxE]. I hope you don't mind. I only edited it to suit my specific needs at the time. :p.
mousemove(x3,y3,x4=0,y4=0,relative=0,time=200){
   if(isnan(x3)||isnan(y3)||isnan(x4)||isnan(y4)||isnan(relative)||isnan(time)) 
      return
   batchlines:=a_batchlines
   setbatchlines,-1
   varsetcapacity(p0,8)
   dllcall("GetCursorPos","Uint",&p0)
   x0:=numget(p0,0,"Int")
   y0:=numget(p0,4,"Int")
   x3:=px4!=0 ? nrand(x3,x4):x3
   y3:=py4!=0 ? nrand(y3,y4):y3
   if relative
   {
      x3:=x0+x3
      y3:=y0+y3
   }
   x2:=nrand(x1:=nrand(x0,x3),x3)
   y2:=nrand(y1:=nrand(y0,y3),y3)
   te:=(ti:=a_tickcount )+(time:=abs(time))
   while(a_tickcount<te)
   {
      dllcall("SetCursorPos",int,round(x0*(u:=(te-a_tickcount)/time)**3+3*x1*(t:=(a_tickcount-ti)/time)*u**2+3*x2*u*t**2+x3*t**3),int,round(y0*u**3+3*y1*t*u**2+3*y2*u*t**2+y3*t**3))
      dllcall("Sleep",UInt,1)
   }
    dllcall("SetCursorPos",int,x3,int,y3)
   setbatchlines,% batchlines
}

isnan(x){
   if x is number
      return false
   return true
}

nrand(x,y){
   f:=a_formatfloat
   setformat,float,0.6
   loop 12
      n+=rand(0.0,1)
   setformat,float,% f
   return (z:=((y>x ? y:x)+(x<y ? x:y))/2+((n-6)*((y>x ? y:x)-(x<y ? x:y)))/6)<(y>x ? y:x) ? z<(x<y ? x:y) ? (x<y ? x:y):z:(y>x ? y:x)
}

rand(x,y){
   random,v,% x,% y
   return v
}

Used GetCursorPos/SetCursorPos since it doesn't rely on coordmode. I asked months ago for a A_CoordModeMouse or something but think it was forgotten about. Either that or I missed it in an update.

Rather then give 3 different coordinates, this function moves the mouse from it's current position through 3 random coordinates.

If x4 or y4 are specified, it'll move the mouse anywhere within x3,y3 and x4,y4. This is useful for when you know the location and size of a control or button that you'd like to move to. The position it chooses it likely to be near the center.

If relative is specified, obviously it moves based on the mouse's current position.

Time is the time taken to move the mouse from x0,y0 to x3,y3. This can be off by a few milliseconds.

;~ move to a random location from 0,0 to a_screenwidth,a_screenheight.
mousemove(0,0,a_screenwidth,a_screenheight)

;~ move to a random location within 100 pixels of the mouse's current positon.
mousemove(-50,-50,50,50,true)

;~ move between 250,500 and 600,750 and take (roughly) 300ms to complete.
mousemove(250,500,600,750,false,300)



Oddly enough, when you remove the sleep... The mouse randomly jumps once in a while during the move to a random location on screen. It's too fast to physically see, but can see it in Paint/Photoshop.

#8 MasterFocus

MasterFocus
  • Moderators
  • 4132 posts

Posted 24 September 2012 - 09:20 AM

I managed to create a related script thanks to [VxE]'s amazing approach for the 't' and '(1-t)' parts of the Bézier formula.
It can be found here: RandomBezier() - <!-- l --><a class="postlink-local" href="http://www.autohotkey.com/community/viewtopic.php?f=2&t=92808">viewtopic.php?f=2&t=92808</a><!-- l -->