Page 1 of 1

[Solved] Checking rotated 2D camera - world boundary

Posted: Mon Feb 09, 2015 7:09 pm
by zorg
In the past weeks, i've been working on a camera system, and it's almost ready in its most basic form; the problem is, i've yet to figure out the following:
In its update function, given the world's and the viewport's dimensions, and the camera's next calculated position, orientation and scale, check whether the viewport is wholly inside of the world, and if not, set it so it stays inside of the world, and does not show anything (the void) outside.

Here's the relevant code that i've been trying to append with the orientation detection:

Code: Select all

-- cam.update(cam,dt) -- uses cam instead of implicit self
-- Limit the camera to the world's boundary, if checks are enabled

	-- cam.x and cam.y are the position of the camera, that is, the middle of the viewport; cam.o is its orientation, and cam.s is the scale.
	if cam.checkBounds then

		-- boundary checking helpers
		local topLeft = {}

		topLeft.x = cam.x - (viewport.width  / 2) / cam.s
		topLeft.y = cam.y - (viewport.height / 2) / cam.s

		-- check if coords are inside the world boundary
		if topLeft.x < 0 then
			cam.x = (viewport.width  / 2) / cam.s
		end
		if topLeft.x > world.width - (viewport.width / cam.s)  then
			cam.x = world.width - (viewport.width  / 2) / cam.s
		end
		if topLeft.y < 0 then
			cam.y = (viewport.height / 2) / cam.s
		end
		if topLeft.y > world.height - (viewport.height / cam.s) then
			cam.y = world.height - (viewport.height / 2) / cam.s
		end

	end
Any help would be appreciated :3

Re: Checking rotated 2D camera - world boundary

Posted: Mon Feb 09, 2015 8:43 pm
by micha
If I understand correctly you want to know the formula for the position of the screen corners, if the camera is rotated by the angle o.
Here you go:

Code: Select all

local dx = viewport.width/2/cam.s
local dy = viewport.height/2/cam.s
topLeft.x = cam.x - dx * math.cos(cam.o) - dy * math.sin(cam.o)
topLeft.y = cam.y + dx * math.sin(cam.o) - dy * math.cos(cam.o)
Usually I screw up the signs (+/-) before the dx and dy. If the above formula is incorrect, then try replacing some + with - or vice versa.

Re: Checking rotated 2D camera - world boundary

Posted: Tue Feb 10, 2015 2:29 am
by zorg
Thanks micha, that worked! Though i still needed to modify the boundary checks to account for all corners; hopefully the following code is correct; i did test it, and it worked flawlessly for me, so i'm hopeful that it has not any bugs :3

Code: Select all

if cam.checkBounds then

		-- boundary checking helpers
		local topLeft = {}
		local bottomRight = {}

		local hx = viewport.width  / 2
		local hy = viewport.height / 2

		local dx = hx / cam.s
		local dy = hy / cam.s

		local ax = -dx * math.cos(cam.o) - dy * math.sin(cam.o)
		local ay =  dx * math.sin(cam.o) - dy * math.cos(cam.o)

		topLeft.x = cam.x + ax
		topLeft.y = cam.y + ay

		bottomRight.x = cam.x - ax
		bottomRight.y = cam.y - ay

		-- check if the viewport is wholly inside the world boundary, and move it back, if not

		if topLeft.x < 0 then cam.x = 0 - ax end
		if topLeft.y < 0 then cam.y = 0 - ay end

		if bottomRight.x < 0 then cam.x = 0 + ax end
		if bottomRight.y < 0 then cam.y = 0 + ay end

		if bottomRight.x > world.width  + dx + ax then cam.x = world.width  + ax end
		if bottomRight.y > world.height + dy + ay then cam.y = world.height + ay end

		if topLeft.x > world.width  + dx - ax then cam.x = world.width  - ax end
		if topLeft.y > world.height + dy - ay then cam.y = world.height - ay end

	end
Edit: nope, there're still a few cases where it shows the outside of the world in some corners, i'll try to fix it. Seems to be the top-right and bottom-left that are problematic; probably will need 8 more if-s.

Re: Checking rotated 2D camera - world boundary

Posted: Tue Feb 10, 2015 6:26 am
by micha
Yes, you need to check for all four corners, if the camera is rotated.

You can reduce the number of if-statements a little bit: First calculate the x coordinate of all four corners. Then find the corner with the largest x-coordinate

Code: Select all

largestX = math.max(topleft.x,topright.x,bottomleft.x,bottomright.x)
And then only compare this largest x to the world boundary:

Code: Select all

if largestX > world.width then
  cam.x = cam.x - (largestX - world.width)
end
Repeat for smallest x and largest and smallest y.

