Is this a bug with world:queryBoundingBox

Questions about the LÖVE API, installing LÖVE and other support related questions go here.
Forum rules
Before you make a thread asking for help, read this.
Post Reply
zell2002
Citizen
Posts: 75
Joined: Sun Feb 23, 2014 9:22 pm

Is this a bug with world:queryBoundingBox

Post by zell2002 » Sat Aug 31, 2019 6:40 pm

Below is code snippet from the basic physics tutorial:
https://love2d.org/wiki/Tutorial:Physics

I'm not sure if I've misunderstood how to use this function. But I noticed my bounding box was offset by 10+ pixels - or something ?

So I took the basic physics tutorial, and added queryBoundingBox. I use the mouse position during update(dt) and a box of 100,50 size to find objects in the world.
Any found fixture I draw using either the fixture:getBoundingBox(), or the fixture's shape's getPoints() using the fixture's body's getWorldPoints(). You can switch between these by pressing Spacebar.

Where the mouse cursor is, I draw a white box that changes colour pending if it has "overlapped" another object - based on queryBoundingBox.
Then for any "found" object in the queryBoundingBox() I draw that fixture's bounding box of shape's position (press Spacebar to switch between them)

Image

This claims the top grey box is within my queryBoundingBox - but it's so far away. The grey box has its green boundingBox drawn over it.
Image


Is this correct behaviour?

simple main.lua:

Code: Select all

io.stdout:setvbuf('no')

function love.load()
  love.physics.setMeter(64) --the height of a meter our worlds will be 64px
  world = love.physics.newWorld(0, 9.81*64, true) --create a world for the bodies to exist in with horizontal gravity of 0 and vertical gravity of 9.81
 
  objects = {} -- table to hold all our physical objects
 
  --let's create the ground
  objects.ground = {}
  objects.ground.body = love.physics.newBody(world, 650/2, 650-50/2) --remember, the shape (the rectangle we create next) anchors to the body from its center, so we have to move it to (650/2, 650-50/2)
  objects.ground.shape = love.physics.newRectangleShape(650, 50) --make a rectangle with a width of 650 and a height of 50
  objects.ground.fixture = love.physics.newFixture(objects.ground.body, objects.ground.shape); --attach shape to body
 
  --let's create a ball
  objects.ball = {}
  objects.ball.body = love.physics.newBody(world, 650/2, 650/2, "dynamic") --place the body in the center of the world and make it dynamic, so it can move around
  objects.ball.shape = love.physics.newCircleShape(20) --the ball's shape has a radius of 20
  objects.ball.fixture = love.physics.newFixture(objects.ball.body, objects.ball.shape, 1) -- Attach fixture to body and give it a density of 1.
  objects.ball.fixture:setRestitution(0.9) --let the ball bounce
 
  --let's create a couple blocks to play around with
  objects.block1 = {}
  objects.block1.body = love.physics.newBody(world, 200, 550, "dynamic")
  objects.block1.shape = love.physics.newRectangleShape(0, 0, 50, 100)
  objects.block1.fixture = love.physics.newFixture(objects.block1.body, objects.block1.shape, 5) -- A higher density gives it more mass.
 
  objects.block2 = {}
  objects.block2.body = love.physics.newBody(world, 200, 400, "dynamic")
  objects.block2.shape = love.physics.newRectangleShape(0, 0, 100, 50)
  objects.block2.fixture = love.physics.newFixture(objects.block2.body, objects.block2.shape, 10)
 
  --initial graphics setup
  love.graphics.setBackgroundColor(0.41, 0.53, 0.97) --set the background color to a nice blue
  love.window.setMode(650, 650) --set the window dimensions to 650 by 650

  local major, minor, revision, codename = love.getVersion()
  print(major, minor)
end
 
mouseposition = {x=0, y=0}
boxcheck = {100, 50}
boxsearch = {1,1,1, 0.25}
boxfound = {1,0,0, 0.25}
found_objects = {}

function love.update(dt)
  world:update(dt) --this puts the world into motion
 
  --here we are going to create some keyboard events
  if love.keyboard.isDown("right") then --press the right arrow key to push the ball to the right
    objects.ball.body:applyForce(400, 0)
  elseif love.keyboard.isDown("left") then --press the left arrow key to push the ball to the left
    objects.ball.body:applyForce(-400, 0)
  elseif love.keyboard.isDown("up") then --press the up arrow key to set the ball in the air
    objects.ball.body:setPosition(650/2, 650/2)
    objects.ball.body:setLinearVelocity(0, 0) --we must set the velocity to zero to prevent a potentially large velocity generated by the change in position
  end

  local mx, my = love.mouse.getPosition()
  mouseposition.x = mx
  mouseposition.y = my

  found_objects = search(mx, my)  
