98 HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
99 DWORD dwBytesWritten = 0;
103 std::cout <<
"Starting Raycaster Engine..." << std::endl;
104 std::cout <<
"-----------------------------" << std::endl;
105 std::cout <<
"Build: " << __DATE__ <<
" " << __TIME__ << std::endl;
106 unsigned int core_count = std::thread::hardware_concurrency();
107 if (core_count == 0) core_count = 1;
108 std::cout <<
"Detected CPU Cores: " << core_count << std::endl;
109 std::cout <<
"FOV: " << (
FOV * 180.0f /
M_PI) <<
" degrees" << std::endl;
110 std::cout <<
"Max Render Depth: " <<
maxDepth << std::endl;
111 std::cout <<
"-----------------------------" << std::endl;
112 std::cout <<
"Initializing display..." << std::endl;
113 std::this_thread::sleep_for(std::chrono::seconds(2));
119 std::cerr <<
"Warning: Could not get console size. Using default "
127 SetConsoleActiveScreenBuffer(hConsole);
129 float halfScreenHeight_float =
static_cast<float>(
screenHeight) / 2.0f;
131 float posX = 8.0f, posY = 8.0f;
133 constexpr float moveSpeed = 5.0f;
134 constexpr float rotSpeed = 1.5f;
136 using clock = std::chrono::high_resolution_clock;
137 auto tp1 = clock::now();
138 std::chrono::duration<float> frameDuration(0);
142 int newWidth, newHeight;
151 halfScreenHeight_float =
static_cast<float>(
screenHeight) / 2.0f;
157 auto tp2 = clock::now();
158 const auto elapsedTimeDuration = tp2 - tp1;
160 const float deltaTime = std::chrono::duration_cast<std::chrono::duration<float>>(elapsedTimeDuration).count();
163 if (GetAsyncKeyState(
VK_ESCAPE) & 0x8000)
break;
164 if (GetAsyncKeyState(
VK_LEFT) & 0x8000) angle -= rotSpeed * deltaTime;
165 if (GetAsyncKeyState(
VK_RIGHT) & 0x8000) angle += rotSpeed * deltaTime;
167 float forwardInput = 0.0f;
168 float strafeInput = 0.0f;
169 if (GetAsyncKeyState(
VK_W) & 0x8000) forwardInput += 1.0f;
170 if (GetAsyncKeyState(
VK_S) & 0x8000) forwardInput -= 1.0f;
171 if (GetAsyncKeyState(
VK_A) & 0x8000) strafeInput -= 1.0f;
172 if (GetAsyncKeyState(
VK_D) & 0x8000) strafeInput += 1.0f;
174 float forwardDx = sinf(angle);
175 float forwardDy = cosf(angle);
176 float strafeDx = cosf(angle);
177 float strafeDy = -sinf(angle);
178 float moveDx = (forwardDx * forwardInput + strafeDx * strafeInput);
179 float moveDy = (forwardDy * forwardInput + strafeDy * strafeInput);
180 float moveMag = sqrtf(moveDx * moveDx + moveDy * moveDy);
181 if (moveMag > 0.001f) {
185 float finalDx = moveDx * moveSpeed * deltaTime;
186 float finalDy = moveDy * moveSpeed * deltaTime;
187 float targetX = posX + finalDx;
188 float targetY = posY + finalDy;
191 int mapCheckX =
static_cast<int>(targetX);
192 int mapCheckY =
static_cast<int>(targetY);
193 if (mapCheckX >= 0 && mapCheckX < mapWidth && mapCheckY >= 0 && mapCheckY <
mapHeight &&
203 std::this_thread::sleep_for(std::chrono::milliseconds(50));
208 unsigned int num_threads = std::thread::hardware_concurrency();
209 if (num_threads == 0) num_threads = 2;
212 std::vector<std::thread> threads;
213 threads.reserve(num_threads);
215 int columns_per_thread_base =
screenWidth / num_threads;
217 int current_start_x = 0;
223 const float current_angle = angle;
224 const float current_posX = posX;
225 const float current_posY = posY;
228 const float current_halfScreenHeight_float = halfScreenHeight_float;
230 for (
unsigned int i = 0; i < num_threads; ++i) {
231 int columns_for_this_thread = columns_per_thread_base + (i < static_cast<unsigned int>(remaining_columns) ? 1 : 0);
232 if (columns_for_this_thread == 0)
continue;
234 int thread_start_x = current_start_x;
235 int thread_end_x = current_start_x + columns_for_this_thread;
236 current_start_x = thread_end_x;
238 threads.emplace_back([
239 &screen, thread_start_x, thread_end_x,
240 current_angle, current_screenWidth, current_screenHeight,
241 current_posX, current_posY,
242 current_halfScreenHeight_float
244 for (
int x = thread_start_x; x < thread_end_x; x++) {
245 const float rayAngle = (current_angle -
FOV_div_2) + ((
static_cast<float>(x) /
static_cast<float>(current_screenWidth)) *
FOV);
246 float distToWall = 0.0f;
247 bool hitWall =
false;
248 bool boundary =
false;
249 const float eyeX = sinf(rayAngle);
250 const float eyeY = cosf(rayAngle);
252 while (!hitWall && distToWall <
maxDepth) {
254 const int testX =
static_cast<int>(current_posX + eyeX * distToWall);
255 const int testY =
static_cast<int>(current_posY + eyeY * distToWall);
264 for (
int tx = 0; tx < 2; tx++) {
265 for (
int ty = 0; ty < 2; ty++) {
266 const float vx =
static_cast<float>(testX) + tx - current_posX;
267 const float vy =
static_cast<float>(testY) + ty - current_posY;
268 const float d = sqrtf(vx*vx + vy*vy);
269 float current_dot = (d > 0.001f) ? ((eyeX*vx/d) + (eyeY*vy/d)) : 1.0f;
270 corners[k++] = {d, std::max(-1.0f, std::min(1.0f, current_dot))};
281 distToWall = std::max(0.1f, distToWall);
283 const int ceiling =
static_cast<int>(current_halfScreenHeight_float -
static_cast<float>(current_screenHeight) / distToWall);
284 const int floor = current_screenHeight - ceiling;
285 wchar_t shade = L
' ';
286 if (distToWall <=
maxDepth/4.0f) shade = 0x2588;
287 else if (distToWall <
maxDepth/3.0f) shade = 0x2593;
288 else if (distToWall <
maxDepth/2.0f) shade = 0x2592;
289 else if (distToWall <
maxDepth) shade = 0x2591;
291 if (boundary) shade = L
' ';
293 for (
int y = 0; y < current_screenHeight; y++) {
294 size_t screenIndex =
static_cast<size_t>(y) * current_screenWidth + x;
295 if (screenIndex >= screen.length())
continue;
297 screen[screenIndex] = L
' ';
298 else if (y > ceiling && y <= floor)
299 screen[screenIndex] = shade;
301 const float b = 1.0f - ((
static_cast<float>(y) - current_halfScreenHeight_float)/current_halfScreenHeight_float);
302 wchar_t floorShade = L
' ';
303 if (b < 0.25) floorShade = L
'#';
304 else if (b < 0.5) floorShade = L
'x';
305 else if (b < 0.75) floorShade = L
'.';
306 else if (b < 0.9) floorShade = L
'-';
307 else floorShade = L
' ';
308 screen[screenIndex] = floorShade;
315 for (
auto& t : threads) {
326 std::fill_n(screen.begin(), std::min(
static_cast<size_t>(
screenWidth), screen.length()), L
' ');
329 wchar_t hudBuffer[256];
330 float fps = (deltaTime > 0.0f) ? (1.0f / deltaTime) : 0.0f;
331 float currentFrameTimeMs = std::chrono::duration_cast<std::chrono::duration<float, std::milli>>(frameDuration).count();
333 int chars_written = swprintf_s(hudBuffer,
sizeof(hudBuffer)/
sizeof(hudBuffer[0]),
334 L
"X:%.2f Y:%.2f A:%.2f | Map(%d,%d) | FPS:%.0f | dT:%.2fms | FrameT:%.2fms | Size:%dx%d",
336 static_cast<int>(posX),
static_cast<int>(posY),
343 if (chars_written > 0) {
344 hudStr.assign(hudBuffer, chars_written);
346 hudStr = L
"HUD Error";
349 size_t hudLen = std::min(
static_cast<size_t>(
screenWidth), hudStr.length());
352 if (hudLen > 0 && hudLen <= screen.length()) {
353 std::copy_n(hudStr.c_str(), hudLen, screen.begin());
354 }
else if (hudLen > 0) {
356 for(
size_t i = 0; i < hudLen && i < screen.length(); ++i) {
357 screen[i] = hudStr[i];
362 int miniMapStartY = 2;
363 for (
int nx = 0; nx <
mapWidth; nx++) {
365 int screenY = ny + miniMapStartY;
367 size_t screenIndex =
static_cast<size_t>(screenY) *
screenWidth + nx;
368 if (screenIndex < screen.length()) {
374 int playerMiniMapX =
static_cast<int>(posX);
375 int playerMiniMapY =
static_cast<int>(posY);
376 playerMiniMapX = std::max(0, std::min(
mapWidth - 1, playerMiniMapX));
377 playerMiniMapY = std::max(0, std::min(
mapHeight - 1, playerMiniMapY));
378 int playerScreenY = playerMiniMapY + miniMapStartY;
380 size_t screenIndex =
static_cast<size_t>(playerScreenY) *
screenWidth + playerMiniMapX;
381 if (screenIndex < screen.length()) {
382 screen[screenIndex] = L
'P';
387 COORD coord = {0, 0};
388 SetConsoleCursorPosition(hConsole, coord);
392 auto frameEndTime = clock::now();
393 frameDuration = frameEndTime - tp2;