#include #include #include #include "jdebug.h" #include "util.h" #include "draw.h" #include "wad.h" #include #include #include #include #include #include #include "triggers.h" #define FOV 277 std::vector verts; struct wall { Uint16 v1 {0}; Uint16 v2 {0}; vec2 normal {0.0f, 0.0f}; float u1 {0.0f}; float u2 {0.0f}; float w1 {0.0f}; int portal {-1}; draw::surface_t *surf {nullptr}; draw::surface_t *upper_surf {nullptr}; draw::surface_t *lower_surf {nullptr}; }; struct sector { float light; float floor_height {0.0f}; float ceiling_height {64.0f}; draw::surface_t *floor_surf {nullptr}; draw::surface_t *ceil_surf {nullptr}; std::vector walls; }; std::vector sectors; int current_sector = 38; int current_sector2 = 38; vec2 position = { 1056.0f, 3616.0f }; //vec2 position = {975.0f, -3525.0f}; float orientation = 90.0f; //270.0f; //float orientation = 180.0f; float height = 41.0f; float real_height = 41.0f; float accel = 500.0f; float speed = 0.0f; float max_speed = 200.0f; float rspeed = 200.0f; float bobbing = 0.0f; float dt; Uint32 *palette; //draw::surface_t *gif; draw::surface_t *spr; draw::surface_t *sky; int drawColumn_count = 0; uint8_t *colormap; #pragma pack(push, 1) struct sidedef { int16_t xoffset; int16_t yoffset; char upper_texture[8]; char lower_texture[8]; char middle_texture[8]; int16_t sector; }; #pragma pack(pop) // ------------------------------------------------------------ // Área firmada de un contorno // ------------------------------------------------------------ float signed_area(const std::vector& contour, const std::vector& verts) { float area = 0.0f; for (size_t i = 0; i < contour.size(); i++) { int a = contour[i].v1; int b = contour[i].v2; const vec2& p = verts[a]; const vec2& q = verts[b]; area += (p.x * q.y - q.x * p.y); } return area * 0.5f; } // ------------------------------------------------------------ // Extrae todos los contornos (ciclos) del sector // ------------------------------------------------------------ std::vector> extract_contours(std::vector walls) { std::vector> contours; std::unordered_multimap startMap; for (int i = 0; i < (int)walls.size(); i++) startMap.emplace(walls[i].v1, i); std::vector used(walls.size(), false); for (int i = 0; i < (int)walls.size(); i++) { if (used[i]) continue; std::vector contour; int current = i; contour.push_back(walls[current]); used[current] = true; int v = walls[current].v2; while (true) { auto range = startMap.equal_range(v); int next = -1; for (auto it = range.first; it != range.second; ++it) { int idx = it->second; if (!used[idx]) { next = idx; break; } } if (next == -1) break; // ciclo cerrado contour.push_back(walls[next]); used[next] = true; v = walls[next].v2; } contours.push_back(contour); } return contours; } // ------------------------------------------------------------ // Ordena y fuerza sentido horario en contorno exterior // ------------------------------------------------------------ void order_sector_walls_with_holes(sector& sector) { if (sector.walls.empty()) return; // 1. Extraer contornos auto contours = extract_contours(sector.walls); // 2. Clasificar contornos por área struct ContourInfo { std::vector walls; float area; }; std::vector info; info.reserve(contours.size()); for (auto& c : contours) { float a = signed_area(c, verts); info.push_back({c, a}); } // 3. Encontrar el contorno exterior (el de mayor área absoluta) int exteriorIndex = 0; float maxAbsArea = std::abs(info[0].area); for (int i = 1; i < (int)info.size(); i++) { float absA = std::abs(info[i].area); if (absA > maxAbsArea) { maxAbsArea = absA; exteriorIndex = i; } } // 4. Forzar sentido horario en el contorno exterior if (info[exteriorIndex].area < 0.0f) { std::reverse(info[exteriorIndex].walls.begin(), info[exteriorIndex].walls.end()); for (auto& w : info[exteriorIndex].walls) std::swap(w.v1, w.v2); } // 5. Mantener agujeros en antihorario (o invertir si tu motor lo exige) for (int i = 0; i < (int)info.size(); i++) { if (i == exteriorIndex) continue; // Si quieres agujeros en horario, descomenta: /* if (info[i].area < 0.0f) { std::reverse(info[i].walls.begin(), info[i].walls.end()); for (auto& w : info[i].walls) std::swap(w.v1, w.v2); } */ } // 6. Reconstruir la lista final de paredes sector.walls.clear(); for (auto& c : info) for (auto& w : c.walls) sector.walls.push_back(w); } void loadMap(const char* name) { int size; int16_t *vertices = (int16_t *)wad::load(name, "VERTEXES", &size); const int num_vertices = size/4; for (int i=0;iw : w.upper_surf ? w.upper_surf->w : w.lower_surf ? w.lower_surf->w : 64; SDL_assert(width>=0); w.u1 /= width; w.w1 /= width; w.u2 = distance(verts[w.v1], verts[w.v2]) / width; //w.v2 = distance(s.verts[w.v1], s.verts[w.v2]) / 32.0f; vec2 norm = { verts[w.v2].x - verts[w.v1].x, verts[w.v2].y - verts[w.v1].y}; normalize(&norm); const float tmp = norm.x; norm.x = -norm.y; norm.y = tmp; w.normal = norm; } } } float light_zoom = 1280; int actual_sector = -1; int last_sector = -1; void drawColumn(sector &s, int screen_column, int start, int end, float a_inc, vec2 origin, vec2 infi) { const float angle = orientation + a_inc; vec2 normal = { SDL_cosf(angle*DEG_TO_RAD), SDL_sinf(angle*DEG_TO_RAD) }; vec2 result, tmp_result; wall *w = nullptr; float stright_dist=100000.0f; for (auto &wall : s.walls) { if (dot(normal, wall.normal) >= 0) continue; if (get_line_intersection(origin, infi, verts[wall.v1], verts[wall.v2], &tmp_result)) { if ( (origin.x==tmp_result.x) && (origin.y==tmp_result.y) ) continue; const float d = distance(position, tmp_result);// * SDL_cosf(a_inc*DEG_TO_RAD); if (dh; //64; const float sector_height = s.ceiling_height - s.floor_height; if (sector_height == 0) return; const int width = w->surf ? w->surf->w : w->upper_surf ? w->upper_surf->w : w->lower_surf ? w->lower_surf->w : 64; draw::map::putp(result.x, result.y, 6); float dist = stright_dist * SDL_cosf(a_inc*DEG_TO_RAD); const vec2 AB = {verts[w->v2].x-verts[w->v1].x, verts[w->v2].y-verts[w->v1].y}; const vec2 AP = {result.x-verts[w->v1].x, result.y-verts[w->v1].y}; float v = dot(AP,AB) / dot(AB,AB); v = (v+w->u1) * w->u2; v = v*width; //w->surf->w; //(v-int(v))*gif->w; float wall_height = (sector_height*FOV)/dist; // [64=altura sector] float wall_cut = 0.0f; float dpix = sector_height/wall_height; // [64=crec que altura sector] float cpix = 0; //triggers::isEnabled("offsets") ? w->surf->h*w->w1 : 0; float wall_start = 120-(wall_height/sector_height)*(sector_height-(height-s.floor_height)); // [64=els dos crec que altura sector] if (wall_start> 1)); float actual_dist = straight_dist / SDL_cosf(a_inc*DEG_TO_RAD); int tx = SDL_abs(int(actual_dist * SDL_cosf(angle*DEG_TO_RAD) - position.x)) % s.ceil_surf->w; int ty = SDL_abs(int(actual_dist * SDL_sinf(angle*DEG_TO_RAD) - position.y)) % s.ceil_surf->h; float light_dist = 1.0f - (-actual_dist>light_zoom ? 1.0f : -actual_dist/light_zoom); float light = 1.0f - (light_dist*s.light); uint8_t pixcolor = s.ceil_surf->pixels[tx+ty*s.ceil_surf->w]; pixcolor = colormap[pixcolor + int(light*31)*256]; draw::putpd(screen_column, y, pixcolor, -straight_dist); } } if (w->portal == -1) { // Pinta la pared draw::surface_t *surf = w->surf; float tex_height = (surf->h*FOV)/dist; // [64=altura sector] if (triggers::isEnabled("offsets")) cpix += tex_height * w->w1 * dpix; if (surf) { for (int i=0; i=end) break; float light_dist = 1.0f - (dist>light_zoom ? 1.0f : dist/light_zoom); float light = 1.0f - (light_dist*s.light); uint8_t pixcolor = surf->pixels[(int(v)%surf->w)+(int(cpix)%surf->h)*surf->w]; pixcolor = colormap[pixcolor + int(light*31)*256]; draw::putpd(screen_column, wall_start+i, pixcolor, stright_dist); cpix += dpix; } } } else { if ((sectors[w->portal].ceiling_height < s.ceiling_height) && sectors[w->portal].ceil_surf) { float upper_height = s.ceiling_height - sectors[w->portal].ceiling_height; float upper_wall_height = (upper_height*FOV)/dist; upper_wall_height -= wall_cut; if (upper_wall_height>0.0f) { // Pinta la pared if (w->upper_surf) for (int i=0; i=end) break; float light_dist = 1.0f - (dist>light_zoom ? 1.0f : dist/light_zoom); float light = 1.0f - (light_dist*s.light); uint8_t pixcolor = w->upper_surf->pixels[(int(v)%w->upper_surf->w)+(int(cpix)%w->upper_surf->h)*w->upper_surf->w]; pixcolor = colormap[pixcolor + int(light*31)*256]; draw::putpd(screen_column, wall_start+i, pixcolor, stright_dist); cpix += dpix; } wall_start += upper_wall_height; wall_height -= upper_wall_height; } } float lower_height = sectors[w->portal].floor_height - s.floor_height; float lower_wall_height = (lower_height*FOV)/dist; int wall_end = wall_start+wall_height+1; if (lower_wall_height>0.0f) wall_end -= lower_wall_height; wall_end = SDL_min(wall_end, end); //SDL_assert(&s != §ors[w->portal]); //printf("Anem al sector: %i\n", w->portal); //SDL_assert(w->portal != last_sector); last_sector = actual_sector; actual_sector = w->portal; drawColumn_count++; if (drawColumn_count>50) { printf("drawColumn() loop overflow. Sectors: %i adn %i\n", last_sector, actual_sector); SDL_assert(false); } drawColumn(sectors[w->portal], screen_column, wall_start, wall_end, a_inc, result, infi); // Pinta la pared semitransparent draw::surface_t *surf = w->surf; if (surf) { float tex_height = (surf->h*FOV)/dist; // [64=altura sector] if (triggers::isEnabled("offsets")) cpix += tex_height * w->w1 * dpix; for (int i=0; i=end) break; float light_dist = 1.0f - (dist>light_zoom ? 1.0f : dist/light_zoom); float light = 1.0f - (light_dist*s.light); uint8_t pixcolor = surf->pixels[(int(v)%surf->w)+(int(cpix)%surf->h)*surf->w]; pixcolor = colormap[pixcolor + int(light*31)*256]; draw::putps(screen_column, wall_start+i, pixcolor, stright_dist); cpix += dpix; } } if (lower_wall_height>0.0f) { cpix += (wall_height-lower_wall_height)*dpix; // Pinta la pared if (w->lower_surf) for (int i=0; i=end) break; float light_dist = 1.0f - (dist>light_zoom ? 1.0f : dist/light_zoom); float light = 1.0f - (light_dist*s.light); uint8_t pixcolor = w->lower_surf->pixels[(int(v)%w->lower_surf->w)+(int(cpix)%w->lower_surf->h)*w->lower_surf->w]; pixcolor = colormap[pixcolor + int(light*31)*256]; draw::putpd(screen_column, wall_end+i, pixcolor, stright_dist); cpix += dpix; } //wall_start += upper_wall_height; //wall_height -= upper_wall_height; } } // Pinta el piso int paint_end = wall_start+wall_height-1; for (int y=paint_end+1; y> 1)); float actual_dist = straight_dist / SDL_cosf(a_inc*DEG_TO_RAD); int tx = SDL_abs(int(actual_dist * SDL_cosf(angle*DEG_TO_RAD) + position.x)) % s.floor_surf->w; int ty = SDL_abs(int(actual_dist * SDL_sinf(angle*DEG_TO_RAD) + position.y)) % s.floor_surf->h; float light_dist = (actual_dist>light_zoom ? 1.0f : actual_dist/light_zoom); float light = 1.0f - SDL_clamp(s.light-light_dist, 0.0f, 1.0f); uint8_t pixcolor = s.floor_surf->pixels[tx+ty*s.floor_surf->w]; pixcolor = colormap[pixcolor + int(light*31)*256]; draw::putpd(screen_column, y, pixcolor, straight_dist); } //line(screen_column, 120-(wall_height), screen_column, 120+(wall_height), 5); } } bool tryMove(float angle, float speed) { float sign = speed > 0 ? 1.0f : -1.0f; bool moved = false; sector &s = sectors[current_sector]; int come_from = current_sector; vec2 newpos = { position.x + SDL_cosf(angle*DEG_TO_RAD)*5*sign, position.y }; bool collision=false; for (auto w : s.walls) { if (get_line_intersection(position, newpos, verts[w.v1], verts[w.v2], NULL)) { if (w.portal != -1) { newpos.x = position.x + SDL_cosf(angle*DEG_TO_RAD)*dt*speed; if (get_line_intersection(position, newpos, verts[w.v1], verts[w.v2], NULL) ) { current_sector = w.portal; real_height = sectors[current_sector].floor_height+41.0f; } } else { collision=true; } break; } } if (!collision) { moved = true; position.x += SDL_cosf(angle*DEG_TO_RAD)*dt*speed; } newpos = { position.x, position.y + SDL_sinf(angle*DEG_TO_RAD)*5*sign }; collision=false; for (auto w : s.walls) { if (get_line_intersection(position, newpos, verts[w.v1], verts[w.v2], NULL)) { if (w.portal != -1) { newpos.y = position.y + SDL_sinf(angle*DEG_TO_RAD)*dt*speed; if (get_line_intersection(position, newpos, verts[w.v1], verts[w.v2], NULL) ) if (w.portal != come_from) { current_sector = w.portal; real_height = sectors[current_sector].floor_height+41.0f; } } else { collision=true; } break; } } if (!collision) { moved = true; position.y += SDL_sinf(angle*DEG_TO_RAD)*dt*speed; } return moved; } // angle is in degrees (0–359), increasing clockwise like Doom. // sky is your loaded surface_t containing the sky texture. // screen_w = 320, screen_h = 240. void draw_sky(draw::surface_t *sky, float angle_deg) { // Convert angle to a horizontal texture offset. // Doom uses a bitshift, but this is equivalent: // scroll = angle * (sky->w / 360) float scroll_f = angle_deg * (sky->w / 360.0f); int scroll = (int)scroll_f; for (int y = 0; y < 240; y++) { // Map screen Y to sky Y. // Doom's sky is not perspective-correct; it simply stretches vertically. int ty = (y * sky->h) / 240; for (int x = 0; x < 320; x++) { // Horizontal wrap-around int tx = (x + scroll) % sky->w; if (tx < 0) tx += sky->w; uint8_t color = sky->pixels[ty * sky->w + tx]; draw::putp(x, y, color); } } } int main(int argc, char *argv[]) { wad::init("doom1.wad"); int len; colormap = wad::load("COLORMAP"); draw::init(); //gif = draw::loadgif("walls.gif"); //gif = wad::loadFlat("FLOOR4_8"); //gif = wad::loadTexture("BROWNPIP"); //gif = wad::loadPatch("LADDER16"); //palette = draw::loadpal("walls.gif"); palette = wad::loadPalette(0); draw::setpal(palette); spr = draw::loadgif("player1.gif"); sky = wad::loadTexture("SKY1"); // [DEBUG] Paleta per al depth buffer //for(int i=0;i<256;++i) palette[i] = (255-i) | ((255-i)<<8) | ((255-i)<<16); //createMap(); loadMap("E1M1"); SDL_Event e; bool should_exit = false; Uint32 millis = SDL_GetTicks(); int fps = 0; int fps_count = 0; int fps_time = SDL_GetTicks(); while (!should_exit) { dt = float(SDL_GetTicks() - millis)/1000.0f; millis = SDL_GetTicks(); bobbing += dt*1000.0f; height = real_height + SDL_sinf(bobbing*DEG_TO_RAD)*(speed/100.0f); uint8_t key_pressed = SDL_SCANCODE_UNKNOWN; while (SDL_PollEvent(&e)) { if (e.type==SDL_EVENT_QUIT) { should_exit=true; break; } if (e.type==SDL_EVENT_KEY_DOWN && e.key.scancode==SDL_SCANCODE_ESCAPE) { should_exit=true; break; } if (e.type==SDL_EVENT_KEY_UP) key_pressed = e.key.scancode; if (e.type==SDL_EVENT_MOUSE_WHEEL) { if (e.wheel.y>0) current_sector2++; else current_sector2--; printf("Current sector: %i\n", current_sector2); } } const bool *keys = SDL_GetKeyboardState(NULL); if (keys[SDL_SCANCODE_Q]) { real_height += dt*100.0f; } if (keys[SDL_SCANCODE_A]) { real_height -= dt*100.0f; } if (keys[SDL_SCANCODE_W]) { //real_height += dt*100.0f; sectors[0].ceiling_height -= dt*100.0f; sectors[1].ceiling_height -= dt*100.0f; sectors[0].floor_height -= dt*100.0f; sectors[1].floor_height -= dt*100.0f; } if (keys[SDL_SCANCODE_S]) { //real_height -= dt*100.0f; sectors[0].ceiling_height += dt*100.0f; sectors[1].ceiling_height += dt*100.0f; sectors[0].floor_height += dt*100.0f; sectors[1].floor_height += dt*100.0f; } if (key_pressed == SDL_SCANCODE_M) { triggers::toggle("minimap"); } if (key_pressed == SDL_SCANCODE_O) { triggers::toggle("offsets"); } if (keys[SDL_SCANCODE_RIGHT]) { orientation += dt*rspeed; } else if (keys[SDL_SCANCODE_LEFT]) { orientation -= dt*rspeed; } if (keys[SDL_SCANCODE_UP]) { if (speed < max_speed) speed += dt*accel; } else if (keys[SDL_SCANCODE_DOWN]) { if (speed > -max_speed) speed -= dt*accel; } else { if (speed > 0.0f) { speed -= dt*accel; if (speed < 0.0f) speed = 0.0f; } if (speed < 0.0f) { speed += dt*accel; if (speed > 0.0f) speed = 0.0f; } } if (speed != 0.0f) if (!tryMove(orientation, speed)) speed = 0.0f; draw::cls(); draw_sky(sky, orientation); sector &s = sectors[current_sector]; actual_sector = current_sector; //printf("Current sector: %i\n", current_sector); // Clear screen //SDL_memset4(screen, 0x00000000, (320*240)>>2); int screen_column = 0; for (float a_inc=-32.0f; a_inc<=32.0f; a_inc+=0.2f) { drawColumn_count = 0; last_sector = -1; const float angle = orientation + a_inc; vec2 infi; infi.x = position.x + SDL_cosf(angle*DEG_TO_RAD)*40000; infi.y = position.y + SDL_sinf(angle*DEG_TO_RAD)*40000; //printf("Column %i...\n", screen_column); drawColumn(s, screen_column, 0, 240, a_inc, position, infi); screen_column++; } debug::print("angle:");debug::print(orientation);debug::newline(); vec2 enemy = {256.0f, 256.0f}; draw::map::putp(enemy.x, enemy.y, 9); float angle_to_enemy = is_enemy_in_fov(position.x, position.y, orientation, enemy.x, enemy.y); if (SDL_fabsf(angle_to_enemy) <= 64.0f) { const float d = distance(position, enemy);// * SDL_cosf(a_inc*DEG_TO_RAD); float dist = d * SDL_cosf(angle_to_enemy*DEG_TO_RAD); float wall_height = (32.0f*FOV)/dist; float wall_start = 120-(wall_height/32.0f)*(32.0f-(height-sectors[0].floor_height)); debug::println("enemy height: ", wall_height); debug::println("enemy start: ", wall_start); int column = int((angle_to_enemy+32.0f)/0.2f); for (int i=0; i=0 && c<320) { for (int j=0;j= 0) continue; draw::map::line(verts[w.v1].x, verts[w.v1].y, verts[w.v2].x, verts[w.v2].y, sec==current_sector?20:4, position.x, position.y); vec2 nx {(verts[w.v2].x+verts[w.v1].x)/2, (verts[w.v2].y+verts[w.v1].y)/2}; draw::map::line(nx.x, nx.y, nx.x+w.normal.x*20, nx.y+w.normal.y*20, sec==current_sector?22:3, position.x, position.y); } sec++; } vec2 infi; infi.x = position.x + SDL_cosf((orientation + -32.0f)*DEG_TO_RAD)*40000; infi.y = position.y + SDL_sinf((orientation + -32.0f)*DEG_TO_RAD)*40000; draw::map::line(position.x, position.y, infi.x, infi.y, 32, position.x, position.y); infi.x = position.x + SDL_cosf((orientation)*DEG_TO_RAD)*40000; infi.y = position.y + SDL_sinf((orientation)*DEG_TO_RAD)*40000; draw::map::line(position.x, position.y, infi.x, infi.y, 42, position.x, position.y); infi.x = position.x + SDL_cosf((orientation + 32.0f)*DEG_TO_RAD)*40000; infi.y = position.y + SDL_sinf((orientation + 32.0f)*DEG_TO_RAD)*40000; draw::map::line(position.x, position.y, infi.x, infi.y, 32, position.x, position.y); // Draw map hero vec2 lookat; lookat.x = SDL_cosf(orientation*DEG_TO_RAD)*20; lookat.y = SDL_sinf(orientation*DEG_TO_RAD)*20; draw::map::line(0, 0, lookat.x, lookat.y, 20, 0, 0); draw::map::putp(0, 0, 20); for (int i=0;i<256;++i) { draw::putp((i&0xf)*2, (i>>4)*2, i); draw::putp((i&0xf)*2+1, (i>>4)*2, i); draw::putp((i&0xf)*2+1, (i>>4)*2+1, i); draw::putp((i&0xf)*2, (i>>4)*2+1, i); } } draw::render(); fps_count++; if (SDL_GetTicks()-fps_time>=1000) { fps = fps_count; fps_count = 0; fps_time = SDL_GetTicks(); } debug::println("fps:", fps); debug::println("sector:", current_sector); debug::println("height:", real_height); debug::println("ceil_height:", sectors[0].ceiling_height); debug::println("floor_height:", sectors[0].floor_height); debug::render(); draw::flip(); } return 0; }