Log Stash

as an Industrial Personnel

프로그래밍

쿼터뷰 좌표계를 직교 좌표계로 변환해서 현재 위치 타일 좌표 검사하기

SavvyTuna 2015. 1. 27. 13:10



방향키로 조종하면 왼쪽의 쿼터뷰에 있는 글자가 움직인다.


제목은 길게도 써놨는데 별거 없다. 고1 때였나 아무튼, 저런 쿼터뷰 형식의 게임 맵에서 현재 플레이어가 어느 타일 위치에 있는지를 알아야 했었다.(아마) 그래서 그때 당시에는 모든 마름모의 각 변을 일차 함수로 표현하고 플레이어 좌표가 함수값 위에 있는지 아래에 있는지 검사하는 방법을 썼었다. 타일 갯수만큼 반복문이 들어가기 때문에 당연히 갯수가 많아질수록 점점 느려지더라.

선형대수 시간에 벡터공간 파트에서 기저벡터에 대한 설명을 듣다가 저 문제가 생각났다. (그 때 이후에도 몇번 쿼터뷰에 대한 고찰을 할 기회가 있었지만 그냥 별 생각 안하고 있었다)


평소에 자주 쓰는 직교 좌표계는 (1,0)과 (0,1)을 기저벡터로 삼는 벡터공간이라고 할 수 있는데, 마찬가지로 쿼터뷰(Isometric)는 (사이각을 120도로 잡고) 직교 좌표계로부터 x축 기저벡터를 -30도 회전 시켜서 (√3/2, -1/2)와 (0,1)을 기저벡터로 삼는 공간으로 볼 수 있다.


다른 기저벡터를 가진 벡터공간으로의 변환은 추이행렬(Transition matrix)를 이용하는데, 이 행렬을 만들기 위해선 그냥 기저벡터를 세로로 써주면 된다. 직교 좌표계에서 쿼터뷰로의 추이행렬은 아래와 같다.



쿼터뷰에서 직교좌표계로의 변환은 위의 추이행렬에 대한 역행렬을 구해주면 된다.


이렇게 하면 '쿼터뷰 좌표계 -> 직교 좌표계 -> 그냥 나눗셈으로 타일 위치 계산'으로 반복문 없이 타일 위치를 얻어낼 수 있다.


사실 쿼터뷰 좌표계에서 모든 로직이 다 구성되면 필요없긴한데, 하다보면 이런게 필요할때도 있고 저런게 필요할때도 있는 법. 마우스 클릭 이벤트가 들어왔는데 어떤 타일을 클릭한건지 알기 위해서 사용할수도 있겠지.


문법 강조기에 루아가 없어서 파이썬으로 설정 해놓았다. gist 가서 보는게 나을듯. 위에선 2x2 행렬로 표현했지만 2차원에서 아핀변환을 하기위해 3x3 행렬을 사용했다. 행렬 라이브러리는 코드 맨 위의 주석 코드를 그대로 가져다 썼고.


Love2D에서 돌아가는 루아 코드이다.

-- https://github.com/davidm/lua-matrix/blob/master/lua/matrix.lua
local matrix = require 'matrix'

-- Origin x,y (Screen coord)
Ix0, Iy0 = 93, 329; -- Isometric
Dx0, Dy0 = 450, 329; -- Descartes

-- invert y axis, move to Dx0, Dy0
descartesToScreenMatrix = (matrix{{1, 0, Dx0}, {0, 1, Dy0}, {0, 0, 1}} * matrix{{1, 0, 0}, {0, -1, 0}, {0, 0, 1}})
-- Basis Vector I is (sqrt(3)/2, -1/2), J is (0, 1)
descartesToIsometricMatrix = matrix{{0.86602, 0, 0}, {-0.5, 1, 0}, {0, 0, 1}};
-- invert D to I
isometricToDescartesMatrix = matrix.invert(descartesToIsometricMatrix);
-- invert y axis, move to Ix0, Iy0
isometricToScreenMatrix = (matrix{{1, 0, Ix0}, {0, 1, Iy0}, {0, 0, 1}} * matrix{{1, 0, 0}, {0, -1, 0}, {0, 0, 1}})

-- Player is in the ISOMETRIC coord.
playerX = 0;
playerY = 0;

-- screen coords.
descartesScreenX, descartesScreenY = 0,0;
isometircScreenX, isometricScreenY = 0,0;

-- transformed player position (in descartes)
desPosX, desPosY = 0,0;

