Ok, so I’ve been wanting to implement pinch-zoom for a while for a scene builder I’ve got plans for. I realised that to effectively move objects around on the stage they would need to be moveable by touch (of course) and this lead me to thinking about pinch-zoom or “multi-touch scaling and rotating”.
The first step was to create something which reliably tracked multiple touch points so that I could refer to them at any time - the reason being that knowing where touch A is when receiving the event for touch B needs some internal management, of course.
So, the code below contains a function called addTouch which basically takes an image and an event object (as passed to a touch event listener) and adds a big red circle to the screen at the touch location. This is useful because, in the simulator, it can be used (via a quick double tap of the trackpad) to add a touch to the screen and leave it there, as if the finger were held down. Actually pressing down on the trackpad will create a second, or third, touch and allow you to move it around. All of that data is managed by the addTouch function and each touch point will call a callback function, if provided, on the img called touchMoved. The touch objects also maintain x/y start, previous and current values of their location. Currently, it does not allow a touch to be moved after it’s been placed, but I’ll sort that out at some point.
Anyway, onto the pinch-zoom-rotation thing…
I was thinking that if the mid-point between the first two touches were calculated and used to set the x/yReference of the image, then scaling and rotating would be easy. In theory, I’m correct. In practice I’m wrong. What I can’t work out is why. In the code below the function img:doScale is trying to position the image’s reference point and perform a scaling around it, based on the change of the distance between the two touch points. But it’s not working.
I’d love it if anyone can figure out why and share here. The code below is my attempt at boiling everything down as neatly as possible to make the job of working out the pinch/zoom as easy and clean as possible…
Matt.
[lua]-- this is useful, if you need it
function getLength( a, b )
local width, height = b.x-a.x, b.y-a.y
return math.sqrt(width*width + height*height)
end
– this is useful, if you need it
function AngleOfPoint( pt )
local x, y = pt.x, pt.y
local radian = math.atan2(y,x)
local angle = radian*180/math.pi
if angle < 0 then angle = 360 + angle end
return angle
end
– this is useful, if you need it
function AngleDiff( pointA, pointB, clockwise )
local angleA, angleB = AngleOfPoint( pointA ), AngleOfPoint( pointB )
if angleA == angleB then
return 0
end
if clockwise then
if angleA > angleB then
return angleA - angleB
else
return 360 - (angleB - angleA)
end
else
if angleA > angleB then
return angleB + (360 - angleA)
else
return angleB - angleA
end
end
end
–[[
This creates a multi-touch tracking object and adds it to a list of multi-touch tracking objects
internal to the image being manipulated by the touches.
The list is maintained as attached to the image and the touch objects store all their original, previous
and current location information.
]]–
function addTouch( img, event )
–[[Construction]]–
print(event.x,event.y)
local touch = display.newCircle( event.x, event.y, 50 )
touch:setFillColor( 255,0,0,150 )
touch.xStart, touch.yStart = event.x, event.y
touch.xPrev, touch.yPrev = event.x, event.y
touch.x, touch.y = event.x, event.y
touch.parentImg = img
touch.id = event.id
– adds the list in which to keep the multi-touch tracking objects, if not there yet
if (touch.parentImg.touchList == nil) then
local list = {}
function list:indexOf( touch )
for i=1, #list do
if (list[i] == touch) then
return i
end
end
return 0
end
touch.parentImg.touchList = list
end
touch.list = touch.parentImg.touchList
touch.list[#touch.list +1] = touch
–[[External functions]]–
– returns true if this touch is the first in the list (probably the first touch made)
function touch:isFirst()
if (touch.list[1] == touch) then
return true
else
return false
end
end
– returns the index of the touch in the list, or 0 if not in the list
function touch:indexOf()
return touch.list:indexOf( touch )
end
–[[Internal functions]]–
function touch:began( event )
– should never be called, this is here for convenience, but should never be called
touch.xStart, touch.yStart = event.x, event.y
end
function touch:moved( event )
if (touch.parentImg.touchMoved ~= nil) then
touch.xPrev, touch.yPrev = touch.x, touch.y
touch.x, touch.y = event.x, event.y
touch.parentImg:touchMoved( touch )
end
end
function touch:endedCancelled( event )
display.getCurrentStage():setFocus( touch, nil )
table.remove( touch.list, touch:indexOf() )
touch:removeSelf()
end
function touch:touch( event )
if (event.phase == “began”) then
touch:began( event )
elseif (event.phase == “moved”) then
touch:moved( event )
elseif (event.phase == “ended” or event.phase == “cancelled”) then
touch:endedCancelled( event )
end
end
–[[Completion]]–
– currently, I don’t think this will let you move an existing, stationary touch, but I will work on that
touch:addEventListener( “touch”, touch )
display.getCurrentStage():setFocus( touch, event.id )
return touch – for convenience, but not really used
end
local img = display.newImage( “crate.png” )
function img:touch( event )
if (event.phase == “began”) then
addTouch( img, event )
end
if (img.touchList ~= nil) then
print(#img.touchList)
end
end
function img:tap( event )
if (event.numTaps == 2) then
addTouch( img, event )
end
end
function img:doScale( touchA, touchB )
local startLen = getLength( {x=touchA.xStart, y=touchA.yStart}, {x=touchB.xStart, y=touchB.yStart} )
local endLen = getLength( {x=touchA.x, y=touchA.y}, {x=touchB.x, y=touchB.y} )
– get touch coords relative to image
local tax = img.x-(img.x - touchA.x)
local tay = img.y-(img.y - touchA.y)
local tbx = img.x-(img.x - touchB.x)
local tby = img.x-(img.y - touchB.y)
print(tax,tay,tbx,tby)
img.xReference = (tbx-tax)/2
img.yReference = (tby-tay)/2
local scale = endLen / startLen
img.xScale, img.yScale = scale, scale
end
function img:touchMoved( touch )
print(‘touch moved’)
if (#img.touchList == 1) then
img.x, img.y = img.x+(touch.x-touch.xPrev), img.y+(touch.y-touch.yPrev)
elseif (#img.touchList == 2) then
img:doScale( img.touchList[1], img.touchList[2] )
end
end
img:addEventListener( “touch”, img )
img:addEventListener( “tap”, img ) – double tap to create a single, unmoving touch object[/lua] [import]uid: 8271 topic_id: 4160 reply_id: 304160[/import]