Display object that has static physics body moves VERY slightly when rotated

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 for each new tile or after a drag.

If a tile is placed in row 5 (so y = 500) and then rotated the y value changes from 500 to 499.99996948242
(In every other row the y value stays the same before and after rotation).

How can this be explained? I notice this only happens if the tile has a physics body.

This code will run stand alone i.e. does not require use any image/sound assets etc.

[lua]
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 = ""

    

    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

            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)

        physics.addBody( img, “static” )

            

        --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” )

–draw gridlines

for i = 49, display.contentHeight, 100 do

    local line = display.newLine(0,i,display.contentWidth,i)

end

for i = 49, display.contentWidth, 100 do

    local line = display.newLine(i,0,i,display.contentHeight )

end

physics.start()

Runtime:addEventListener(“touch”, onTouch)

 

[/lua]

This code reproduces what you’re talking about:

local physics = require("physics") local rect = display.newRect(0, 0, 10, 10) rect.x, rect.y = 500, 500 print(rect.x, rect.y) physics.start() physics.addBody(rect, "static") print(rect.x, rect.y) timer.performWithDelay(100, function() print(rect.x, rect.y) end)

The output is

500 500 500 500 499.99996948242 499.99996948242

So basically after Box2D has a chance to do its thing, the numbers change slightly. My guess on what’s going on:

  1. When using the physics system, you’re handing over control of your display object’s placement to Box2D

2.  Box2D uses some different coordinate system than Corona and stores coordinates as floating point

  1. When converting to Box2D coordinates, 500 becomes some value that cannot be accurately represented as a float, e.g. 0.1

  2. When converting back to Corona coordinates, the loss of precision becomes evident

I tried with x, y = 200, 200 and there is no loss of precision there. This strenghtens the theory (whatever number 200 was converted to could be represented accurately as a float). This is why you’re only seeing it with x, y = 500, 500.

I don’t know if this will matter for any practical purpose in your app, since the value is very close to 500.

Thanks so much for the reply, I see that you are right because if I put a tile in position 500,500 and then rotate both values are updated (so we can assume that the problem is value related not position related).

This does cause a slight problem for me as I use the x and y values to find neighbouring tiles in my application.  I can see a fairly simple way around this by storing table entries such as roundX and roundY, updating those every time a tile moves and using those to find neighbours instead.

This code reproduces what you’re talking about:

local physics = require("physics") local rect = display.newRect(0, 0, 10, 10) rect.x, rect.y = 500, 500 print(rect.x, rect.y) physics.start() physics.addBody(rect, "static") print(rect.x, rect.y) timer.performWithDelay(100, function() print(rect.x, rect.y) end)

The output is

500 500 500 500 499.99996948242 499.99996948242

So basically after Box2D has a chance to do its thing, the numbers change slightly. My guess on what’s going on:

  1. When using the physics system, you’re handing over control of your display object’s placement to Box2D

2.  Box2D uses some different coordinate system than Corona and stores coordinates as floating point

  1. When converting to Box2D coordinates, 500 becomes some value that cannot be accurately represented as a float, e.g. 0.1

  2. When converting back to Corona coordinates, the loss of precision becomes evident

I tried with x, y = 200, 200 and there is no loss of precision there. This strenghtens the theory (whatever number 200 was converted to could be represented accurately as a float). This is why you’re only seeing it with x, y = 500, 500.

I don’t know if this will matter for any practical purpose in your app, since the value is very close to 500.

Thanks so much for the reply, I see that you are right because if I put a tile in position 500,500 and then rotate both values are updated (so we can assume that the problem is value related not position related).

This does cause a slight problem for me as I use the x and y values to find neighbouring tiles in my application.  I can see a fairly simple way around this by storing table entries such as roundX and roundY, updating those every time a tile moves and using those to find neighbours instead.