# Pinch pan & zoom

#### RaspberryDJ

##### Member
I'm looking for a way in Cerberus to create a pinch pan & zoom, any ideas how to go about doing that?

#### MikeHart

Look into TouchX and TouchY. These give you the coordinates of a Touch. You have to determine these for two touches. You need to keep track of these in each frame. Divide the current distance in pixel of the touches by the initial distance and you get a zoom factor.

#### MikeHart

Calculating the current angle and subtract the initial one will give you a rotation angle.

#### RaspberryDJ

##### Member
How do you handle multiple touches? I'm trying to do something simple like the example in the example folder :

transform mouse example:
``````' Map panning, rotating and zooming all using easy matrices
' by Christian Perfect
' By saving a transformation matrix and using the Translate, Rotate and Scale operations on it,
'  we can manipulate the player's view of a map very easily.
' The InvTransform command allows you to do the transformation backwards, converting screen
' co-ordinates to map co-ordinates

Import mojo

Class TransformMapApp Extends App
Field mx#,my#            'current screen co-ordinates of mouse
Field tmx#,tmy#        'current map co-ordinates of mouse
Field omx#,omy#        'previous map co-ordinates of mouse
Field mapMatrix#[]    'the transformation matrix applied to the map

Method OnCreate()
SetUpdateRate 0
'I want the map to be size 400x300 and to fill the entire screen when the program starts.
'So, the first step is to Scale the screen and grab the resulting transformation matrix
PushMatrix
Scale DeviceWidth()/400.0, DeviceHeight()/300.0
mapMatrix = GetMatrix()
PopMatrix
'The Push/PopMatrix isn't strictly necessary here since the matrix gets reset when the program first renders, but it's good to get in the habit of using it
End

Method OnUpdate()
Local coords#[]
mx = MouseX() ; my = MouseY()
'We're going to do some transformations on the mapMatrix, and they need to be undone so they don't affect drawing, so we need a Push/PopMatrix round all this code
PushMatrix
'Translate to the mouse's screen co-ords. This way, the rotation and scaling are centred on the mouse.
Translate mx,my
Rotate (KeyDown(KEY_LEFT)-KeyDown(KEY_RIGHT))*1.5
Local s# = 1+(KeyDown(KEY_UP) - KeyDown(KEY_DOWN))*.01
Scale s,s
'Now move back. While other points on the map might have been transformed, the mouse's position remains unchanged.
Translate -mx,-my
'Apply the last saved mapMatrix, that is, all rotations, scalings and translations applied since the program started
Transform mapMatrix,mapMatrix,mapMatrix,mapMatrix,mapMatrix,mapMatrix
'Work out what map co-ordinate the mouse is pointing at by doing the matrix transformation backwards.
coords = InvTransform([mx,my])
tmx = coords
tmy = coords
If TouchDown(0)
'Pan the map based on how far the mouse has moved since last frame
Translate tmx-omx, tmy-omy
Endif
mapMatrix = GetMatrix()        'Save the new map transformation matrix, preserving all the transformations we've just done.
'Work out the mouse's map co-ordinates based on the new matrix.
coords = InvTransform([mx,my])
omx = coords
omy = coords
PopMatrix
End

Method OnRender()
Cls
SetColor 255,255,255
SetFont Null
PushMatrix
'apply the saved map transformation matrix
Transform mapMatrix,mapMatrix,mapMatrix,mapMatrix,mapMatrix,mapMatrix
drawMap
'Draw the mouse's map co-ordinates above the cursor
DrawText Int(omx)+","+Int(omy),omx,omy-TextHeight()
Local bits#[] = InvTransform([mx,my])
DrawText Int(bits)+","+Int(bits),omx,omy-TextHeight()*2
PopMatrix
DrawText "Click and drag to move",0,0
DrawText "Arrow keys to rotate and zoom",0,TextHeight()

End

Method drawMap()
For Local x=0 To 400 Step 100
DrawLine x,0,x,300
Next
For Local y=0 To 300 Step 100
DrawLine 0,y,400,y
Next
For Local x=50 To 400 Step 100
For Local y=50 To 300 Step 100
DrawCircle x,y,2
DrawText x+","+y,x+2,y+2
Next
Next
End
End

Function Main()
New TransformMapApp
End``````

#### Phil7

##### Moderator
You can use the index of TouchX(index:Int) for multiple touchpoints. So finger one is TouchX(0) and finger two is TouchX(1).
I would use the coords of the middle between the two fingers for panning and rotation: xMiddle= (x0-x1)/2.0

BTW @MikeHart Do we have functions for vector calculation in cx. Maybe this is a good starting point for enhancement.
vector functions -> easy to use functions/classes for swipe, pinch, and collisions

#### Rich

##### Active Member
3rd Party Module Dev
3rd Party Tool Dev
My next fix for my GUI class will be pinch and zoom
Hopefully I'll get this done tomorrow evening

#### RaspberryDJ

