Pinch zoom rotate bug?

Hey Everyone.

I have added in rotation support into Corona’s default pinch-zoom sample code (and got it working!).

Since I can’t test multitouch in the corona simulator, I’m exporting to the iOS simulator. Everything seems to work as it should in the basic iPhone simulator, but breaks on the Retina iPhone simulator. In the retina simulator and when exporting to an actual phone, I am getting a strange flip issue that seems to be related to inconsistent touch values in my calculateRotation function.

I used the same “touch” and “event” values that the sample code uses to calculate distance etc. When I print out the x,y values to see whats happening, I find that in one frame, touch 1 (touch.x, touch.y) might be something like: 100, 300 while touch 2 (event.x, event.y) could be 200, 250. The next frame they switch! So now touch 1 (touch.x,touch.y) is 200,250 and touch 2 (event.x,event.y) is 100,300. This seems like the obvious culprit, but if it is then I am confused as to why this works at all in the iOS (non retina) simulator. Any ideas what might be going on here?

I’ve also created a project file for anyone to download and test with here:
http://dl.dropbox.com/u/46392597/pinch_zoom_roate_example.zip

If we can get this working correctly I was going to throw it up on code share for everyone to use, I’ve just got this weird bug.

Here’s the code as well if you just want to look at it.

[lua]local function calculateDelta( previousTouches, event )

local id,touch = next( previousTouches )
if event.id == id then
id,touch = next( previousTouches, id )
assert( id ~= event.id )
end

local dx = touch.x - event.x
local dy = touch.y - event.y
return dx, dy
end

local function calculateCenter( previousTouches, event )

local id,touch = next( previousTouches )
if event.id == id then
id,touch = next( previousTouches, id )
assert( id ~= event.id )
end

local cx = math.floor( ( touch.x + event.x ) * 0.5 )
local cy = math.floor( ( touch.y + event.y ) * 0.5 )
return cx, cy

end

local function calculateRotation( previousTouches, event )

local id,touch = next( previousTouches )
if event.id == id then
id,touch = next( previousTouches, id )
assert( id ~= event.id )
end

local radianValue = (math.atan2((touch.x - event.x),(touch.y - event.y)))
local degreeValue = math.deg(radianValue)
return degreeValue
end

local function initialTouchRotation( previousTouches, event )

local id,touch = next( previousTouches )
if event.id == id then
id,touch = next( previousTouches, id )
assert( id ~= event.id )
end

local radianValue2 = (math.atan2((touch.x - event.x),(touch.y - event.y)))
local degreeValue2 = math.deg(radianValue2)
return degreeValue2
end

function background:touch( event )
if (alignImageMode == true) then
local phase = event.phase
local eventTime = event.time
local previousTouches = self.previousTouches

if not self.xScaleStart then
self.xScaleStart, self.yScaleStart = self.xScale, self.yScale
end

local numTotalTouches = 1
if previousTouches then
– add in total from previousTouches, subtract one if event is already in the array
numTotalTouches = numTotalTouches + self.numPreviousTouches
if previousTouches[event.id] then
numTotalTouches = numTotalTouches - 1
end
end

if “began” == phase then
– Very first “began” event
if not self.isFocus then
– Subsequent touch events will target button even if they are outside the contentBounds of button
display.getCurrentStage():setFocus( self )
self.isFocus = true

– Store initial position
self.x0 = event.x - self.x
self.y0 = event.y - self.y

previousTouches = {}
self.previousTouches = previousTouches
self.numPreviousTouches = 0
self.firstTouch = event

elseif not self.distance then
local dx,dy
local cx,cy

if previousTouches and numTotalTouches >= 2 then
dx,dy = calculateDelta( previousTouches, event )
cx,cy = calculateCenter( previousTouches, event )
initialTouchRotationValue = initialTouchRotation( previousTouches, event )
initialImageRotationValue = self.rotation
end

– initialize to distance between two touches
if dx and dy then
local d = math.sqrt( dx*dx + dy*dy )
if d > 0 then
self.distance = d
self.xScaleOriginal = self.xScale
self.yScaleOriginal = self.yScale

self.x0 = cx - self.x
self.y0 = cy - self.y
end
end

