Miscellaneous scripts
This repository contains miscellaneous scripts that does not fit in one repository, yet I will use them sometimes for my personal use. Note that some of the scripts might contain hardcoded paths and opinionated presets, and you are advised to inspect them before actually using.
Loading...
Searching...
No Matches
raycaster.cpp File Reference
#include <windows.h>
#include <winuser.h>
#include <iostream>
#include <vector>
#include <chrono>
#include <thread>
#include <cmath>
#include <algorithm>
#include <string_view>
#include <string>
#include <sstream>
#include <cstdio>
Include dependency graph for raycaster.cpp:

Go to the source code of this file.

Classes

struct  CornerInfo

Macros

#define M_PI   3.14159265358979323846
#define VK_A   0x41
#define VK_D   0x44
#define VK_ESCAPE   0x1B
#define VK_LEFT   0x25
#define VK_RIGHT   0x27
#define VK_S   0x53
#define VK_W   0x57

Functions

bool GetConsoleSize (HANDLE hConsole, int &width, int &height)
int main ()

Variables

constexpr float boundary_angle_check_val = 0.01f
constexpr float cos_boundary_angle_check = 0.99995f
constexpr float FOV = 3.14159f / 4.0f
constexpr float FOV_div_2 = FOV / 2.0f
constexpr std::string_view map
constexpr int mapHeight = 16
constexpr int mapWidth = 16
constexpr float maxDepth = 16.0f
int screenHeight = 40
int screenWidth = 120

Macro Definition Documentation

◆ M_PI

#define M_PI   3.14159265358979323846

Definition at line 72 of file raycaster.cpp.

Referenced by main().

◆ VK_A

#define VK_A   0x41

Definition at line 29 of file raycaster.cpp.

Referenced by main().

◆ VK_D

#define VK_D   0x44

Definition at line 35 of file raycaster.cpp.

Referenced by main().

◆ VK_ESCAPE

#define VK_ESCAPE   0x1B

Definition at line 17 of file raycaster.cpp.

Referenced by main().

◆ VK_LEFT

#define VK_LEFT   0x25

Definition at line 20 of file raycaster.cpp.

Referenced by main().

◆ VK_RIGHT

#define VK_RIGHT   0x27

Definition at line 23 of file raycaster.cpp.

Referenced by main().

◆ VK_S

#define VK_S   0x53

Definition at line 32 of file raycaster.cpp.

Referenced by main().

◆ VK_W

#define VK_W   0x57

Definition at line 26 of file raycaster.cpp.

Referenced by main().

Function Documentation

◆ GetConsoleSize()

bool GetConsoleSize ( HANDLE hConsole,
int & width,
int & height )

Definition at line 76 of file raycaster.cpp.

76 {
77 CONSOLE_SCREEN_BUFFER_INFO csbi;
78 if (GetConsoleScreenBufferInfo(hConsole, &csbi)) {
79 width = csbi.dwSize.X;
80 // Height should be the window height, not buffer height, for visibility
81 height = csbi.srWindow.Bottom - csbi.srWindow.Top + 1;
82 return true;
83 }
84 return false;
85}

Referenced by main().

Here is the caller graph for this function:

◆ main()

int main ( void )

Definition at line 97 of file raycaster.cpp.