end

function search(mx, my)
  local found = {}
  function callback(fixture)
    table.insert(found, fixture)
    -- keep looking
   return true
  end
  
  world:queryBoundingBox(mx, my, mx + boxcheck[1], my + boxcheck[2], callback)

  return found
end


function love.draw()
  love.graphics.setColor(0.28, 0.63, 0.05) -- set the drawing color to green for the ground
  love.graphics.polygon("fill", objects.ground.body:getWorldPoints(objects.ground.shape:getPoints())) -- draw a "filled in" polygon using the ground's coordinates
 
  love.graphics.setColor(0.76, 0.18, 0.05) --set the drawing color to red for the ball
  love.graphics.circle("fill", objects.ball.body:getX(), objects.ball.body:getY(), objects.ball.shape:getRadius())
 
  love.graphics.setColor(0.20, 0.20, 0.20) -- set the drawing color to grey for the blocks
  love.graphics.polygon("fill", objects.block1.body:getWorldPoints(objects.block1.shape:getPoints()))
  love.graphics.polygon("fill", objects.block2.body:getWorldPoints(objects.block2.shape:getPoints()))

  local mx = mouseposition.x
  local my = mouseposition.y
  local cBox = boxsearch
  local drawfixtures = {}

  if #found_objects > 0 then
    cBox = boxfound
    for _, fixture in ipairs(found_objects) do
      local topLeftX, topLeftY, bottomRightX, bottomRightY = fixture:getBoundingBox(1)
      local shape = fixture:getShape()
      local body = fixture:getBody()
      
      if shape:type() == "PolygonShape" then
        local points = {shape:getPoints()}
        -- calculate the bounding box size
        local w = math.abs(topLeftX - bottomRightX)
        local h = math.abs(topLeftY - bottomRightY)
        local t = {
          x = topLeftX,
          y = topLeftY,
          w = w,
          h = h,
          points = {body:getWorldPoints(unpack(points))}
        }
        table.insert(drawfixtures, t)
      end
    end
  end
  love.graphics.setColor(cBox)
  love.graphics.rectangle("fill", mx, my, unpack(boxcheck))

  for _, fixture in ipairs(drawfixtures) do
    if not useFixtureShapePoints then
      love.graphics.setColor(0.2, 0.8, 0.5, 0.50)
      love.graphics.rectangle("fill", fixture.x, fixture.y, fixture.w, fixture.h)
    else
      love.graphics.setColor(1, 0.2, 0.7, 0.90)
      love.graphics.polygon("fill", fixture.points)
    end
  end



  -- draw text
  drawtext("Press Space to switch between drawing with getPoints and getBoundingBox", 10, 10)

  local txtUseShapePoints = "Using shape:getPoints()"
  local txtUseBoundingBox = "Using fixture:getBoundingBox"
  local txt = txtUseBoundingBox  
  if useFixtureShapePoints then
    txt = txtUseShapePoints
  end
  
  drawtext(txt, 10, 30)  
end

function drawtext(text, x, y)
  love.graphics.setColor(0,0,0)
  love.graphics.print(text, x,y)
  love.graphics.setColor(1,1,1)
  love.graphics.print(text, x+1, y+1)
end

useFixtureShapePoints = false
function love.keypressed(key)
  if key == "space" then
    useFixtureShapePoints = not useFixtureShapePoints
  end
end

User avatar
pgimeno
Party member
Posts: 1905
Joined: Sun Oct 18, 2015 2:58 pm
Location: Valencia, ES

Re: Is this a bug with world:queryBoundingBox

Post by pgimeno » Sat Aug 31, 2019 11:34 pm

I think it's correct. The 64 pixels per length unit are making Box2D's internal margins bigger. Using a smaller value makes the margin smaller as well.

zell2002
Citizen
Posts: 75
Joined: Sun Feb 23, 2014 9:22 pm

Re: Is this a bug with world:queryBoundingBox

Post by zell2002 » Sun Sep 01, 2019 8:23 am

But if I'm using 64 pixels for my "Meter" of game world, I can't lower that without experiencing changes in all my current code right?
If I drop 64 to 4 or 8 I assume movement will get faster or something?

why is this so btw ? im drawing the boundingboxes in blue - why are they being picked up 64 pixels away ? seems like you cant trust queryBoundingBox() or you have to do some extra calculations? If so, that is fine - just dont get it..

User avatar
pgimeno
Party member
Posts: 1905
Joined: Sun Oct 18, 2015 2:58 pm
Location: Valencia, ES

Re: Is this a bug with world:queryBoundingBox

Post by pgimeno » Sun Sep 01, 2019 1:55 pm

