Quick Links: Download Gideros Studio | Gideros Documentation | Gideros Development Center | Gideros community chat | DONATE
Help with rectangle intersection code — Gideros Forum

Help with rectangle intersection code

GhandoGhando Member
edited February 2012 in General questions
Hi guys,
I'd been using a collision detection function for rotated rectangles - original code: http://forums.xkcd.com/viewtopic.php?f=11&t=63710 - and had it working in another SDK.

I've changed the code over to Gideros but am not seeing accurate collision detection. I'm sure it's something I'm doing - not taking into account the correct angle? Position? Anchor point? - but can't work out what.

Any help would be greatly appreciated.
 
	rect1 = Shape.new()
	rect1:setLineStyle(3, 0x000000)
	rect1:setFillStyle(Shape.SOLID, 0xff0000, 0.5)
	rect1:beginPath()
	rect1:moveTo(0, 0)
	rect1:lineTo(100, 0)
	rect1:lineTo(100, 50)
	rect1:lineTo(0, 50)
	rect1:closePath()
	rect1:endPath()
	rect1:setPosition(100, 100)
	rect1:setRotation(45)
	stage:addChild(rect1)
 
	rect2 = Shape.new()
	rect2:setLineStyle(3, 0x000000)
	rect2:setFillStyle(Shape.SOLID, 0xff0000, 0.5)
	rect2:beginPath()
	rect2:moveTo(0, 0)
	rect2:lineTo(100, 0)
	rect2:lineTo(100, 50)
	rect2:lineTo(0, 50)
	rect2:closePath()
	rect2:endPath()
	rect2:setPosition(200, 200)	
	stage:addChild(rect2)
 
function onMouseDown(self, event)
	if self:hitTestPoint(event.x, event.y) then
		self.isFocus = true
 
		self.x0 = event.x
		self.y0 = event.y
 
		event:stopPropagation()
	end
end
 
function onMouseMove(self, event)
	if self.isFocus then
		local dx = event.x - self.x0
		local dy = event.y - self.y0
 
		self:setX(self:getX() + dx)
		self:setY(self:getY() + dy)
 
		self.x0 = event.x
		self.y0 = event.y
 
		event:stopPropagation()
	end
end
 
function onMouseUp(self, event)
	if self.isFocus then
		self.isFocus = false
		event:stopPropagation()
	end
end
 
rect1:addEventListener(Event.MOUSE_DOWN, onMouseDown, rect1)
rect1:addEventListener(Event.MOUSE_MOVE, onMouseMove, rect1)
rect1:addEventListener(Event.MOUSE_UP, onMouseUp, rect1)
 
 function checkForIntersection(a, b)
	 local function checkRotated(a, b)
			-- we put everything into a coordinate system where rectangle a is centered at the origin
			-- with no rotation.  i.e., it's corners are (-a.w/2,-a.h/2) and (a.w/2,a.h/2)
			--
			-- we'll need these a lot
			a_w2 = a:getWidth()/2.0
			a_h2 = a:getHeight()/2.0
			b_w2 = b:getWidth()/2.0
			b_h2 = b:getHeight()/2.0
			--
			-- first we put the center of b into this system
			-- translate
			b_xc_tmp = (b:getX()+b_w2) - (a:getX()+a_w2)
			b_yc_tmp = (b:getY()+b_h2) - (a:getY()+a_h2)
			-- rotate by -a.theta
			c = math.cos(-a:getRotation())
			s = math.sin(-a:getRotation())
			b_xc = b_xc_tmp*c - b_yc_tmp*s
			b_yc = b_yc_tmp*c + b_xc_tmp*s
			--
			-- next we compute each corner of rectangle b relative to (b_xc,b_yc)
			theta = b:getRotation() - a:getRotation()
			c = math.cos(theta)
			s = math.sin(theta)
 
			-- because we're rotating around the center, we have some symmetry
			b_x1 = b_w2*c - b_h2*s
			b_y1 = b_w2*s + b_h2*c
			b_x2 = b_w2*c + b_h2*s
			b_y2 = b_w2*s - b_h2*c
 
			-- find the bounding rectangle
			b_xmin = b_xc + math.min(b_x1, b_x2, -b_x1, -b_x2)
			b_xmax = b_xc + math.max(b_x1, b_x2, -b_x1, -b_x2)
			b_ymin = b_yc + math.min(b_y1, b_y2, -b_y1, -b_y2)
			b_ymax = b_yc + math.max(b_y1, b_y2, -b_y1, -b_y2)
			--
			-- check for intersection with rectangle a
			return b_xmax < -a_w2 or b_xmin > a_w2 or b_ymax < -a_h2 or b_ymin > a_h2
	 end
    return not (checkRotated(a, b) or checkRotated(b, a))
 end
 
 function onEnterFrame(self, event)
    myvar = checkForIntersection(rect1, rect2)
    if myvar then
		rect1:setColorTransform(0,139,0,1)
    else
		rect1:setColorTransform()
	end
 end
 
 stage:addEventListener(Event.ENTER_FRAME, onEnterFrame)