97 {
98 HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
99 DWORD dwBytesWritten = 0;
100
101 // --- Initial Debug Info & Pause ---
102 system("cls"); // Clear screen first
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; // Avoid printing 0 cores
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)); // Halt for 2 seconds
114 system("cls"); // Clear screen again before game starts
115 // --- End Initial Debug Info & Pause ---
116
117 // --- Initial Console Size Detection ---
118 if (!GetConsoleSize(hConsole, screenWidth, screenHeight)) {
119 std::cerr << "Warning: Could not get console size. Using default "
120 << screenWidth << "x" << screenHeight << std::endl;
121 // Optionally wait for user input here if it's critical
122 }
123 // ---
124
125 // Initialize screen buffer with detected/default size
126 std::wstring screen(screenWidth * screenHeight, L' ');
127 SetConsoleActiveScreenBuffer(hConsole); // Ensure we're writing to the right buffer
128
129 float halfScreenHeight_float = static_cast<float>(screenHeight) / 2.0f; // Pre-calculate
130
131 float posX = 8.0f, posY = 8.0f; // player start
132 float angle = 0.0f; // player angle
133 constexpr float moveSpeed = 5.0f; // movement units/sec
134 constexpr float rotSpeed = 1.5f; // rotation radians/sec
135
136 using clock = std::chrono::high_resolution_clock;
137 auto tp1 = clock::now();
138 std::chrono::duration<float> frameDuration(0); // To store frame processing time
139
140 while (true) {
141 // --- Check for Console Resize ---
142 int newWidth, newHeight;
143 if (GetConsoleSize(hConsole, newWidth, newHeight)) {
144 if (newWidth != screenWidth || newHeight != screenHeight) {
145 screenWidth = newWidth;
146 screenHeight = newHeight;
147 // Resize the screen buffer, fill with spaces
148 screen.assign(screenWidth * screenHeight, L' ');
149 // Optional: Clear console here if resizing causes artifacts
150 // system("cls"); // This flickers, better to just overwrite
151 halfScreenHeight_float = static_cast<float>(screenHeight) / 2.0f; // Update pre-calculated value
152 }
153 }
154 // --- End Resize Check ---
155
156 // Timing
157 auto tp2 = clock::now();
158 const auto elapsedTimeDuration = tp2 - tp1;
159 tp1 = tp2;
160 const float deltaTime = std::chrono::duration_cast<std::chrono::duration<float>>(elapsedTimeDuration).count();
161
162 // Controls using GetAsyncKeyState
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;
166
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;
173
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) {
182 moveDx /= moveMag;
183 moveDy /= moveMag;
184 }
185 float finalDx = moveDx * moveSpeed * deltaTime;
186 float finalDy = moveDy * moveSpeed * deltaTime;
187 float targetX = posX + finalDx;
188 float targetY = posY + finalDy;
189
190 // Check bounds before accessing map array
191 int mapCheckX = static_cast<int>(targetX);
192 int mapCheckY = static_cast<int>(targetY);
193 if (mapCheckX >= 0 && mapCheckX < mapWidth && mapCheckY >= 0 && mapCheckY < mapHeight &&
194 map[mapCheckY * mapWidth + mapCheckX] != '#')
195 {
196 posX = targetX;
197 posY = targetY;
198 }
199
200 // Render
201 // Check if screen dimensions are valid before rendering
202 if (screenWidth <= 0 || screenHeight <= 0) {
203 std::this_thread::sleep_for(std::chrono::milliseconds(50)); // Avoid busy-waiting if console is invalid
204 continue; // Skip rendering and output if dimensions are bad
205 }
206
207 // --- Manual Threading for Rendering ---
208 unsigned int num_threads = std::thread::hardware_concurrency();
209 if (num_threads == 0) num_threads = 2; // Fallback if hardware_concurrency returns 0 or 1 (still use 2 for some parallelism)
210 if (static_cast<unsigned int>(screenWidth) < num_threads) num_threads = std::max(1, screenWidth); // Don't use more threads than columns
211
212 std::vector<std::thread> threads;
213 threads.reserve(num_threads);
214
215 int columns_per_thread_base = screenWidth / num_threads;
216 int remaining_columns = screenWidth % num_threads;
217 int current_start_x = 0;
218
219 // Capture variables needed by threads. Capture by value for those that might change
220 // or to ensure each thread has a consistent copy for its workload.
221 // 'screen' is captured by reference as it's the output target.
222 // 'map' is a constexpr string_view, safe to capture by reference or value.
223 const float current_angle = angle;
224 const float current_posX = posX;
225 const float current_posY = posY;
226 const int current_screenWidth = screenWidth;
227 const int current_screenHeight = screenHeight;
228 const float current_halfScreenHeight_float = halfScreenHeight_float;
229
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;
233
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;
237
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
243 ]() {
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);
251
252 while (!hitWall && distToWall < maxDepth) {
253 distToWall += 0.05f;
254 const int testX = static_cast<int>(current_posX + eyeX * distToWall);
255 const int testY = static_cast<int>(current_posY + eyeY * distToWall);
256
257 if (testX < 0 || testX >= mapWidth || testY < 0 || testY >= mapHeight) {
258 hitWall = true;
259 distToWall = maxDepth;
260 } else if (map[testY * mapWidth + testX] == '#') {
261 hitWall = true;
262 CornerInfo corners[4];
263 int k = 0;
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))};
271 }
272 }
273 std::sort(corners, corners + 4, [](const CornerInfo& a, const CornerInfo& b) {
274 return a.dist < b.dist;
275 });
276 if (corners[0].dot > cos_boundary_angle_check) boundary = true;
277 if (!boundary && corners[1].dot > cos_boundary_angle_check) boundary = true;
278 }
279 }
280
281 distToWall = std::max(0.1f, distToWall); // Clamp minimum distance for perspective calc
282
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;
290 else shade = L' ';
291 if (boundary) shade = L' ';
292
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;
296 if (y < ceiling)
297 screen[screenIndex] = L' ';
298 else if (y > ceiling && y <= floor)
299 screen[screenIndex] = shade;
300 else {
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;
309 }
310 }
311 }
312 });
313 }
314
315 for (auto& t : threads) {
316 if (t.joinable()) {
317 t.join();
318 }
319 }
320 // --- End Manual Threading ---
321
322 // HUD and Mini-map display
323 // Clear only the necessary HUD area (first line)
324 // Using std::fill_n for potentially cleaner/faster fill if screen is large enough
325 if (!screen.empty() && screenWidth > 0) { // Ensure screen is not empty and screenWidth is positive
326 std::fill_n(screen.begin(), std::min(static_cast<size_t>(screenWidth), screen.length()), L' ');
327 }
328
329 wchar_t hudBuffer[256]; // Buffer for swprintf_s
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();
332
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",
335 posX, posY, angle,
336 static_cast<int>(posX), static_cast<int>(posY),
337 fps,
338 deltaTime * 1000.0f,
339 currentFrameTimeMs,
341
342 std::wstring hudStr;
343 if (chars_written > 0) {
344 hudStr.assign(hudBuffer, chars_written);
345 } else {
346 hudStr = L"HUD Error"; // Fallback
347 }
348
349 size_t hudLen = std::min(static_cast<size_t>(screenWidth), hudStr.length());
350 // Use std::copy or manual loop for safety if hudStr is longer than screenWidth
351 // The existing loop is safe. For potentially more idiomatic C++, std::copy:
352 if (hudLen > 0 && hudLen <= screen.length()) { // Ensure hudLen is valid for screen
353 std::copy_n(hudStr.c_str(), hudLen, screen.begin());
354 } else if (hudLen > 0) { // If hudLen is too large for screen (should not happen with screenWidth limit)
355 // Fallback to manual copy with checks, or ensure screen is always large enough
356 for(size_t i = 0; i < hudLen && i < screen.length(); ++i) {
357 screen[i] = hudStr[i];
358 }
359 }
360
361 // Display mini map (check bounds)
362 int miniMapStartY = 2; // Start minimap below HUD
363 for (int nx = 0; nx < mapWidth; nx++) {
364 for (int ny = 0; ny < mapHeight; ny++) {
365 int screenY = ny + miniMapStartY;
366 if (nx < screenWidth && screenY < screenHeight) { // Check bounds before drawing
367 size_t screenIndex = static_cast<size_t>(screenY) * screenWidth + nx;
368 if (screenIndex < screen.length()) { // Double check index
369 screen[screenIndex] = map[ny * mapWidth + nx];
370 }
371 }
372 }
373 }
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;
379 if (playerMiniMapX < screenWidth && playerScreenY < screenHeight) { // Check bounds
380 size_t screenIndex = static_cast<size_t>(playerScreenY) * screenWidth + playerMiniMapX;
381 if (screenIndex < screen.length()) {
382 screen[screenIndex] = L'P';
383 }
384 }
385
386 // Output to console
387 COORD coord = {0, 0};
388 SetConsoleCursorPosition(hConsole, coord); // Reset cursor position
389 WriteConsoleOutputCharacterW(hConsole, screen.c_str(), screenWidth * screenHeight, coord, &dwBytesWritten);
390
391 // Calculate frame duration for HUD (Frame Limiter removed)
392 auto frameEndTime = clock::now();
393 frameDuration = frameEndTime - tp2;
394 }
395
396 // Optional: Clear screen or restore console state on exit
397 system("cls"); // Example: Clear screen on exit
398
399 return 0;
400}
#define VK_D
Definition raycaster.cpp:35
bool GetConsoleSize(HANDLE hConsole, int &width, int &height)
Definition raycaster.cpp:76
constexpr float FOV
Definition raycaster.cpp:66
constexpr int mapWidth
Definition raycaster.cpp:41
#define VK_S
Definition raycaster.cpp:32
#define VK_A
Definition raycaster.cpp:29
constexpr float maxDepth
Definition raycaster.cpp:68
int screenHeight
Definition raycaster.cpp:63
constexpr float cos_boundary_angle_check
Definition raycaster.cpp:95
#define VK_W
Definition raycaster.cpp:26
#define VK_LEFT
Definition raycaster.cpp:20
#define VK_RIGHT
Definition raycaster.cpp:23
constexpr int mapHeight
Definition raycaster.cpp:42
int screenWidth
Definition raycaster.cpp:62
#define M_PI
Definition raycaster.cpp:72
#define VK_ESCAPE
Definition raycaster.cpp:17
constexpr float FOV_div_2
Definition raycaster.cpp:67
constexpr std::string_view map
Definition raycaster.cpp:43