setMeter is just a divider that LÖVE applies to the length units and to other units where a length intervenes (e.g. speed). You'd get the same result if you divided the units by hand. Lowering the value without adjusting other units accordingly would slow down movement, because your objects would become proportionally bigger and they would take longer to traverse the same path.

queryBoundingBox is designed to capture all fixtures than can possibly affect the given area, without missing any. I'm not surprised it's tuned to err on the side of returning more fixtures rather than fewer. This StackOverflow answer talks about this margin: https://stackoverflow.com/questions/251 ... e#25195793

You can add an AABB check against each returned fixture's bounding box to the query callback, if you want more precise results.

Edit: I've also tried with a sensor, but it requires the other bodies to be kept awake and it still doesn't return the ground (probably because it's not dynamic):

Code: Select all

local mouseposition, boxcheck, boxsearch, boxfound, found_objects, useFixtureShapePoints
local search, drawtext

local function beginContact(f1, f2, c)
if f1 == objects.sensor.fixture then print("begin", f2) end
if f2 == objects.sensor.fixture then print("begin", f1) end
--print("begin", f1, f2, objects.sensor.fixture)
  if f2 == objects.sensor.fixture then
    found_objects[f1] = (found_objects[f1] or 0) + 1
  elseif f1 == objects.sensor.fixture then
    found_objects[f2] = (found_objects[f2] or 0) + 1
  end
end

local function endContact(f1, f2, c)
if f1 == objects.sensor.fixture then print("end", f2) end
if f2 == objects.sensor.fixture then print("end", f1) end
  if f2 == objects.sensor.fixture then
    found_objects[f1] = (found_objects[f1] or 1) - 1
    if found_objects[f1] == 0 then found_objects[f1] = nil end
  elseif f1 == objects.sensor.fixture then
    found_objects[f2] = (found_objects[f2] or 1) - 1
    if found_objects[f2] == 0 then found_objects[f2] = nil end
  end
end

function love.load()
  love.physics.setMeter(64) --the height of a meter our worlds will be 64px
  world = love.physics.newWorld(0, 9.81*14, true) --create a world for the bodies to exist in with horizontal gravity of 0 and vertical gravity of 9.81

  world:setCallbacks(beginContact, endContact)
 
  objects = {} -- table to hold all our physical objects
 
  --let's create the ground
  objects.ground = {}
  objects.ground.body = love.physics.newBody(world, 650/2, 650-50/2) --remember, the shape (the rectangle we create next) anchors to the body from its center, so we have to move it to (650/2, 650-50/2)
  objects.ground.shape = love.physics.newRectangleShape(650, 50) --make a rectangle with a width of 650 and a height of 50
  objects.ground.fixture = love.physics.newFixture(objects.ground.body, objects.ground.shape); --attach shape to body
 
  --let's create a ball
  objects.ball = {}
  objects.ball.body = love.physics.newBody(world, 650/2, 650/2, "dynamic") --place the body in the center of the world and make it dynamic, so it can move around
  objects.ball.shape = love.physics.newCircleShape(20) --the ball's shape has a radius of 20
  objects.ball.fixture = love.physics.newFixture(objects.ball.body, objects.ball.shape, 1) -- Attach fixture to body and give it a density of 1.
  objects.ball.fixture:setRestitution(0.9) --let the ball bounce
 
  --let's create a couple blocks to play around with
  objects.block1 = {}
  objects.block1.body = love.physics.newBody(world, 200, 550, "dynamic")
  objects.block1.shape = love.physics.newRectangleShape(0, 0, 50, 100)
  objects.block1.fixture = love.physics.newFixture(objects.block1.body, objects.block1.shape, 5) -- A higher density gives it more mass.
 
  objects.block2 = {}
  objects.block2.body = love.physics.newBody(world, 200, 400, "dynamic")
  objects.block2.shape = love.physics.newRectangleShape(0, 0, 100, 50)
  objects.block2.fixture = love.physics.newFixture(objects.block2.body, objects.block2.shape, 10)

  objects.sensor = {}
  objects.sensor.body = love.physics.newBody(world, love.mouse.getX(), love.mouse.getY(), "static")
  objects.sensor.shape = love.physics.newRectangleShape(boxcheck[1]/2, boxcheck[2]/2, boxcheck[1], boxcheck[2])
  objects.sensor.fixture = love.physics.newFixture(objects.sensor.body, objects.sensor.shape)
  objects.sensor.fixture:setSensor(true)

  -- This needs to be done for the sensor to work (sigh)
  objects.block1.body:setSleepingAllowed(false)
  objects.block2.body:setSleepingAllowed(false)
  objects.ground.body:setSleepingAllowed(false)
  objects.ball.body:setSleepingAllowed(false)

  --initial graphics setup
  love.graphics.setBackgroundColor(0.41, 0.53, 0.97) --set the background color to a nice blue
  love.window.setMode(650, 650) --set the window dimensions to 650 by 650

  local major, minor, revision, codename = love.getVersion()
  print(major, minor)
end
 
mouseposition = {x=0, y=0}
boxcheck = {100, 50}
boxsearch = {1,1,1, 0.25}
boxfound = {1,0,0, 0.25}
found_objects = {}

function love.update(dt)
  world:update(dt) --this puts the world into motion
 
  --here we are going to create some keyboard events
  if love.keyboard.isDown("right") then --press the right arrow key to push the ball to the right
    objects.ball.body:applyForce(400, 0)
  elseif love.keyboard.isDown("left") then --press the left arrow key to push the ball to the left
    objects.ball.body:applyForce(-400, 0)
  elseif love.keyboard.isDown("up") then --press the up arrow key to set the ball in the air
    objects.ball.body:setPosition(650/2, 650/2)
    objects.ball.body:setLinearVelocity(0, 0) --we must set the velocity to zero to prevent a potentially large velocity generated by the change in position
  end

  local mx, my = love.mouse.getPosition()
  mouseposition.x = mx
  mouseposition.y = my

  objects.sensor.body:setPosition(mx, my)
  objects.sensor.body:setLinearVelocity(0, 0)
--  found_objects = search(mx, my)  
end

--[[
function search(mx, my)
  local found = {}
  local function callback(fixture)
    table.insert(found, fixture)
    -- keep looking
   return true
  end
  
  world:queryBoundingBox(mx, my, mx + boxcheck[1], my + boxcheck[2], callback)

  return found
end
--]]


function love.draw()
  love.graphics.setColor(0.28, 0.63, 0.05) -- set the drawing color to green for the ground
  love.graphics.polygon("fill", objects.ground.body:getWorldPoints(objects.ground.shape:getPoints())) -- draw a "filled in" polygon using the ground's coordinates
 
  love.graphics.setColor(0.76, 0.18, 0.05) --set the drawing color to red for the ball
  love.graphics.circle("fill", objects.ball.body:getX(), objects.ball.body:getY(), objects.ball.shape:getRadius())
 
  love.graphics.setColor(0.20, 0.20, 0.20) -- set the drawing color to grey for the blocks
  love.graphics.polygon("fill", objects.block1.body:getWorldPoints(objects.block1.shape:getPoints()))
  love.graphics.polygon("fill", objects.block2.body:getWorldPoints(objects.block2.shape:getPoints()))

  local mx = mouseposition.x
  local my = mouseposition.y
  local cBox = boxsearch
  local drawfixtures = {}

  if next(found_objects) then
    cBox = boxfound
    for fixture in pairs(found_objects) do
      local topLeftX, topLeftY, bottomRightX, bottomRightY = fixture:getBoundingBox(1)
      local shape = fixture:getShape()
      local body = fixture:getBody()
      
      if shape:type() == "PolygonShape" then
        local points = {shape:getPoints()}
        -- calculate the bounding box size
        local w = math.abs(topLeftX - bottomRightX)
        local h = math.abs(topLeftY - bottomRightY)
        local t = {
          x = topLeftX,
          y = topLeftY,
          w = w,
          h = h,
          points = {body:getWorldPoints(unpack(points))}
        }
        table.insert(drawfixtures, t)
      end
    end
  end
  love.graphics.setColor(cBox)
  love.graphics.rectangle("fill", mx, my, unpack(boxcheck))

  for _, fixture in ipairs(drawfixtures) do
    if not useFixtureShapePoints then
      love.graphics.setColor(0.2, 0.8, 0.5, 0.50)
      love.graphics.rectangle("fill", fixture.x, fixture.y, fixture.w, fixture.h)
    else
      love.graphics.setColor(1, 0.2, 0.7, 0.90)
      love.graphics.polygon("fill", fixture.points)
    end
  end



  -- draw text
  drawtext("Press Space to switch between drawing with getPoints and getBoundingBox", 10, 10)

  local txtUseShapePoints = "Using shape:getPoints()"
  local txtUseBoundingBox = "Using fixture:getBoundingBox"
  local txt = txtUseBoundingBox  
  if useFixtureShapePoints then
    txt = txtUseShapePoints
  end
  
  drawtext(txt, 10, 30)  
end

function drawtext(text, x, y)
  love.graphics.setColor(0,0,0)
  love.graphics.print(text, x,y)
  love.graphics.setColor(1,1,1)
  love.graphics.print(text, x+1, y+1)
end

useFixtureShapePoints = false
function love.keypressed(key)
  if key == "space" then
    useFixtureShapePoints = not useFixtureShapePoints
  end
end

Post Reply

Who is online

Users browsing this forum: No registered users and 6 guests