Pinch pan & zoom

RaspberryDJ

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

MikeHart

Administrator
Joined
Jun 19, 2017
Location
Germany
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

Administrator
Joined
Jun 19, 2017
Location
Germany
Calculating the current angle and subtract the initial one will give you a rotation angle.
 

RaspberryDJ

Member
Joined
Jun 3, 2019
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[0],mapMatrix[1],mapMatrix[2],mapMatrix[3],mapMatrix[4],mapMatrix[5]
            'Work out what map co-ordinate the mouse is pointing at by doing the matrix transformation backwards.
            coords = InvTransform([mx,my])
            tmx = coords[0]
            tmy = coords[1]
            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[0]
            omy = coords[1]
        PopMatrix
    End
    
    Method OnRender()
        Cls
        SetColor 255,255,255
        SetFont Null
        PushMatrix
        'apply the saved map transformation matrix
        Transform mapMatrix[0],mapMatrix[1],mapMatrix[2],mapMatrix[3],mapMatrix[4],mapMatrix[5]
        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[0])+","+Int(bits[1]),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
Joined
Jun 26, 2017
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
 

RaspberryDJ

Member
Joined
Jun 3, 2019
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[0],mapMatrix[1],mapMatrix[2],mapMatrix[3],mapMatrix[4],mapMatrix[5]
            coords = InvTransform([mx,my])
            tmx = coords[0] ; tmy = coords[1]
            If TouchDown(0) Then Translate tmx-omx, tmy-omy           
            mapMatrix = GetMatrix()
            coords = InvTransform([mx,my])
            omx = coords[0] ; omy = coords[1]
        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[0],mapMatrix[1],mapMatrix[2],mapMatrix[3],mapMatrix[4],mapMatrix[5]
        drawMap
        DrawText Int(omx)+","+Int(omy),omx,omy-TextHeight()
        Local bits#[] = InvTransform([mx,my])
        DrawText Int(bits[0])+","+Int(bits[1]),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

Administrator
Joined
Jun 19, 2017
Location
Germany
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
Joined
Jun 3, 2019
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
    ad += a * d
    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
  adbc = ad − bc
  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
Joined
Mar 5, 2018
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
 
Top Bottom