The quantity (largestX-world.width) is the amount of overlap.

Re: Checking rotated 2D camera - world boundary

Posted: Tue Feb 10, 2015 8:12 am
by s-ol
micha wrote:Yes, you need to check for all four corners, if the camera is rotated.

You can reduce the number of if-statements a little bit: First calculate the x coordinate of all four corners. Then find the corner with the largest x-coordinate

Code: Select all

largestX = math.max(topleft.x,topright.x,bottomleft.x,bottomright.x)
And then only compare this largest x to the world boundary:

Code: Select all

if largestX > world.width then
  cam.x = cam.x - (largestX - world.width)
end
Repeat for smallest x and largest and smallest y.

The quantity (largestX-world.width) is the amount of overlap.

Code: Select all

function minmax( min, max, va, vb )
  return math.min(min, va), math.max(max, vb)
end
local maxx, maxy = math.huge, math.huge
local minx, miny = -math.huge, -math.huge
for i,v in ipairs( rects ) do -- or whatever floats your boat
  minx, maxx = minmax( minx, maxx, v.x, v.x+v.w )
  miny, maxy = minmax( miny, maxy, v.y, v.y+v.h )
---- or ----
  minx = math.min( minx, v.x )
  miny = math.min ( miny, v.y )
  maxx = math.max( maxx, v.x+v.w )
  maxy = math.max( maxy, v.y+v.h )
end

Re: Checking rotated 2D camera - world boundary

Posted: Tue Feb 10, 2015 11:10 am
by kikito
Hi there,

I have fought with this problem in my camera library, gamera.

Here's the (private) function which calculates the visible area (width and height) based on the angle, zoom factor, and original width and height of the viewport:

Code: Select all

local function getVisibleArea(self, scale)
  scale = scale or self.scale
  local sin, cos = abs(self.sin), abs(self.cos)
  local w,h = self.w / scale, self.h / scale
  w,h = cos*w + sin*h, sin*w + cos*h
  return min(w,self.ww), min(h, self.wh)
end
You can use that to adjust the position of the camera so that it does not go beyond the limits of the world.

Code: Select all

local function adjustPosition(self)
  local wl,wt,ww,wh = self.wl, self.wt, self.ww, self.wh
  local w,h = getVisibleArea(self)
  local w2,h2 = w*0.5, h*0.5

  local left, right  = wl + w2, wl + ww - w2
  local top,  bottom = wt + h2, wt + wh - h2

  self.x, self.y = clamp(self.x, left, right), clamp(self.y, top, bottom)
end

Re: Checking rotated 2D camera - world boundary

Posted: Wed Feb 11, 2015 7:05 pm
by zorg
S0lll0s wrote:(...)
kikito wrote:(...)
Thanks to both of you for helping!

kikito, I tried to somehow integrate your solution into my code, that apparently handled the cam position a bit differently, and i couldn't believe it, it's finally working! One question though, in this case, that you gave two functions, and i modified them to my needs, would i need to paste your license into this, or would an attribution line suffice (like --this part adapted from kikito's gamera <link>-- or something), or how does this work? I'm a bit new to this specific thing. :huh:

For posterity, the final code (sans the clamp function):

Code: Select all

if cam.checkBounds then

	-- scale the viewport
	local dx = viewport.width  / cam.s
	local dy = viewport.height / cam.s

	-- calculate half of that
	local hx = dx / 2
	local hy = dy / 2

	-- this would be your w,h = cos*w + sin*h, sin*w+cos*h code from getVisibleArea(), along with the line below it, and
	-- the line local w2,h2 = w*0.5, h*0.5 from adjustPosition()
	local ax = math.min((math.abs(math.cos(cam.o))*dx + math.abs(math.sin(cam.o))*dy),world.width ) / 2
	local ay = math.min((math.abs(math.sin(cam.o))*dx + math.abs(math.cos(cam.o))*dy),world.height) / 2

	-- from adjustPosition()
	local left, right = 0 + ax, 0 + world.width  - ax
	local top, bottom = 0 + ay, 0 + world.height - ay

	-- quick and dirty inline, for test purposes only
	local clamp = function(n,min,max)
		if min>max then min, max = max, min end
		return math.min(math.max(n, min), max)
	end

	cam.x = clamp(cam.x,left,right)
	cam.y = clamp(cam.y,top,bottom)

end
Thanks again everyone, for all the help. I'll release this as soon as possible.

Re: Checking rotated 2D camera - world boundary

Posted: Thu Feb 12, 2015 10:51 am
by kikito
Hi, I'm glad you got it working!

Your code is different enough from mine so no you don't need to include my license at all. A reference/link somewhere in your readme (a "credits" section, maybe?) or similar would be nice, but it's totally ok if you don't do it.