#include #include #include #include "sector.h" #define VERTS_CAP 512 #define SECTS_CAP 512 #define NUMS_CAP 512 #define PLRS_CAP 512 #define MAX_QUEUE 32 #define hfov (0.73f*SH) // Affects the horizontal field of vision #define vfov (.2f*SH) // Affects the vertical field of vision void vline(int x, int y1, int y2, uint32_t col); // !! drawing is dogshit !! int map_draw(map_t *map, int SW, int SH) { int sect_rendered[map->nsects]; for(size_t i = 0; i < map->nsects; i++) sect_rendered[i] = 0; int ytop[SW], ybottom[SW]; for(int i = 0; i < SW; i++) { ytop[i] = 0; ybottom[i] = SH-1; } struct render_info { size_t sectno; int sx1, sx2; } queue[MAX_QUEUE]; int head = -1, tail = -1; queue[++tail] = (struct render_info){ map->player.sector, 0, SW-1 }; do { head = (head+1) % MAX_QUEUE; struct render_info now = queue[head]; if(sect_rendered[now.sectno] >= 5) continue; struct sector *sect = &map->sectors[now.sectno]; for(size_t i = 0; i < sect->nverts; i++) { size_t s1 = sect->vertices[i], s0 = sect->vertices[i+1]; float vx1 = map->vertices[s0].x - map->player.pos.x, vy1 = map->vertices[s0].y - map->player.pos.y; float vx2 = map->vertices[s1].x - map->player.pos.x, vy2 = map->vertices[s1].y - map->player.pos.y; float pcos = map->player.anglecos, psin = map->player.anglesin; float tx1 = vx1 * psin - vy1 * pcos, tz1 = vx1 * pcos + vy1 * psin; float tx2 = vx2 * psin - vy2 * pcos, tz2 = vx2 * pcos + vy2 * psin; if(tz1 <= 0 && tz2 <= 0) continue; // ??? magic if(tz1 <= 0 || tz2 <= 0) { float nearz = 1e-4f, farz = 5, nearside = 1e-5f, farside = 20.f; // Find an intersection between the wall and the approximate edges of player's view struct xy i1 = intersect(tx1,tz1,tx2,tz2, -nearside,nearz, -farside,farz); struct xy i2 = intersect(tx1,tz1,tx2,tz2, nearside,nearz, farside,farz); if(tz1 < nearz) { if(i1.y > 0) { tx1 = i1.x; tz1 = i1.y; } else { tx1 = i2.x; tz1 = i2.y; } } if(tz2 < nearz) { if(i1.y > 0) { tx2 = i1.x; tz2 = i1.y; } else { tx2 = i2.x; tz2 = i2.y; } } } float xscale1 = hfov / tz1, yscale1 = vfov / tz1; int x1 = SW/2 - (int)(tx1 * xscale1); float xscale2 = hfov / tz2, yscale2 = vfov / tz2; int x2 = SW/2 - (int)(tx2 * xscale2); if(x1 >= x2 || x2 < now.sx1 || x1 > now.sx2) continue; // Only render if it's visible /* Acquire the floor and ceiling heights, relative to where the player's view is */ float yceil = sect->ceil - map->player.pos.z; float yfloor = sect->floor - map->player.pos.z; size_t neighbor = sect->neighbors[i]; float nyceil = 0.0f, nyfloor = 0.0f; if(neighbor != (size_t)-1) // Is another sector showing through this portal? { nyceil = map->sectors[neighbor].ceil - map->player.pos.z; nyfloor = map->sectors[neighbor].floor - map->player.pos.z; } int y1a = SH/2 - (int)(yceil * yscale1), y1b = SH/2 - (int)(yfloor * yscale1); int y2a = SH/2 - (int)(yceil * yscale2), y2b = SH/2 - (int)(yfloor * yscale2); int ny1a = SH/2 - (int)(nyceil * yscale1), ny1b = SH/2 - (int)(nyfloor * yscale1); int ny2a = SH/2 - (int)(nyceil * yscale2), ny2b = SH/2 - (int)(nyfloor * yscale2); int beginx = max(x1, now.sx1), endx = min(x2, now.sx2); for(int x = beginx; x <= endx; ++x) { int z = ((x - x1) * (tz2-tz1) / (x2-x1) + tz1) * 8; /* Acquire the Y coordinates for our ceiling & floor for this X coordinate. Clamp them. */ int ya = (x - x1) * (y2a-y1a) / (x2-x1) + y1a, cya = clamp(ya, ytop[x],ybottom[x]); // top int yb = (x - x1) * (y2b-y1b) / (x2-x1) + y1b, cyb = clamp(yb, ytop[x],ybottom[x]); // bottom // save because we need to draw the ceilling and floor // after (over) the walls int old_ytop = ytop[x], old_ybottom = ybottom[x]; // depth-shaded white color uint wall_col = scale_color(COLOR_WHITE, (2*z/3)); if(neighbor == (size_t)-1) { // no neighbor vline(x, cya, cyb, wall_col); } else { // yes neighbor int nyb = (x - x1) * (ny2b-ny1b) / (x2-x1) + ny1b, cnyb = clamp(nyb, ytop[x],ybottom[x]); int nya = (x - x1) * (ny2a-ny1a) / (x2-x1) + ny1a, cnya = clamp(nya, ytop[x],ybottom[x]); vline(x, cya, cnya-1, wall_col); // upper wall // vline(x, cnya, cnyb, COLOR_RED); // portal vline(x, cnyb+1, cyb, wall_col); // lower wall ytop[x] = clamp(max(cya, cnya), ytop[x], SH-1); // Shrink the remaining window below these ceilings ybottom[x] = clamp(min(cyb, cnyb), 0, ybottom[x]); // Shrink the remaining window above these floors } /* Render ceiling: everything above this sector's ceiling height. */ vline(x, old_ytop, cya-1, scale_color(COLOR_GREEN, 0)); /* Render floor: everything below this sector's floor height. */ vline(x, cyb+1, old_ybottom, scale_color(COLOR_BLUE, 0)); } if((neighbor != (size_t)-1) && (endx >= beginx) && ((tail+1)%MAX_QUEUE != head)) { tail = (tail+1) % MAX_QUEUE; queue[tail] = (struct render_info){ neighbor, beginx, endx }; } } sect_rendered[now.sectno]++; } while(head != tail); return 0; } // !! collision is dogshit !! void map_detect_collision(map_t *map) { player_t *player = &map->player; struct sector *sect = &map->sectors[player->sector]; float eyeheight = player->ducking ? player->duck_height : player->stand_height; player->velocity.z -= 0.05f; float nextz = player->pos.z + player->velocity.z; if(nextz < eyeheight + sect->floor) // falling inside the floor { player->pos.z = sect->floor + eyeheight; player->velocity.z = 0.0f; } if(nextz + player->head_margin > sect->ceil) // falling up to the celling { player->pos.z = sect->ceil - player->head_margin; player->velocity.z = 0.0f; } float px = player->pos.x, py = player->pos.y; float dx = player->velocity.x, dy = player->velocity.y; #define vert(x) map->vertices[x] // wether the player is intersecting a sector edge for(unsigned i = 0; i < sect->nverts; i++) { size_t s1 = sect->vertices[i]; size_t s0 = sect->vertices[i+1]; if(!(intersectbox(px,py, px+dx,py+dy, vert(s0).x, vert(s0).y, vert(s1).x, vert(s1).y)) || !(pointside(px+dx, py+dy, vert(s0).x, vert(s0).y, vert(s1).x, vert(s1).y) < 0)) continue; float hole_low = 0.0f, hole_high = 0.0f; if(sect->neighbors[i] != (size_t)-1) { hole_high = min(sect->ceil, map->sectors[sect->neighbors[i]].ceil); hole_low = map->sectors[sect->neighbors[i]].floor; } // slide along the wall if(sect->neighbors[i] == (size_t)-1 || hole_low - (player->pos.z - eyeheight ) > player->knee_height || hole_high - hole_low < eyeheight + player->head_margin) { // formula from wikipedia float xd = vert(s1).x - vert(s0).x, yd = vert(s1).y - vert(s0).y; player->velocity.x = xd * (dx*xd + yd*dy) / (xd*xd + yd*yd); player->velocity.y = yd * (dx*xd + yd*dy) / (xd*xd + yd*yd); } else { map->player.sector = sect->neighbors[i]; } break; } } int map_load(map_t *map, char *filename) { int ret = 1; FILE *fp = fopen(filename, "rb"); if(!fp) { log_critical(LOG_SYSTEM, "fopen (%s): %s\n", filename, strerror(errno)); return ret; } // temp structures struct xy vertices[VERTS_CAP]; size_t verts = 0; struct sector sectors[SECTS_CAP]; size_t sects = 0; // player_t players[PLRS_CAP]; // size_t plrs = 0; player_t players[1]; char line[512], *ptr; int offset = 0; while(fgets(line, sizeof(line), fp) != NULL) { char word[256] = {0}; if(sscanf(ptr = line, "%s %n", word, &offset) != 1) word[0] = '\0'; switch(word[0]) { case 'v':; // vertex struct xy vert; sscanf(ptr += offset, "%f %n", &vert.x, &offset); while(sscanf(ptr += offset, "%f %n", &vert.y, &offset) != EOF) { if(verts >= VERTS_CAP) { log_critical(LOG_APPLICATION, "VERTS_CAP of %d reached", VERTS_CAP); goto exit; } vertices[verts++] = vert; } break; case 's': // sector if(sects >= SECTS_CAP) { log_critical(LOG_APPLICATION, "SECTS_CAP of %d reached", SECTS_CAP); goto exit; } sscanf(ptr += offset, "%f %f %n", §ors[sects].floor, §ors[sects].ceil, &offset); size_t numbers[NUMS_CAP]; size_t nums = 0; while(sscanf(ptr += offset, "%s %n", word, &offset) != EOF) { if(nums >= NUMS_CAP) { log_critical(LOG_APPLICATION, "NUMS_CAP of %d reached", NUMS_CAP); goto exit; } numbers[nums++] = (word[0] == 'x') ? -1 : atoi(word); } size_t nverts = nums/2; // populate vertices sectors[sects].vertices = malloc((nverts + 1) * sizeof(*sectors[sects].vertices)); for(size_t i = 0; i < nverts; i++) sectors[sects].vertices[i+1] = numbers[i]; sectors[sects].vertices[0] = sectors[sects].vertices[nverts]; // need to form a loop // populate neighbors sectors[sects].neighbors = malloc(nverts * sizeof(*sectors[sects].neighbors)); for(size_t i = 0; i < nverts; i++) sectors[sects].neighbors[i] = numbers[nverts + i]; sectors[sects++].nverts = nverts; break; case 'p': // player player_default(&players[0]); sscanf(ptr += offset, "%f %f %f %zu %n", &players[0].pos.x, &players[0].pos.y, &players[0].angle, &players[0].sector, &offset); break; } } map->sectors = malloc(sects * sizeof(*sectors)); map->nsects = sects; memcpy(map->sectors, sectors, sects * sizeof(*sectors)); map->vertices = malloc(verts * sizeof(*vertices)); map->nverts = verts; memcpy(map->vertices, vertices, verts * sizeof(*vertices)); memcpy(&map->player, &players[0], sizeof(map->player)); ret = 0; exit: fclose(fp); return ret; } void map_unload(map_t *map) { for(size_t i = 0; i < map->nsects; i++) { free(map->sectors[i].vertices); free(map->sectors[i].neighbors); } free(map->sectors); free(map->vertices); } int map_save(map_t *map, char *filename) { FILE *fp = fopen(filename, "wb"); if(!fp) { log_error(LOG_SYSTEM, "fopen (%s): %s\n", filename, strerror(errno)); return 1; } (void)map; // something something return 0; } void map_print(map_t *map) { log("%zu %zu", map->nverts, map->nsects); for(size_t i = 0; i < map->nverts; i++) { printf("v %zu {%.1f %.1f}\n", i, map->vertices[i].x, map->vertices[i].y); } for(size_t i = 0; i < map->nsects; i++) { printf("s %zu %.1f %.1f ", i, map->sectors[i].floor, map->sectors[i].ceil); for(size_t j = 0; j < map->sectors[i].nverts + 1; j++) printf("%zd ", map->sectors[i].vertices[j]); printf(" "); for(size_t j = 0; j < map->sectors[i].nverts; j++) printf("%zd ", map->sectors[i].neighbors[j]); printf("\n"); } printf("p %.1f %.1f %.1f %zu", map->player.pos.x, map->player.pos.y, map->player.angle, map->player.sector); }