Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

Mouse Splines (Cubic Bézier Path)


  • Please log in to reply
8 replies to this topic
VxE
  • Moderators
  • 3622 posts
  • Last active: Dec 24 2015 02:21 AM
  • Joined: 07 Oct 2006
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 ) ----------------------------------


MasterFocus
  • Moderators
  • 4323 posts
  • Last active: Jan 28 2016 01:38 AM
  • Joined: 08 Apr 2009
Very nice!
You should also adapt this and upload it here: <!-- m -->http://rosettacode.o...<!-- m -->

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Antonio França -- git.io -- github.com -- ahk4.net -- sites.google.com -- ahkscript.org

Member of the AHK community since 08/Apr/2009. Moderator since mid-2012.


evandevon
  • Members
  • 88 posts
  • Last active: Oct 15 2014 01:03 PM
  • Joined: 22 Apr 2008
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 



Inventing problems that need solutions...

Open Communicator
MouseTrainer

Wicked
  • Members
  • 504 posts
  • Last active: Nov 18 2018 02:17 AM
  • Joined: 07 Jun 2008
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.

:).

3nL8f.png


VxE
  • Moderators
  • 3622 posts
  • Last active: Dec 24 2015 02:21 AM
  • Joined: 07 Oct 2006

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


Wicked
  • Members
  • 504 posts
  • Last active: Nov 18 2018 02:17 AM
  • Joined: 07 Jun 2008
That's a very neat way of doing it! Thanks!

3nL8f.png


Wicked
  • Members
  • 504 posts
  • Last active: Nov 18 2018 02:17 AM
  • Joined: 07 Jun 2008
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.

3nL8f.png


MasterFocus
  • Moderators
  • 4323 posts
  • Last active: Jan 28 2016 01:38 AM
  • Joined: 08 Apr 2009
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 -->

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Antonio França -- git.io -- github.com -- ahk4.net -- sites.google.com -- ahkscript.org

Member of the AHK community since 08/Apr/2009. Moderator since mid-2012.


polyethene
  • Members
  • 5519 posts
  • Last active: May 17 2015 06:39 AM
  • Joined: 26 Oct 2012

Nice!

 

Here's a version of an n-order bezier curve I wrote in Java a while back

	private static Vector3 bezier(final double t, final Vector3... p) {
		final double u = 1 - t;
		final int n = p.length - 1;
		Vector3 q = p[0].mul(Math.pow(u, n));
		for (int i = 1; i < n; i++) {
			q = q.add(p[i].mul(3 * Math.pow(u, n - i) * Math.pow(t, i)));
		}
		q = q.add(p[n].mul(Math.pow(t, n)));
		return q;
	}

I prefer catmull rom splines though, and if you want to go a bit further try get in fitt's law for timings


autohotkey.com/net Site Manager

 

Contact me by email (polyethene at autohotkey.net) or message tidbit