References cos_boundary_angle_check, CornerInfo::dist, FOV, FOV_div_2, GetConsoleSize(), M_PI, map, mapHeight, mapWidth, maxDepth, screenHeight, screenWidth, VK_A, VK_D, VK_ESCAPE, VK_LEFT, VK_RIGHT, VK_S, and VK_W.

Here is the call graph for this function:

Variable Documentation

◆ boundary_angle_check_val

float boundary_angle_check_val = 0.01f
constexpr

Definition at line 94 of file raycaster.cpp.

◆ cos_boundary_angle_check

float cos_boundary_angle_check = 0.99995f
constexpr

Definition at line 95 of file raycaster.cpp.

Referenced by main().

◆ FOV

float FOV = 3.14159f / 4.0f
constexpr

Definition at line 66 of file raycaster.cpp.

Referenced by main().

◆ FOV_div_2

float FOV_div_2 = FOV / 2.0f
constexpr

Definition at line 67 of file raycaster.cpp.

Referenced by main().

◆ map

std::string_view map
constexpr
Initial value:
=
"################"
"#..............#"
"#.......########"
"#..............#"
"#......##......#"
"#......##......#"
"#..............#"
"###............#"
"##.............#"
"#......####..###"
"#......#.......#"
"#......#.......#"
"#..............#"
"#......#########"
"#..............#"
"################"

Definition at line 43 of file raycaster.cpp.

Referenced by main().

◆ mapHeight

int mapHeight = 16
constexpr

Definition at line 42 of file raycaster.cpp.

Referenced by main().

◆ mapWidth

int mapWidth = 16
constexpr

Definition at line 41 of file raycaster.cpp.

Referenced by main().

◆ maxDepth

float maxDepth = 16.0f
constexpr

Definition at line 68 of file raycaster.cpp.

Referenced by main().

◆ screenHeight

int screenHeight = 40

Definition at line 63 of file raycaster.cpp.

Referenced by main().

◆ screenWidth

int screenWidth = 120

Definition at line 62 of file raycaster.cpp.

Referenced by main().