function love.init()
	-- nothing to do.
end

function love.update()

	local amt = 1;

	-- move player's coord (screen coord sys)

	if love.keyboard.isDown("left") or love.keyboard.isDown("a") then
		playerX = playerX - amt;
	end
	if love.keyboard.isDown("right") or love.keyboard.isDown("d") then
		playerX = playerX + amt;
	end
	if love.keyboard.isDown("up") or love.keyboard.isDown("w") then
		playerY = playerY + amt;
	end
	if love.keyboard.isDown("down") or love.keyboard.isDown("s") then
		playerY = playerY - amt;
	end

	isometircScreenX, isometricScreenY 	= IsometricToScreenCoordinate(playerX, playerY);
	desPosX, desPosY 					= IsometricToDescartesCoordinate(playerX, playerY);
	descartesScreenX, descartesScreenY 	= DescartesToScreenCoordinate(desPosX, desPosY);

end

function love.draw()

	love.graphics.setColor(255, 255, 255, 255);

	-- info
	love.graphics.print("[Use WASD or Arrow keys to move, you're moving player in isometric]", 0, 0);
	love.graphics.print("Descartes player Position "..math.floor(desPosX).." , "..math.floor(desPosY), 0, 30);
	love.graphics.print("Player is on Tile ("..math.floor(desPosX/20).." , "..math.floor(desPosY/20)..")", 0, 60);

	-- display 'descartes'
	love.graphics.print("DESCARTES", descartesScreenX, descartesScreenY);

	-- display 'isometric'
	love.graphics.print("ISOMETRIC", isometircScreenX, isometricScreenY);

	-- draw graphs
	DrawGraph("Transformed", DescartesToScreenCoordinate);
	DrawGraph("Game Map", DescartesToIsometricScreenCoordinate);

end

function DrawGraph(title, transfromFunction)

	drawLine = function(x0, y0, x1, y1)
		local xBegin, yBegin = transfromFunction(x0, y0);
		local xEnd, yEnd = transfromFunction(x1, y1);
		love.graphics.line(xBegin, yBegin, xEnd, yEnd);
	end

	local x,y = 0,0;

	x2,y2 = transfromFunction(x,y + 220);
	love.graphics.print(title, x2, y2);

	drawLine(x, y, x, y + 200);
	drawLine(x, y, x + 200, y);

	love.graphics.setColor(255, 255, 255, 128);

	for i = 0, 10 do
		drawLine(x + i * 20, y, x + i * 20, y+200) -- y
	end

	for i = 0, 10 do
		drawLine(x, y + i * 20, x+200, y + i * 20) -- x
	end
	
	love.graphics.setColor(255, 255, 255, 255);

end

-- matrix * vector (transform) functions

function DescartesToScreenCoordinate(x, y)
	descartesPosition = matrix{{x}, {y}, {1}};
	screenPosition = descartesToScreenMatrix * descartesPosition;

	return screenPosition[1][1], screenPosition[2][1];
end

function DescartesToIsometricCoordinate(x, y)
	descartesPosition = matrix{{x}, {y}, {1}};
	isometricPosition = descartesToIsometricMatrix * descartesPosition;

	return isometricPosition[1][1], isometricPosition[2][1];
end

function IsometricToDescartesCoordinate(x, y)
	isometricPosition = matrix{{x}, {y}, {1}};
	descartesPosition = isometricToDescartesMatrix * isometricPosition;

	return descartesPosition[1][1], descartesPosition[2][1];
end

function IsometricToScreenCoordinate(x, y)
	isometricPosition = matrix{{x}, {y}, {1}};
	screenPosition = isometricToScreenMatrix * isometricPosition;

	return screenPosition[1][1], screenPosition[2][1];
end

-- only for isometric graph drawing
function DescartesToIsometricScreenCoordinate(x, y)
	isometricPosition = matrix{{x}, {y}, {1}};
	screenPosition = isometricToScreenMatrix * descartesToIsometricMatrix * isometricPosition;

	return screenPosition[1][1], screenPosition[2][1];
end


'프로그래밍' 카테고리의 다른 글

Cost Amortization  (0) 2016.10.30
Advanced GIT 발표자료  (0) 2016.08.13
라그랑주 보간법을 이용한 달팽이 배열 만들기  (0) 2015.01.26
알고스팟 weird 문제  (2) 2015.01.24
알고스팟 Boggle 문제  (0) 2015.01.24