嘿~ 鐵匠史密斯又來啦
經過前兩天的推導,我們已經讓光線能夠依照方向一步步地走,並且記錄它的座標。
今天要聊的,是光在不同情況下該怎麼處理:
地圖 map
的邊界,照理說就是世界盡頭
一旦光線超出邊界,我們就當作它撞到一堵「無形的牆」
// Check if ray is out of bounds
if ((nTestX < 0) || (nTestX >= nMapWidth) || (nTestY < 0) || (nTestY >= nMapHeight))
{
bHitWall = true; // Out of bounds, we hit the wall
fDistanceToWall = fDepth; // Set distance to the maximum depth
}
其中 fDepth
就是我們地圖 map
的最大距離,目前是 16 x 16
,所以玩家的最大可視距離也就是 光可以走的最大距離:
float fDepth{ 16.0f }; // Depth of the raycaster
目前以光行走的最大距離來避免光線走到地圖外圍
日後會有更好的光程檢測方式
如果光線還在地圖內,就要檢查它是不是撞到了世界內的牆(#
)。
檢查方式就是用 map
的索引 找對應位置的符號:
else // If ray is still in bounds
{
if (map[nTestY * nMapWidth + nTestX] == '#') // If we hit a wall
{
bHitWall = true;
}
}
沒錯,我們地圖 map
是 1-D 的 wstring
陣列,所以查看map
上特定位置 index
為index = nTestY * nMapWidth + nTestX
利用 2-D 座標轉換成 1-D wstrin
陣列索引,才可以找出該位置的值: #
or
這樣的話,只要 bHitwall = true
-> 代表光已經碰到牆壁 -> 停止前進並得到距離 fDistanceToWall
一旦碰到牆,得到距離 fDistanceToWall
後,一切的光資訊就獲得了
我們就可以依照每道光的資訊,來決定牆在 screen
上的高度與亮度
我們繼續走下去~~~
以下是目前的 code:
#include <iostream>
#include <Windows.h>
#include <string>
using namespace std;
int nMapWidth { 16 };
int nMapHeight { 16 };
float fPlayerX{ 8.0f };
float fPlayerY{ 3.0f };
float fPlayerA{ 0.0f }; // Player angle in radians
float fAOV{ 3.14159f / 4.0f }; // Angle of view in radians
float fDepth{ 16.0f }; // Depth of the raycaster
int main()
{
const int nScreenWidth { 120 };
const int nScreenHeight { 40 };
// Create the canvas
wchar_t* screen{ new wchar_t[nScreenWidth * nScreenHeight] };
// Create elements in screen
for (int i = 0; i < nScreenWidth * nScreenHeight; i++)
{
if (i < 1200)
screen[i] = L' ';
else
screen[i] = L'%';
}
screen[nScreenWidth * nScreenHeight - 1] = '\0'; // null terminator
// Create console handler (custom screen buffer)
HANDLE hConsole{ CreateConsoleScreenBuffer(
GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL) };
// Set Active Screen Buffer so that console will show this buffer first
SetConsoleActiveScreenBuffer(hConsole);
DWORD dwBytesWritten{ 0 };
// Create world map
std::wstring map{};
map += L"################";
map += L"#..............#";
map += L"#..............#";
map += L"#..............#";
map += L"#..............#";
map += L"#..............#";
map += L"#..............#";
map += L"#..............#";
map += L"#..............#";
map += L"#..............#";
map += L"#..............#";
map += L"#..............#";
map += L"#..............#";
map += L"#..............#";
map += L"#..............#";
map += L"################";
while (true) {
// Axis going across the screen
for (int x = 0; x < nScreenWidth; x++)
{
// for each column, calculate the projected ray angle into the world space
float fRayAngle{ (fPlayerA + fAOV / 2.0f) - ((float)x / (float)nScreenWidth) * fAOV };
// Track distance from player to the wall
float fDistanceToWall{ 0.0f };
bool bHitWall{ false }; // Whether we hit a wall
// Unit vector of the ray direction so that we can take one step on that direction to see if we hit a wall
float fEyeX{ sinf(fRayAngle) };
float fEyeY{ cosf(fRayAngle) };
// Unit vector for the ray -> we can make one step on that direction to detect walls
while (!bHitWall && fDistanceToWall < fDepth)
{
// If we didn't hit the wall, we can take a step in the ray direction
fDistanceToWall += 0.1f;
// because wall's boundaries are at integer coordinates(dot index), so we use intger casting to get the coordinates of the wall
int nTestX{ (int)(fPlayerX + fEyeX * fDistanceToWall) };
int nTestY{ (int)(fPlayerY + fEyeY * fDistanceToWall) };
// Check if ray is out of bounds
if ((nTestX < 0) || (nTestX >= nMapWidth) || (nTestY < 0) || (nTestY >= nMapHeight))
{
bHitWall = true; // Out of bounds, we hit the wall
fDistanceToWall = fDepth; // Set distance to the maximum depth
}
else // If ray is still in bounds
{
if (map[nTestY * nMapWidth + nTestX] == '#') // If we hit a wall
{
bHitWall = true;
}
}
}
}
// Write cavas into console to show screen
WriteConsoleOutputCharacter(hConsole, screen, nScreenWidth * nScreenHeight, { 0,0 }, &dwBytesWritten);
}
return 0;
}