Question

When I run this code (which is based on the "DragMe" sample app) I get very unexpected results in very particular circumstances.

My "snapToGrid" function sets x and y to the nearest round 100 value after a drag.

The "examine" function outputs the x and y values after an object is created, moved or rotated (by clicking on it).

If you place an object in row 5 (so y = 500) and rotate it you will see that the y value changes to 499.99996948242 but this does not happen in any other row.

How can this be explained? I notice this does not happen if the physics bodies are not added to the display object.

I know I could call snapToGrid after rotation or round the value before I use it for anything else but I think there is an important opportunity here for me to learn something useful about Corona.

local function snapToGrid(t)
    modHalf = t.x % t.width
    if modHalf > t.width/2 then
        t.x = t.x + (t.width-modHalf)
    end
    if modHalf < t.width/2 then 
        t.x = t.x - modHalf
    end
    modHalfY = t.y % t.height
    if modHalfY > t.height/2 then
        t.y = t.y + (t.height-modHalfY)
    end
    if modHalfY < t.height/2 then 
        t.y = t.y - modHalfY
    end
    display.getCurrentStage():setFocus( nil )
    t.isFocus = false
    return true
end

function rotatePiece(target)
    if target.rotation == 270 then
        target.rotation = 0
    else
        target.rotation = target.rotation + 90
    end
end

local function dragBody(event)
    local target = event.target
    local phase = event.phase
    local halfWidth = target.width/2
    local halfHeight = target.height/2
    --get tileX and tileY relative to tile centre
    local tileX = event.x-target.x
    local tileY = event.y-target.y
    local modHalf = ""
    local snap = 15

    if phase == "began" then
        -- Make target the top-most object
        display.getCurrentStage():setFocus( target )

        -- Spurious events can be sent to the target, e.g. the user presses 
        -- elsewhere on the screen and then moves the finger over the target.
        -- To prevent this, we add this flag. Only when it's true will "move"
        -- events be sent to the target.
        target.isFocus = true

        -- Store initial position
        target.x0 = event.x - target.x
        target.y0 = event.y - target.y
    elseif target.isFocus then
        if phase == "moved" then
            -- Make object move (we subtract target.x0,target.y0 so that moves are
            -- relative to initial grab point, rather than object "snapping").
            target.x = event.x - target.x0
            target.y = event.y - target.y0
            if target.x > display.contentWidth - (target.width/2) then target.x = display.contentWidth - (target.width/2) end
            if target.y > display.contentHeight - (target.height/2) then target.y = display.contentHeight - (target.height/2) end
            if target.x < 0 + (target.width/2) then target.x = 0 + (target.width/2) end
            if target.y < 0 + (target.height/2) then target.y = 0 + (target.height/2) end
            modHalf = target.x % target.width
            if modHalf < snap then
                target.x = target.x - modHalf 
            end
            if modHalf > ((target.width) - snap) then 
                target.x = target.x + ((target.width)-modHalf)
            end
            modHalfY = target.y % target.height
            if modHalfY < snap then
                target.y    = target.y - modHalfY 
            end
            if modHalfY > ((target.height) - snap) then 
                target.y = target.y + ((target.height)-modHalfY)
            end
            hasMoved = true
            return true
        elseif phase == "ended" then
            if hasMoved then 
                hasMoved = false
                snapToGrid(target)
                --tile has moved
                examine(target)

                return true
            else
                --rotate piece
                    rotatePiece(target)
                    display.getCurrentStage():setFocus( nil )
                    target.isFocus = false
                    --tile has rotated
                    examine(target)
                    return true
            end
        end
    end

    -- Important to return true. This tells the system that the event
    -- should not be propagated to listeners of any objects underneath.
    return true
end

local onTouch = function(event)
    if event.phase == "began" then
        local tile = {}
        img = display.newRect(event.x,event.y,100,100)
        img:addEventListener( "touch", dragBody )
        snapToGrid(img)

        --top right corner and top middle solid 
        topRight = {16,-16,16,50,50,50,50,-16}
        --top left and left middle solid 
        topLeft = {-16,-16,-16,-50,50,-50,50,-16}
        --bottom right and right middle solid 
        bottomRight = {16,16,16,50,-50,50,-50,16}
        --bottom left and bottom middle solid 
        bottomLeft = {-16,16,-16,-50,-50,-50,-50,16}

        physics.addBody( img, "static",
            {shape=topRight},
            {shape=topLeft},
            {shape=bottomLeft},
            {shape=bottomRight}
        )

        --new tile created
        examine(img)

        return true
    end
end


function examine(img)
    print("--------------------------------------------------")
    if img ~= nil then
        print("X: "..img.x..", Y: "..img.y)
    end
    print("--------------------------------------------------")
end

local img
local physics = require( "physics" )
physics.setDrawMode( "hybrid" )

--draw gridlines
for i = 49, display.contentHeight, 100 do
    local line = display.newLine(0,i,display.contentWidth,i)
    local line2 = display.newLine(0,i+2,display.contentWidth,i+2)
end
for i = 49, display.contentWidth, 100 do
    local line = display.newLine(i,0,i,display.contentHeight )
    local line2 = display.newLine(i+2,0,i+2,display.contentHeight )
end
--init
physics.start()
Runtime:addEventListener("touch", onTouch)
Was it helpful?

Solution 2

Actually this problem only occurs if you add the physics, so I think it is fair to say that the issue is caused by transferring control to box2d, not by Corona handling of the number alone. I asked on Corona forums and got this answer.

http://forums.coronalabs.com/topic/46245-display-object-that-has-static-physics-body-moves-very-slightly-when-rotated/

OTHER TIPS

There several possibilities. Firstly, since there are no integers in Lua, all numbers are double floating point values. According to FloatingPoint on Lua wiki,

Some vendors' printf implementations may not be able to handle printing floating point numbers accurately. Believe it or not, some may incorrectly print integers (that are floating point numbers). This can manifest itself as incorrectly printing some numbers in Lua.

Indeed, try the following:

for i=1,50,0.01 do print(i) end

You will see that a lot of numbers print exactly as you would expect, by many print with an error of 10^-12 or even 2 x 10^-12.

However, your x prints fine when you don't give it to physics module. So surely the physics module does some computation on object position and changes it. I would certainly expect that for dynamic objects (due to collision detection), but here your object seems to be "static". So it must be that physics adjusts x even for static objects. The adjustment is so small that it would not be visible on the screen (you can't perceive any motion less than a pixel), but you're right that it is interesting to ask why.

The SO post Lua: subtracting decimal numbers doesn't return correct precision is worth reading; it has some links you might find interesting and gives the neat example that decimal 0.01 cannot be represented exactly in base 2; just like 1/3 can't be represented exactly in base 10 (but it can in base 3: it would be 0.1 base 3!). The question shows that, although Lua (or possibly, the underlying C) is smart enough to print 0.01 as 0.01, it fails to print 10.08-10.07 as 0.01.

If you really want to shake your understanding of floating point values, try this:

> a=0.3
> b=0.3
> print(a==b)
true
> -- so far so good; now this: 
> a=0.15 + 0.15
> b=0.1 + 0.2
> print(a,b)
0.3     0.3
> print(c==d)
false -- woa!

This is explained by the fact that 0.15 in binary has a small error which is different from that of 0.1 or 0.2, so in terms of bits they are not identical; although when printed, the difference is too small to show. You may want to read the Floating Point Guide.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top