#define _USE_MATH_DEFINES #define NOMINMAX #include // Решение проблемы с конфликтом min/max в GDI+ #include using std::min; using std::max; #include #include #include #include #include #include // Линковка библиотеки GDI+ #pragma comment (lib, "Gdiplus.lib") // Константы const int WIDTH = 800; const int HEIGHT = 600; const int HALF_HEIGHT = HEIGHT / 2; const int TILE_SIZE = 64; const float fov = static_cast(M_PI) / 3.0f; const int NUM_RAYS = 160; const int SCALE = WIDTH / NUM_RAYS; const int MAP_SIZE = 32; struct Color { unsigned char b, g, r, a; Color() : r(0), g(0), b(0), a(255) {} Color(unsigned char r, unsigned char g, unsigned char b) : r(r), g(g), b(b), a(255) {} }; std::vector frame_buffer(WIDTH* HEIGHT); float player_x = 2.0f * TILE_SIZE + 32.0f; float player_y = 2.0f * TILE_SIZE + 32.0f; int cell_x = 2; int cell_y = 2; float player_angle = 0.0f; const float player_speed = 100.0f; const float rotation_speed = 2.0f; bool demo_mode = true; bool day_mode = false; int current_dir_index = 0; const std::vector DIRECTIONS = { 0.0f, static_cast(M_PI) / 2.0f, static_cast(M_PI), -static_cast(M_PI) / 2.0f }; enum class AIState { MOVE, TURN }; AIState ai_state = AIState::MOVE; float target_angle = 0.0f; std::map, int> world_map; std::mt19937 rng(std::random_device{}()); float random_float() { std::uniform_real_distribution dist(0.0f, 1.0f); return dist(rng); } void generate_maze(int size) { for (int row = 0; row < size; ++row) { for (int col = 0; col < size; ++col) { if (col == 0 || col == size - 1 || row == 0 || row == size - 1) { world_map[{col, row}] = 1; } else if (col >= 1 && col <= 3 && row >= 1 && row <= 3) { world_map[{col, row}] = 0; } else { world_map[{col, row}] = (random_float() < 0.30f) ? 1 : 0; } } } } int get_wall_type(int col, int row) { auto it = world_map.find({ col, row }); if (it != world_map.end()) return it->second; return 1; } std::vector tex_wall(TILE_SIZE* TILE_SIZE); std::vector tex_ceil(TILE_SIZE* TILE_SIZE); std::vector tex_floor(TILE_SIZE* TILE_SIZE); bool load_png_texture(const wchar_t* filename, std::vector& buffer) { Gdiplus::Bitmap bitmap(filename); if (bitmap.GetLastStatus() != Gdiplus::Ok) { return false; } Gdiplus::BitmapData bitmapData; Gdiplus::Rect rect(0, 0, bitmap.GetWidth(), bitmap.GetHeight()); if (bitmap.LockBits(&rect, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &bitmapData) == Gdiplus::Ok) { UINT* pixels = (UINT*)bitmapData.Scan0; int stride = bitmapData.Stride / 4; for (int y = 0; y < TILE_SIZE; ++y) { for (int x = 0; x < TILE_SIZE; ++x) { int src_x = (x * bitmap.GetWidth()) / TILE_SIZE; int src_y = (y * bitmap.GetHeight()) / TILE_SIZE; UINT p = pixels[src_y * stride + src_x]; Color c; c.a = (p >> 24) & 0xFF; c.r = (p >> 16) & 0xFF; c.g = (p >> 8) & 0xFF; c.b = p & 0xFF; buffer[y * TILE_SIZE + x] = c; } } bitmap.UnlockBits(&bitmapData); return true; } return false; } void create_textures() { if (!load_png_texture(L"wall.png", tex_wall)) { std::fill(tex_wall.begin(), tex_wall.end(), Color(125, 45, 30)); for (int y : {0, 16, 32, 48, 63}) for (int x = 0; x < 64; ++x) tex_wall[y * TILE_SIZE + x] = Color(110, 105, 100); for (int x : {0, 32, 64}) { if (x < 64) { for (int y = 0; y < 16; ++y) tex_wall[y * TILE_SIZE + x] = Color(110, 105, 100); for (int y = 32; y < 48; ++y) tex_wall[y * TILE_SIZE + x] = Color(110, 105, 100); } } for (int x : {16, 48}) { for (int y = 16; y < 32; ++y) tex_wall[y * TILE_SIZE + x] = Color(110, 105, 100); for (int y = 48; y < 64; ++y) tex_wall[y * TILE_SIZE + x] = Color(110, 105, 100); } } if (!load_png_texture(L"top.png", tex_ceil)) { std::fill(tex_ceil.begin(), tex_ceil.end(), Color(75, 75, 75)); } if (!load_png_texture(L"floor.png", tex_floor)) { std::fill(tex_floor.begin(), tex_floor.end(), Color(45, 45, 50)); for (int i = 0; i < TILE_SIZE; i += 16) { for (int y = 0; y < TILE_SIZE; ++y) tex_floor[y * TILE_SIZE + i] = Color(25, 25, 28); for (int x = 0; x < TILE_SIZE; ++x) tex_floor[i * TILE_SIZE + x] = Color(25, 25, 28); } } } LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_DESTROY: PostQuitMessage(0); return 0; case WM_KEYDOWN: if (wParam == 'Y') { demo_mode = !demo_mode; ai_state = AIState::MOVE; } if (wParam == 'L') { day_mode = !day_mode; } if (wParam == VK_ESCAPE) PostQuitMessage(0); return 0; } return DefWindowProc(hwnd, uMsg, wParam, lParam); } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { Gdiplus::GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL); const wchar_t CLASS_NAME[] = L"MazeWindowClass"; WNDCLASS wc = {}; wc.lpfnWndProc = WindowProc; wc.hInstance = hInstance; wc.lpszClassName = CLASS_NAME; wc.hCursor = LoadCursor(NULL, IDC_ARROW); RegisterClass(&wc); RECT rect = { 0, 0, WIDTH, HEIGHT }; AdjustWindowRect(&rect, WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, FALSE); HWND hwnd = CreateWindowEx(0, CLASS_NAME, L"3DMAZE", WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, rect.right - rect.left, rect.bottom - rect.top, NULL, NULL, hInstance, NULL); if (hwnd == NULL) { Gdiplus::GdiplusShutdown(gdiplusToken); return 0; } ShowWindow(hwnd, nCmdShow); generate_maze(MAP_SIZE); create_textures(); BITMAPINFO bmi = {}; bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmi.bmiHeader.biWidth = WIDTH; bmi.bmiHeader.biHeight = -HEIGHT; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; auto last_time = std::chrono::high_resolution_clock::now(); bool running = true; while (running) { MSG msg; while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { if (msg.message == WM_QUIT) running = false; TranslateMessage(&msg); DispatchMessage(&msg); } auto current_time = std::chrono::high_resolution_clock::now(); float dt = std::chrono::duration(current_time - last_time).count(); last_time = current_time; if (demo_mode) { if (ai_state == AIState::MOVE) { float target_x = static_cast(cell_x * TILE_SIZE + 32); float target_y = static_cast(cell_y * TILE_SIZE + 32); float move_cos = std::round(std::cos(DIRECTIONS[current_dir_index])); float move_sin = std::round(std::sin(DIRECTIONS[current_dir_index])); float dist = std::hypot(target_x - player_x, target_y - player_y); if (dist > 1.0f) { float step = player_speed * dt; if (step > dist) step = dist; player_x += move_cos * step; player_y += move_sin * step; } else { player_x = target_x; player_y = target_y; std::vector available_dirs; for (size_t i = 0; i < DIRECTIONS.size(); ++i) { int t_cos = static_cast(std::round(std::cos(DIRECTIONS[i]))); int t_sin = static_cast(std::round(std::sin(DIRECTIONS[i]))); if (get_wall_type(cell_x + t_cos, cell_y + t_sin) == 0) available_dirs.push_back(static_cast(i)); } int backward_dir_index = (current_dir_index + 2) % 4; std::vector forward_and_sides; for (int d : available_dirs) if (d != backward_dir_index) forward_and_sides.push_back(d); int next_dir = current_dir_index; if (!forward_and_sides.empty()) { if (std::find(forward_and_sides.begin(), forward_and_sides.end(), current_dir_index) != forward_and_sides.end() && random_float() < 0.75f) { next_dir = current_dir_index; } else { std::uniform_int_distribution dist_dir(0, static_cast(forward_and_sides.size() - 1)); next_dir = forward_and_sides[dist_dir(rng)]; } } else if (std::find(available_dirs.begin(), available_dirs.end(), backward_dir_index) != available_dirs.end()) { next_dir = backward_dir_index; } if (next_dir != current_dir_index) { current_dir_index = next_dir; target_angle = DIRECTIONS[current_dir_index]; ai_state = AIState::TURN; } else { cell_x += static_cast(move_cos); cell_y += static_cast(move_sin); } } } else if (ai_state == AIState::TURN) { float angle_diff = std::fmod(target_angle - player_angle + static_cast(M_PI), 2.0f * static_cast(M_PI)); if (angle_diff < 0) angle_diff += 2.0f * static_cast(M_PI); angle_diff -= static_cast(M_PI); if (std::abs(angle_diff) > 0.01f) { float step = rotation_speed * dt; if (step > std::abs(angle_diff)) step = std::abs(angle_diff); player_angle += (angle_diff > 0 ? 1.0f : -1.0f) * step; } else { player_angle = target_angle; float move_cos = std::round(std::cos(DIRECTIONS[current_dir_index])); float move_sin = std::round(std::sin(DIRECTIONS[current_dir_index])); cell_x += static_cast(move_cos); cell_y += static_cast(move_sin); ai_state = AIState::MOVE; } } } else { if (GetAsyncKeyState('A') || GetAsyncKeyState(VK_LEFT)) player_angle -= 3.0f * dt; if (GetAsyncKeyState('D') || GetAsyncKeyState(VK_RIGHT)) player_angle += 3.0f * dt; float cos_a = std::cos(player_angle), sin_a = std::sin(player_angle); float mx = 0, my = 0; if (GetAsyncKeyState('W') || GetAsyncKeyState(VK_UP)) { mx = player_speed * cos_a * dt; my = player_speed * sin_a * dt; } else if (GetAsyncKeyState('S') || GetAsyncKeyState(VK_DOWN)) { mx = -player_speed * cos_a * dt; my = -player_speed * sin_a * dt; } float r = 12.0f; float sign_x = (mx > 0) ? r : (mx < 0 ? -r : 0); float sign_y = (my > 0) ? r : (my < 0 ? -r : 0); if (get_wall_type(static_cast((player_x + mx + sign_x) / TILE_SIZE), static_cast(player_y / TILE_SIZE)) == 0) player_x += mx; if (get_wall_type(static_cast(player_x / TILE_SIZE), static_cast((player_y + my + sign_y) / TILE_SIZE)) == 0) player_y += my; cell_x = static_cast(player_x / TILE_SIZE); cell_y = static_cast(player_y / TILE_SIZE); } Color bg_color = day_mode ? Color(140, 140, 145) : Color(15, 15, 15); std::fill(frame_buffer.begin(), frame_buffer.end(), bg_color); float start_angle = player_angle - fov / 2.0f; for (int ray = 0; ray < NUM_RAYS; ++ray) { float angle = start_angle + ray * (fov / NUM_RAYS); float r_cos = (std::cos(angle) != 0) ? std::cos(angle) : 0.00001f; float r_sin = (std::sin(angle) != 0) ? std::sin(angle) : 0.00001f; float x_depth = 9999.0f; int x_texture = 0; float x_step = TILE_SIZE / r_cos; float first_x = (r_cos > 0) ? static_cast((static_cast(player_x / TILE_SIZE) + 1) * TILE_SIZE) : static_cast(static_cast(player_x / TILE_SIZE) * TILE_SIZE); float current_depth_x = (first_x - player_x) / r_cos; for (int i = 0; i < 30; ++i) { float tx = player_x + current_depth_x * r_cos; float ty = player_y + current_depth_x * r_sin; int col = static_cast((tx + (r_cos > 0 ? 1.0f : -1.0f)) / TILE_SIZE); int row = static_cast(ty / TILE_SIZE); if (get_wall_type(col, row) > 0) { x_depth = current_depth_x; x_texture = static_cast(std::fmod(ty, static_cast(TILE_SIZE))); if (x_texture < 0) x_texture += TILE_SIZE; break; } current_depth_x += std::abs(x_step); } float y_depth = 9999.0f; int y_texture = 0; float y_step = TILE_SIZE / r_sin; float first_y = (r_sin > 0) ? static_cast((static_cast(player_y / TILE_SIZE) + 1) * TILE_SIZE) : static_cast(static_cast(player_y / TILE_SIZE) * TILE_SIZE); float current_depth_y = (first_y - player_y) / r_sin; for (int i = 0; i < 30; ++i) { float tx = player_x + current_depth_y * r_cos; float ty = player_y + current_depth_y * r_sin; int col = static_cast(tx / TILE_SIZE); int row = static_cast((ty + (r_sin > 0 ? 1.0f : -1.0f)) / TILE_SIZE); if (get_wall_type(col, row) > 0) { y_depth = current_depth_y; y_texture = static_cast(std::fmod(tx, static_cast(TILE_SIZE))); if (y_texture < 0) y_texture += TILE_SIZE; break; } current_depth_y += std::abs(y_step); } float depth = 0; int x_tex_coord = 0; bool is_vertical_wall = false; if (x_depth < y_depth) { depth = x_depth; x_tex_coord = x_texture; is_vertical_wall = true; } else { depth = y_depth; x_tex_coord = y_texture; is_vertical_wall = false; } int wall_y_pos = HALF_HEIGHT; if (depth < 9999.0f) { float fixed_depth = depth * std::cos(player_angle - angle); if (fixed_depth < 1.0f) fixed_depth = 1.0f; int proj_height = static_cast((TILE_SIZE / fixed_depth) * 600.0f); if (proj_height > HEIGHT * 2) proj_height = HEIGHT * 2; wall_y_pos = HALF_HEIGHT - proj_height / 2; int shadow = 0; if (day_mode) { shadow = is_vertical_wall ? 15 : 0; } else { int base_shadow = static_cast(fixed_depth * 0.75f); if (is_vertical_wall) base_shadow += 20; shadow = std::clamp(base_shadow, 0, 255); } int start_screen_y = std::max(0, wall_y_pos); int end_screen_y = std::min(HEIGHT, wall_y_pos + proj_height); for (int screen_y = start_screen_y; screen_y < end_screen_y; ++screen_y) { int tex_y = ((screen_y - wall_y_pos) * TILE_SIZE) / proj_height; tex_y = std::clamp(tex_y, 0, TILE_SIZE - 1); Color pixel_color = tex_wall[tex_y * TILE_SIZE + std::clamp(x_tex_coord, 0, TILE_SIZE - 1)]; pixel_color.r = static_cast(std::max(0, pixel_color.r - shadow)); pixel_color.g = static_cast(std::max(0, pixel_color.g - shadow)); pixel_color.b = static_cast(std::max(0, pixel_color.b - shadow)); for (int s = 0; s < SCALE; ++s) { int pixel_x = ray * SCALE + s; if (pixel_x < WIDTH) frame_buffer[screen_y * WIDTH + pixel_x] = pixel_color; } } float cos_tilt = std::cos(player_angle - angle); for (int screen_y = 0; screen_y < wall_y_pos; screen_y += 4) { if (screen_y >= HALF_HEIGHT) break; float current_dist = (static_cast(TILE_SIZE * HEIGHT)) / (2.0f * static_cast(HALF_HEIGHT - screen_y) * cos_tilt); int tex_x = static_cast(player_x + r_cos * current_dist) % TILE_SIZE; int tex_y_floor = static_cast(player_y + r_sin * current_dist) % TILE_SIZE; if (tex_x < 0) tex_x += TILE_SIZE; if (tex_y_floor < 0) tex_y_floor += TILE_SIZE; int f_shadow = day_mode ? 0 : std::clamp(static_cast(current_dist * 0.75f), 0, 255); Color c_color = tex_ceil[tex_y_floor * TILE_SIZE + tex_x]; c_color.r = static_cast(std::max(0, c_color.r - f_shadow)); c_color.g = static_cast(std::max(0, c_color.g - f_shadow)); c_color.b = static_cast(std::max(0, c_color.b - f_shadow)); Color fl_color = tex_floor[tex_y_floor * TILE_SIZE + tex_x]; fl_color.r = static_cast(std::max(0, fl_color.r - f_shadow)); fl_color.g = static_cast(std::max(0, fl_color.g - f_shadow)); fl_color.b = static_cast(std::max(0, fl_color.b - f_shadow)); for (int h = 0; h < 4; ++h) { int curr_y = screen_y + h; int floor_y = HEIGHT - curr_y - 1; if (curr_y < wall_y_pos) { for (int s = 0; s < SCALE; ++s) { int pixel_x = ray * SCALE + s; if (pixel_x < WIDTH) { frame_buffer[curr_y * WIDTH + pixel_x] = c_color; frame_buffer[floor_y * WIDTH + pixel_x] = fl_color; } } } } } } } HDC hdc = GetDC(hwnd); StretchDIBits(hdc, 0, 0, WIDTH, HEIGHT, 0, 0, WIDTH, HEIGHT, frame_buffer.data(), &bmi, DIB_RGB_COLORS, SRCCOPY); ReleaseDC(hwnd, hdc); } Gdiplus::GdiplusShutdown(gdiplusToken); return 0; }