##### Member
Thanks for the response!
After some head scratching I took out some Monkey2 code and put into it to check how compatible they are. It seems like the answer is not too much, but it might gives me something to build upon. The AffineMat3f datatype might be the one thing that is missing (or I just haven't found it yet).

Cerberus X:
``````Import mojo

Class TransformMapApp Extends App
Field mx#,my#
Field tmx#,tmy#
Field omx#,omy#
Field mapMatrix#[]
Field gesturing:Bool
Field g0#[]      ' ?
Field g1:AffineMat3f = New AffineMat3f
Field gmatrix:AffineMat3f = New AffineMat3f

Method OnCreate()
SetUpdateRate 0
PushMatrix
Scale DeviceWidth()/400.0, DeviceHeight()/300.0
mapMatrix = GetMatrix()
PopMatrix
End

Method OnUpdate()
Local coords#[]
mx = TouchX(0) ; my = TouchY(0)
PushMatrix

' ORIGINAL------------------------------------------------------------
Translate mx,my
Rotate (KeyDown(KEY_LEFT)-KeyDown(KEY_RIGHT))*1.5
Local s# = 1+(KeyDown(KEY_UP) - KeyDown(KEY_DOWN))*.01
Scale s,s
Translate -mx,-my

' TOUCH---------------------------------------------------------
If Not gesturing
If TouchDown(0) And TouchDown(1)
' If g1 Then     g0 = g1 * GestureMatrix(TouchXY(0),TouchXY(1))
' If Not g1 Then g0 =      GestureMatrix(TouchXY(0),TouchXY(1))
gesturing=True
Endif
Endif

If gesturing
If TouchDown(0) And TouchDown(1)
' gmatrix = -g0 * GestureMatrix(TouchXY(0),TouchXY(1))
Else
' g1 = -gmatrix ; gesturing = False
Endif
Endif
' -------------------------------------------------------------

Transform mapMatrix,mapMatrix,mapMatrix,mapMatrix,mapMatrix,mapMatrix
coords = InvTransform([mx,my])
tmx = coords ; tmy = coords
If TouchDown(0) Then Translate tmx-omx, tmy-omy
mapMatrix = GetMatrix()
coords = InvTransform([mx,my])
omx = coords ; omy = coords
PopMatrix
End

Method GestureMatrix:AffineMat3f(p0:Vec2f,p1:Vec2f)
Local d = p1-p0,r = -ATan2(d.y,d.x),s = d.Length
Local center = (p0-p1) / 2.0 + p1
Return New AffineMat3f().Translate(p0).Rotate(r).Scale(s,s)
End

Method OnRender()
Cls
SetColor 255,255,255
SetFont Null
PushMatrix
Transform mapMatrix,mapMatrix,mapMatrix,mapMatrix,mapMatrix,mapMatrix
drawMap
DrawText Int(omx)+","+Int(omy),omx,omy-TextHeight()
Local bits#[] = InvTransform([mx,my])
DrawText Int(bits)+","+Int(bits),omx,omy-TextHeight()*2
PopMatrix
End

Method drawMap()
For Local x=0 To 400 Step 100
DrawLine x,0,x,300
Next
For Local y=0 To 300 Step 100
DrawLine 0,y,400,y
Next
For Local x=50 To 400 Step 100
For Local y=50 To 300 Step 100
DrawCircle x,y,2
DrawText x+","+y,x+2,y+2
Next
Next
End
End

Function Main()
New TransformMapApp
End``````

#### MikeHart

BTW @MikeHart Do we have functions for vector calculation in cx. Maybe this is a good starting point for enhancement.
vector functions -> easy to use functions/classes for swipe, pinch, and collisions
Yup, in my framework fantomCX I have something like that already and it could easily be integrated into the next CX version.

#### RaspberryDJ

##### Member
Got some very interesting ideas going on here.
Normally you use two fingers for pan zoom rotation.

Here's the next generation of UI.. any number of fingers goes.

The big thing here is that even if you use only two fingers, as it dynamically knows how to handle any numbers of fingers it handles the transitions between any number of fingers, without immediate jumps.
Normally going from 1 to 2 fingers brings problem, and introducing a third or 5 fingers, would confuse it, or it would simply ignore them and prioritise only 2 fingers.

But this algorithm, will take equal account for all fingers, and always feel natural and "do the right thing".

Btw Epsilon would be a small positive number close to zero but not zero.

True-multi-touch:
``````def estimate translation scaling rotation(X,Y):

N=min(len(X),len(Y))
a1 = b1 = c1 = d1 = 0
a2 = b2 = ac = ad = bc = bd = 0

for i in range(1,N):
a = X[ i ] [ 0 ]
b = X[ i ] [ 1 ]
c = Y[ i ] [ 0 ]
d = Y[ 1 ] [ 1 ]
a1 += a
b1 += b
c1 += c
d1 += d
a2 += a * a
b2 += b * b
ac += a * c
bc += b ∗ c
bd += b ∗ d

g = N ∗ a2 + N ∗ b2 − a1 ∗ a1 − b1 ∗ b1

if g < epsilon:
if N == 0 :
return 1,0,0,0
return 1,0,(c1 − a1) / N,(d1 − b1) / N

acbd = ac + bd
s = (N ∗ acbd − a1 ∗ c1 − b1 ∗ d1) / g
r = (N ∗ adbc + b1 ∗ c1 − a1 ∗ d1) / g
t1 = (−a1 ∗ acbd + b1 ∗ adbc + a2 ∗ c1 + b2 ∗ c1) / g
t2 = (−b1 ∗ acbd − a1 ∗ adbc + a2 ∗ d1 + b2 ∗ d1) / g
return s,r,t1,t2``````

#### magic

##### Active Member
3rd Party Module Dev
3rd Party Tool Dev
Referring to the example that you show (..examples/mojo/warpy/transform/transform_mouse.cxs), I notice that when I run in android (ver 7.0 in my case) I cannot PAN the drawing like in html. When I tap to pan, the maps always move to my tab location. Weird.. because this thing not happen in html