Comments

  • ar2rsawseenar2rsawseen Maintainer
    edited February 2012
    I think problem is in rotation
    c = math.cos(-a:getRotation())
    s = math.sin(-a:getRotation())
    a:getRotation() returns rotation in degrees, but math.cos and math.sin accepts rotation in radians, you need to recalculate that
    c = math.cos(math.rad(-a:getRotation()))
    s = math.sin(math.rad(-a:getRotation()))
    Same in other sections of your code, when you perform this calculations
  • Whoops! Thanks ar2rsawseen, I posted the wrong code - had already added the math.rad to the version I'm testing but am still seeing the same issue.

    Using the same code but without a rotation works fine, so I'm guessing that I'm not working with the correct points of my rotated rectangle??
  • Well by the way you draw your rectangle, you are rotating it by bottom left corner.

    If you want to rotate it by center, you should draw it like this:
    rect1:beginPath()
    rect1:moveTo(-50, -25)
    rect1:lineTo(50, -25)
    rect1:lineTo(50, 25)
    rect1:lineTo(-50, 25)
    rect1:lineTo(-50, -25)
    rect1:endPath()
    Didn't test but I hope you get the idea.
  • Thanks ar2rsawseen but I'm having the same issue if I replace the shapes with images eg:
    local rect1 = Bitmap.new(Texture.new("rectangle.png"))
    rect1:setAnchorPoint(0.5, 0.5)
    rect1:setPosition(100, 100)
    rect1:setRotation(45)
    stage:addChild(rect1)
     
    local rect2 = Bitmap.new(Texture.new("rectangle.png"))
    rect2:setAnchorPoint(0.5, 0.5)
    rect2:setPosition(100, 100)
    rect2:setRotation(45)
    stage:addChild(rect2)
    Code works if both rectangles aren't rotated, breaks if one of them is rotated.

    As the code works in the another LUA SDK I guess I'm making an incorrect assumption with Gideros positioning and then calculating the corners of the rectangle.
  • Ok, here's another sample that worked fine in Corona but not in Gideros (original code can be found at: http://developer.anscamobile.com/forum/2012/02/02/collision-detection-rotated-rectangles)

    I'm seeing the same problem with "collisions" occurring even though objects aren't overlapping.

    In the following both .png are 60px x 60px

    What am I doing wrong?
     
    local rect1 = Bitmap.new(Texture.new("blue.png"))
    rect1:setPosition(100, 100)
    rect1:setRotation(45)
    stage:addChild(rect1)
     
    local rect2 = Bitmap.new(Texture.new("green.png"))
    rect2:setPosition(200, 200)
    stage:addChild(rect2)
     
    function onMouseDown(self, event)
    	if self:hitTestPoint(event.x, event.y) then
    		self.isFocus = true
     
    		self.x0 = event.x
    		self.y0 = event.y
     
    		event:stopPropagation()
    	end
    end
     
    function onMouseMove(self, event)
    	if self.isFocus then
    		local dx = event.x - self.x0
    		local dy = event.y - self.y0
     
    		self:setX(self:getX() + dx)
    		self:setY(self:getY() + dy)
     
    		self.x0 = event.x
    		self.y0 = event.y
     
    		event:stopPropagation()
    	end
    end
     
    function onMouseUp(self, event)
    	if self.isFocus then
    		self.isFocus = false
    		event:stopPropagation()
    	end
    end
     
    rect1:addEventListener(Event.MOUSE_DOWN, onMouseDown, rect1)
    rect1:addEventListener(Event.MOUSE_MOVE, onMouseMove, rect1)
    rect1:addEventListener(Event.MOUSE_UP, onMouseUp, rect1)
     
    rect2:addEventListener(Event.MOUSE_DOWN, onMouseDown, rect2)
    rect2:addEventListener(Event.MOUSE_MOVE, onMouseMove, rect2)
    rect2:addEventListener(Event.MOUSE_UP, onMouseUp, rect2)
     
    function checkForIntersection(rectangle1, rectangle2)
     
       local function chkForRotated(r1, r2)
          local x1 = r2:getX()-r1:getX()
          local y1 = r2:getY()-r1:getY()
     
          local x2 = x1*math.cos(math.rad(r1:getRotation())) + y1*math.sin(math.rad(r1:getRotation()))
          local y2 = y1*math.cos(math.rad(r1:getRotation())) - x1*math.sin(math.rad(r1:getRotation()))
     
          local angle1 = math.cos(math.rad(r2:getRotation() - r1:getRotation()))
          local angle2 = math.sin(math.rad(r2:getRotation() - r1:getRotation()))
          local arr1 = { r2:getWidth()*angle1, -r2:getHeight()*angle2, r2:getWidth()*angle1 - r2:getHeight()*angle2}
          local arr2 = { r2:getWidth()*angle2, r2:getHeight()*angle1, r2:getWidth()*angle2 + r2:getHeight()*angle1}
     
          local minX = 0 
          local maxX = 0
          local minY = 0
          local maxY = 0
          for i=1,3 do
                    if minX > arr1[i] then
                            minX = arr1[i]
                    end
                    if maxX <  arr1[i] then
                             maxX = arr1[i]
                    end
                    if minY > arr2[i] then
                            minY = arr2[i]
                    end
                    if maxY <  arr2[i] then
                             maxY = arr2[i]
                    end
          end
     
          return x2 + maxX < 0 or x2 + minX > r1:getWidth() or y2 + maxY < 0 or y2 + minY > r1:getHeight()
       end
       return not (chkForRotated(rectangle1,rectangle2) or chkForRotated(rectangle2,rectangle1))
    end
     
     function onEnterFrame(self, event)
        myvar = checkForIntersection(rect1, rect2)
        if myvar then
    		rect1:setColorTransform(0,139,0,1)
        else
    		rect1:setColorTransform()
    	end
     end
     
     stage:addEventListener(Event.ENTER_FRAME, onEnterFrame)
  • atilimatilim Maintainer
    edited February 2012
    Hi,

    The problem is you call getWidth() and getHeight() after rotating the sprite. Therefore, they return bigger bounds. If you store the result of getWidth() and getHeight() before rotating and use the stored width/height, everything works well:
    local rect1 = Bitmap.new(Texture.new("blue.png"))
    rect1.width = rect1:getWidth()
    rect1.height = rect1:getHeight()
    rect1:setPosition(100, 100)
    rect1:setRotation(45)
    stage:addChild(rect1)
     
    local rect2 = Bitmap.new(Texture.new("green.png"))
    rect2.width = rect2:getWidth()
    rect2.height = rect2:getHeight()
    rect2:setPosition(200, 200)
    stage:addChild(rect2)
     
    function onMouseDown(self, event)
    	if self:hitTestPoint(event.x, event.y) then
    		self.isFocus = true
     
    		self.x0 = event.x
    		self.y0 = event.y
     
    		event:stopPropagation()
    	end
    end
     
    function onMouseMove(self, event)
    	if self.isFocus then
    		local dx = event.x - self.x0
    		local dy = event.y - self.y0
     
    		self:setX(self:getX() + dx)
    		self:setY(self:getY() + dy)
     
    		self.x0 = event.x
    		self.y0 = event.y
     
    		event:stopPropagation()
    	end
    end
     
    function onMouseUp(self, event)
    	if self.isFocus then
    		self.isFocus = false
    		event:stopPropagation()
    	end
    end
     
    rect1:addEventListener(Event.MOUSE_DOWN, onMouseDown, rect1)
    rect1:addEventListener(Event.MOUSE_MOVE, onMouseMove, rect1)
    rect1:addEventListener(Event.MOUSE_UP, onMouseUp, rect1)
     
    rect2:addEventListener(Event.MOUSE_DOWN, onMouseDown, rect2)
    rect2:addEventListener(Event.MOUSE_MOVE, onMouseMove, rect2)
    rect2:addEventListener(Event.MOUSE_UP, onMouseUp, rect2)
     
    function checkForIntersection(rectangle1, rectangle2)
     
       local function chkForRotated(r1, r2)
          local x1 = r2:getX()-r1:getX()
          local y1 = r2:getY()-r1:getY()
     
          local x2 = x1*math.cos(math.rad(r1:getRotation())) + y1*math.sin(math.rad(r1:getRotation()))
          local y2 = y1*math.cos(math.rad(r1:getRotation())) - x1*math.sin(math.rad(r1:getRotation()))
     
          local angle1 = math.cos(math.rad(r2:getRotation() - r1:getRotation()))
          local angle2 = math.sin(math.rad(r2:getRotation() - r1:getRotation()))
          local arr1 = { r2.width*angle1, -r2.height*angle2, r2.width*angle1 - r2.height*angle2}
          local arr2 = { r2.width*angle2, r2.height*angle1, r2.width*angle2 + r2.height*angle1}
     
          local minX = 0 
          local maxX = 0
          local minY = 0
          local maxY = 0
          for i=1,3 do
                    if minX > arr1[i] then
                            minX = arr1[i]
                    end
                    if maxX <  arr1[i] then
                             maxX = arr1[i]
                    end
                    if minY > arr2[i] then
                            minY = arr2[i]
                    end
                    if maxY <  arr2[i] then
                             maxY = arr2[i]
                    end
          end
     
          return x2 + maxX < 0 or x2 + minX > r1.width or y2 + maxY < 0 or y2 + minY > r1.height
       end
       return not (chkForRotated(rectangle1,rectangle2) or chkForRotated(rectangle2,rectangle1))
    end
     
     function onEnterFrame(self, event)
        myvar = checkForIntersection(rect1, rect2)
        if myvar then
    		rect1:setColorTransform(0,139,0,1)
        else
    		rect1:setColorTransform()
    	end
     end
     
     stage:addEventListener(Event.ENTER_FRAME, onEnterFrame)
  • Thanks Atilim. That's been stumping me for weeks. I should have been tipped off when the docs referred to "content bounds".
  • atilimatilim Maintainer
    edited March 2012
    Yes, I agree. there is a possibility of misunderstanding here. I'll update the reference manual to be more clear.

Sign In or Register to comment.