end

if not previousTouches[event.id] then
self.numPreviousTouches = self.numPreviousTouches + 1
end
previousTouches[event.id] = event

elseif self.isFocus then
if “moved” == phase then
print (“moving”)
if self.distance then
local dx,dy
local cx,cy
if previousTouches and numTotalTouches == 2 then
dx,dy = calculateDelta( previousTouches, event )
cx,cy = calculateCenter( previousTouches, event )
rotationValue = calculateRotation( previousTouches, event )
end

if dx and dy then
local newDistance = math.sqrt( dx*dx + dy*dy )
local scale = newDistance / self.distance

if scale > 0 then
self.xScale = self.xScaleOriginal * scale
self.yScale = self.yScaleOriginal * scale

– Make object move while scaling
self.x = cx - ( self.x0 * scale )
self.y = cy - ( self.y0 * scale )

–Make object rotate while moving and scaling
self.rotation = initialImageRotationValue -(rotationValue - initialTouchRotationValue)

end
end
else
if event.id == self.firstTouch.id then
– don’t move unless this is the first touch id.
– Make object move (we subtract self.x0, self.y0 so that moves are
– relative to initial grab point, rather than object “snapping”).
self.x = event.x - self.x0
self.y = event.y - self.y0
end
end

if event.id == self.firstTouch.id then
self.firstTouch = event
end

if not previousTouches[event.id] then
self.numPreviousTouches = self.numPreviousTouches + 1
end
previousTouches[event.id] = event

elseif “ended” == phase or “cancelled” == phase then
– check for taps
local dx = math.abs( event.xStart - event.x )
local dy = math.abs( event.yStart - event.y )
if eventTime - previousTouches[event.id].time < 150 and dx < 10 and dy < 10 then
if not self.tapTime then
– single tap
self.tapTime = eventTime
self.tapDelay = timer.performWithDelay( 300, function() self.tapTime = nil end )
elseif eventTime - self.tapTime < 300 then
– double tap
timer.cancel( self.tapDelay )
self.tapTime = nil
if self.xScale == self.xScaleStart and self.yScale == self.yScaleStart then
transition.to( self, { time=300, transition=easing.inOutQuad, xScale=self.xScale*2, yScale=self.yScale*2, x=event.x - self.x0*2, y=event.y - self.y0*2 } )
else
local factor = self.xScaleStart / self.xScale
transition.to( self, { time=300, transition=easing.inOutQuad, xScale=self.xScaleStart, yScale=self.yScaleStart, x=event.x - self.x0*factor, y=event.y - self.y0*factor } )
end
end
end


if previousTouches[event.id] then
self.numPreviousTouches = self.numPreviousTouches - 1
previousTouches[event.id] = nil
print (“here”)
end

if self.numPreviousTouches == 1 then
– must be at least 2 touches remaining to pinch/zoom
print (“here2”)
self.distance = nil
– reset initial position
local id,touch = next( previousTouches )
self.x0 = touch.x - self.x
self.y0 = touch.y - self.y
self.firstTouch = touch

elseif self.numPreviousTouches == 0 then
– previousTouches is empty so no more fingers are touching the screen
– Allow touch events to be sent normally to the objects they “hit”
display.getCurrentStage():setFocus( nil )
self.isFocus = false
self.distance = nil
self.xScaleOriginal = nil
self.yScaleOriginal = nil

– reset array
self.previousTouches = nil
self.numPreviousTouches = nil
end
end
end

return true
end
end[/lua] [import]uid: 3116 topic_id: 16749 reply_id: 316749[/import]

Without reading through your code thoughly… and doing my development on android… I do have the following comment…

I have notitced inconsistancies in how scaled images/objects are treated. (Have an app that allows you to view an image, drag it around and zoom in/out on any portion of it) I have taken to setting the scaling of the image back to 1 at the begining of any function that moves/changes the x or y referance and changing it back to the desired scaling at the end of the function. (This cleared up my inconsistansies and as the image is only updated when the code block completes, the scale change (to 1 and back) is invisable to the user. [import]uid: 40100 topic_id: 16749 reply_id: 62720[/import]