antizona

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit d3339c3c93cbaa9bc1efc4a0cb3dca9c0953039c
parent d5ad0965b955e167c1def42a448b2384cd667587
Author: averagecoder <averagecoder@noreply.codeberg.org>
Date:   Sun, 23 Aug 2026 20:17:42 +0300

feat: add colls, gravity

Diffstat:
Msrc/build.c | 481+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/render.c | 547+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------
2 files changed, 912 insertions(+), 116 deletions(-)

diff --git a/src/build.c b/src/build.c @@ -16,11 +16,59 @@ #include "skins.h" +#define COL_HASH_SZ 65536 +#define GRID_SZ 64 +#define MAP_MAX 3000.0f +#define MAP_MIN -3000.0f +#define MAX_COL_MODELS 32768 #define MAX_FILES 32768 #define NAME_SZ 24 #define PATH_CACHE_SZ 16384 #define PATH_DB_SZ 65536 + +struct col_cell { + struct col_triangle *tris; + uint32_t count; + uint32_t capacity; +}; + +struct col_header { + char magic[4]; + uint32_t size; + char model_name[22]; + uint16_t model_id; + float radius; + float center[3]; + float min[3]; + float max[3]; + uint16_t num_spheres; + uint16_t num_boxes; + uint16_t num_faces; + uint8_t num_lines; + uint8_t pad; + uint32_t flags; + uint32_t spheres_offset; + uint32_t boxes_offset; + uint32_t lines_offset; + uint32_t vertices_offset; + uint32_t faces_offset; + uint32_t shadow_offset; +} __attribute__((packed)); + +struct col_model { + char name[NAME_SZ]; + struct col_triangle *tris; + uint32_t num_tris; +}; + +struct col_triangle { + float v0[3]; + float v1[3]; + float v2[3]; + uint8_t surface; +}; + struct file_list { char names[MAX_FILES][NAME_SZ]; int count; @@ -57,6 +105,29 @@ struct path_entry { char resolved[1024]; }; +struct raw_col_box { + float min[3]; + float max[3]; + uint8_t surface; + uint8_t piece; + uint8_t lighting; + uint8_t pad; +} __attribute__((packed)); + +struct raw_col_face { + uint16_t a; + uint16_t b; + uint16_t c; + uint8_t surface; + uint8_t piece; +} __attribute__((packed)); + +struct raw_col_vertex { + int16_t x; + int16_t y; + int16_t z; +} __attribute__((packed)); + struct rw_header { uint32_t type; uint32_t size; @@ -143,6 +214,11 @@ struct tar_header { static struct ide_entry ide_db[65536]; static int ide_count = 0; +static struct col_cell world_col_grid[GRID_SZ][GRID_SZ]; +static struct col_model col_db[MAX_COL_MODELS]; +static int col_db_count = 0; +static int col_hash[COL_HASH_SZ]; + static char existing_models[32768][NAME_SZ]; static int existing_models_count = 0; static char existing_textures[32768][NAME_SZ]; @@ -1507,7 +1583,6 @@ process_water(const char *root) count = 0; water_face_count = 0; - /* phase 1: read all faces to memory and clamp to boundaries */ while (fgets(line, sizeof(line), f)) { p = line; while (*p == ' ' || *p == '\t') { @@ -1534,7 +1609,6 @@ process_water(const char *root) face->x[j] = strtof(tokens[j * 7 + 0], NULL); face->y[j] = strtof(tokens[j * 7 + 1], NULL); - /* snap height to 0.1m grid to eliminate micro seams */ face->z[j] = strtof(tokens[j * 7 + 2], NULL); face->z[j] = roundf(face->z[j] * 10.0f) / 10.0f; @@ -1542,7 +1616,6 @@ process_water(const char *root) face->v[j] = strtof(tokens[j * 7 + 4], NULL); face->h[j] = strtof(tokens[j * 7 + 6], NULL); - /* clip to infinite ocean boundaries */ if (face->x[j] > limit) { face->x[j] = limit; } @@ -1561,7 +1634,6 @@ process_water(const char *root) } } - /* phase 2: smart clipping of overlapping internal tiles */ for (i = 0; i < water_face_count; i++) { a = &water_db[i]; if (a->num == 0) { @@ -1620,7 +1692,6 @@ process_water(const char *root) } } - /* resolve intersecting bounding boxes on same height */ if (bx_max > ax_min && bx_min < ax_max && by_max > ay_min && by_min < ay_max) { if (bx_min < ax_max && bx_max > ax_max && bx_min > ax_min) { for (k = 0; k < b->num; k++) { @@ -1651,7 +1722,6 @@ process_water(const char *root) } } - /* phase 3: write perfect tiles to file */ fwrite(&count, sizeof(count), 1, out); for (i = 0; i < water_face_count; i++) { face = &water_db[i]; @@ -1680,6 +1750,392 @@ process_water(const char *root) } static void +mat_translate(struct mat4 *m, float x, float y, float z) +{ + mat_identity(m); + m->m[0][3] = x; + m->m[1][3] = y; + m->m[2][3] = z; +} + +static void +mat_from_quat(struct mat4 *m, float qx, float qy, float qz, float qw) +{ + float len; + + qw = -qw; + len = sqrtf(qx * qx + qy * qy + qz * qz + qw * qw); + if (len > 0.0f) { + qx /= len; + qy /= len; + qz /= len; + qw /= len; + } + mat_identity(m); + m->m[0][0] = 1.0f - 2.0f * (qy * qy + qz * qz); + m->m[0][1] = 2.0f * (qx * qy - qz * qw); + m->m[0][2] = 2.0f * (qx * qz + qy * qw); + m->m[1][0] = 2.0f * (qx * qy + qz * qw); + m->m[1][1] = 1.0f - 2.0f * (qx * qx + qz * qz); + m->m[1][2] = 2.0f * (qy * qz - qx * qw); + m->m[2][0] = 2.0f * (qx * qz - qy * qw); + m->m[2][1] = 2.0f * (qy * qz + qx * qw); + m->m[2][2] = 1.0f - 2.0f * (qx * qx + qy * qy); +} + +static inline void +mat_transform(float dst[3], float *w_out, const struct mat4 *m, const float v[3]) +{ + dst[0] = m->m[0][0] * v[0] + m->m[0][1] * v[1] + m->m[0][2] * v[2] + m->m[0][3]; + dst[1] = m->m[1][0] * v[0] + m->m[1][1] * v[1] + m->m[1][2] * v[2] + m->m[1][3]; + dst[2] = m->m[2][0] * v[0] + m->m[2][1] * v[1] + m->m[2][2] * v[2] + m->m[2][3]; + *w_out = m->m[3][0] * v[0] + m->m[3][1] * v[1] + m->m[3][2] * v[2] + m->m[3][3]; +} + +static uint32_t +str_hash(const char *s) +{ + uint32_t h; + + h = 5381; + while (*s) { + h = ((h << 5) + h) + (unsigned char)*s; + s++; + } + return (h); +} + +static void +col_db_insert(struct col_model *cm) +{ + uint32_t slot; + + slot = str_hash(cm->name) % COL_HASH_SZ; + while (col_hash[slot] != 0) { + slot = (slot + 1) % COL_HASH_SZ; + } + col_hash[slot] = col_db_count; +} + +static void +parse_col_data(const uint8_t *data, size_t size) +{ + const struct col_header *h; + const uint8_t *base; + const struct raw_col_vertex *verts; + const struct raw_col_face *faces; + const struct raw_col_box *boxes; + struct col_model *cm; + struct col_triangle *t; + size_t pos; + uint32_t total_tris, i, b; + int k; + float x0, y0, z0, x1, y1, z1; + uint8_t surf; + float p[8][3]; + static const int box_idx[12][3] = { + {0, 1, 2}, {0, 2, 3}, {4, 6, 5}, {4, 7, 6}, + {0, 4, 5}, {0, 5, 1}, {3, 2, 6}, {3, 6, 7}, + {0, 3, 7}, {0, 7, 4}, {1, 5, 6}, {1, 6, 2} + }; + + pos = 0; + while (pos + sizeof(struct col_header) <= size) { + h = (const struct col_header *)(data + pos); + if (memcmp(h->magic, "COL2", 4) != 0 && + memcmp(h->magic, "COL3", 4) != 0 && + memcmp(h->magic, "COLL", 4) != 0) { + break; + } + if (col_db_count >= MAX_COL_MODELS) { + break; + } + + cm = &col_db[col_db_count]; + strncpy(cm->name, h->model_name, sizeof(cm->name) - 1); + cm->name[sizeof(cm->name) - 1] = '\0'; + trim_spaces(cm->name); + to_lower_str(cm->name); + + total_tris = h->num_faces + (h->num_boxes * 12); + if (total_tris > 0) { + cm->tris = malloc(total_tris * sizeof(struct col_triangle)); + if (!cm->tris) { + break; + } + cm->num_tris = 0; + base = data + pos + 4; + + if (h->num_faces > 0 && h->vertices_offset && h->faces_offset) { + verts = (const struct raw_col_vertex *)(base + h->vertices_offset); + faces = (const struct raw_col_face *)(base + h->faces_offset); + for (i = 0; i < h->num_faces; i++) { + t = &cm->tris[cm->num_tris++]; + t->v0[0] = verts[faces[i].a].x / 128.0f; + t->v0[1] = verts[faces[i].a].y / 128.0f; + t->v0[2] = verts[faces[i].a].z / 128.0f; + + t->v1[0] = verts[faces[i].b].x / 128.0f; + t->v1[1] = verts[faces[i].b].y / 128.0f; + t->v1[2] = verts[faces[i].b].z / 128.0f; + + t->v2[0] = verts[faces[i].c].x / 128.0f; + t->v2[1] = verts[faces[i].c].y / 128.0f; + t->v2[2] = verts[faces[i].c].z / 128.0f; + + t->surface = faces[i].surface; + } + } + + if (h->num_boxes > 0 && h->boxes_offset) { + boxes = (const struct raw_col_box *)(base + h->boxes_offset); + for (b = 0; b < h->num_boxes; b++) { + x0 = boxes[b].min[0]; + y0 = boxes[b].min[1]; + z0 = boxes[b].min[2]; + x1 = boxes[b].max[0]; + y1 = boxes[b].max[1]; + z1 = boxes[b].max[2]; + surf = boxes[b].surface; + + p[0][0] = x0; p[0][1] = y0; p[0][2] = z0; + p[1][0] = x1; p[1][1] = y0; p[1][2] = z0; + p[2][0] = x1; p[2][1] = y1; p[2][2] = z0; + p[3][0] = x0; p[3][1] = y1; p[3][2] = z0; + p[4][0] = x0; p[4][1] = y0; p[4][2] = z1; + p[5][0] = x1; p[5][1] = y0; p[5][2] = z1; + p[6][0] = x1; p[6][1] = y1; p[6][2] = z1; + p[7][0] = x0; p[7][1] = y1; p[7][2] = z1; + + for (k = 0; k < 12; k++) { + t = &cm->tris[cm->num_tris++]; + memcpy(t->v0, p[box_idx[k][0]], sizeof(float) * 3); + memcpy(t->v1, p[box_idx[k][1]], sizeof(float) * 3); + memcpy(t->v2, p[box_idx[k][2]], sizeof(float) * 3); + t->surface = surf; + } + } + } + col_db_count++; + col_db_insert(cm); + } + pos += 8 + h->size; + } +} + +static void +process_gta_dat_col(const char *root, const char *dat_path) +{ + FILE *f; + FILE *cf; + char line[512]; + char rel[512]; + char *p; + char *src; + uint8_t *buf; + size_t sz, i; + + f = fopen_simple(root, dat_path); + if (!f) { + return; + } + + while (fgets(line, sizeof(line), f)) { + p = line; + while (*p == ' ' || *p == '\t') { + p++; + } + if (strncasecmp(p, "COLFILE ", 8) == 0) { + src = p + 8; + while (*src == ' ' || *src == '\t') { + src++; + } + while (*src >= '0' && *src <= '9') { + src++; + } + while (*src == ' ' || *src == '\t') { + src++; + } + for (i = 0; i < sizeof(rel) - 1 && src[i]; i++) { + if (src[i] == '\\') { + rel[i] = '/'; + } else { + rel[i] = src[i]; + } + rel[i + 1] = '\0'; + } + trim_spaces(rel); + + cf = fopen_simple(root, rel); + if (cf) { + fseek(cf, 0, SEEK_END); + sz = ftell(cf); + fseek(cf, 0, SEEK_SET); + buf = malloc(sz); + if (buf && fread(buf, 1, sz, cf) == sz) { + parse_col_data(buf, sz); + } + free(buf); + fclose(cf); + } + } + } + fclose(f); +} + +static struct col_model * +col_lookup(const char *name) +{ + uint32_t slot; + int idx; + + slot = str_hash(name) % COL_HASH_SZ; + while (col_hash[slot] != 0) { + idx = col_hash[slot] - 1; + if (strcmp(col_db[idx].name, name) == 0) { + return (&col_db[idx]); + } + slot = (slot + 1) % COL_HASH_SZ; + } + return (NULL); +} + +static void +instantiate_collision(const char *model_name, const struct mat4 *world_mat) +{ + struct col_model *cm; + struct col_triangle wt; + struct col_cell *c; + float w, min_x, max_x, min_z, max_z; + uint32_t i; + int gx0, gx1, gz0, gz1, gx, gz; + + cm = col_lookup(model_name); + if (!cm || cm->num_tris == 0) { + return; + } + + for (i = 0; i < cm->num_tris; i++) { + mat_transform(wt.v0, &w, world_mat, cm->tris[i].v0); + mat_transform(wt.v1, &w, world_mat, cm->tris[i].v1); + mat_transform(wt.v2, &w, world_mat, cm->tris[i].v2); + wt.surface = cm->tris[i].surface; + + min_x = fminf(wt.v0[0], fminf(wt.v1[0], wt.v2[0])); + max_x = fmaxf(wt.v0[0], fmaxf(wt.v1[0], wt.v2[0])); + min_z = fminf(wt.v0[2], fminf(wt.v1[2], wt.v2[2])); + max_z = fmaxf(wt.v0[2], fmaxf(wt.v1[2], wt.v2[2])); + + gx0 = (int)((min_x - MAP_MIN) / ((MAP_MAX - MAP_MIN) / GRID_SZ)); + gx1 = (int)((max_x - MAP_MIN) / ((MAP_MAX - MAP_MIN) / GRID_SZ)); + gz0 = (int)((min_z - MAP_MIN) / ((MAP_MAX - MAP_MIN) / GRID_SZ)); + gz1 = (int)((max_z - MAP_MIN) / ((MAP_MAX - MAP_MIN) / GRID_SZ)); + + if (gx0 < 0) gx0 = 0; + if (gx1 >= GRID_SZ) gx1 = GRID_SZ - 1; + if (gz0 < 0) gz0 = 0; + if (gz1 >= GRID_SZ) gz1 = GRID_SZ - 1; + + for (gz = gz0; gz <= gz1; gz++) { + for (gx = gx0; gx <= gx1; gx++) { + c = &world_col_grid[gx][gz]; + if (c->count >= c->capacity) { + c->capacity = c->capacity == 0 ? 64 : c->capacity * 2; + c->tris = realloc(c->tris, c->capacity * sizeof(struct col_triangle)); + } + c->tris[c->count++] = wt; + } + } + } +} + +static void +bake_world_collision(void) +{ + FILE *f; + char line[512]; + char name[NAME_SZ]; + char *p; + struct mat4 rot, trans, sa2gl, world; + int in_inst, id, interior, lod, time_on, time_off; + float px, py, pz, qx, qy, qz, qw; + + f = fopen("assets/map.ipl", "r"); + if (!f) { + return; + } + + in_inst = 0; + while (fgets(line, sizeof(line), f)) { + p = line; + while (*p == ' ' || *p == '\t') { + p++; + } + if (*p == '\0' || *p == '#') { + continue; + } + if (strncasecmp(p, "inst", 4) == 0 || strncasecmp(p, "tobj", 4) == 0) { + in_inst = 1; + continue; + } + if (strncasecmp(p, "end", 3) == 0) { + in_inst = 0; + continue; + } + if (in_inst) { + lod = -1; + time_on = 0; + time_off = 0; + if (sscanf(p, "%d, %23[^,], %d, %f, %f, %f, %f, %f, %f, %f, %d, %d, %d", + &id, name, &interior, &px, &py, &pz, &qx, &qy, &qz, &qw, + &lod, &time_on, &time_off) >= 10) { + trim_spaces(name); + to_lower_str(name); + + mat_from_quat(&rot, qx, qy, qz, qw); + mat_translate(&trans, px, py, pz); + mat_mul(&world, &trans, &rot); + + memset(&sa2gl, 0, sizeof(sa2gl)); + sa2gl.m[0][0] = 1.0f; + sa2gl.m[1][2] = 1.0f; + sa2gl.m[2][1] = 1.0f; + sa2gl.m[3][3] = 1.0f; + mat_mul(&world, &sa2gl, &world); + + instantiate_collision(name, &world); + } + } + } + fclose(f); +} + +static void +save_collision_bin(void) +{ + FILE *out; + uint32_t cnt; + int gx, gz; + + out = fopen("assets/collision.bin", "wb"); + if (!out) { + return; + } + + for (gz = 0; gz < GRID_SZ; gz++) { + for (gx = 0; gx < GRID_SZ; gx++) { + cnt = world_col_grid[gx][gz].count; + fwrite(&cnt, sizeof(uint32_t), 1, out); + if (cnt > 0) { + fwrite(world_col_grid[gx][gz].tris, + sizeof(struct col_triangle), cnt, out); + } + } + } + fclose(out); +} + +static void process_timecyc(const char *root) { FILE *f; @@ -1987,7 +2443,6 @@ main(int argc, char *argv[]) process_gta_dat_ide(argv[1], "data/default.dat", &fl); process_gta_dat_ide(argv[1], "data/gta.dat", &fl); - /* forcefully register special actors as required assets */ for (int i = 0; i < (int)(sizeof(special_actors) / sizeof(special_actors[0])); i++) { char d_name[64], t_name[64]; snprintf(d_name, sizeof(d_name), "%s.dff", special_actors[i].model); @@ -2047,6 +2502,12 @@ main(int argc, char *argv[]) parse_text_ipl((char *)buf, size, map_out, &fl); } } + + if (strstr(name_lower, ".col") != NULL) { + uint32_t size = entries[i].size * 2048; + const uint8_t *buf = img_data + entries[i].offset * 2048; + parse_col_data(buf, size); + } } fclose(map_out); @@ -2083,11 +2544,15 @@ main(int argc, char *argv[]) process_img_file(argv[1], "models/gta_int.img", &fl, num_threads, threads, args); process_img_file(argv[1], "models/cutscene.img", &fl, num_threads, threads, args); + process_gta_dat_col(argv[1], "data/default.dat"); + process_gta_dat_col(argv[1], "data/gta.dat"); + printf("Loaded %d collision models into memory\n", col_db_count); + bake_world_collision(); + save_collision_bin(); free(threads); free(args); - /* Compile outputs into assets.tar */ printf("Packing assets.tar...\n"); tar = fopen("assets.tar", "wb"); if (tar) { diff --git a/src/render.c b/src/render.c @@ -41,6 +41,18 @@ struct vec3 { float x, y, z; }; struct mat4 { float m[4][4]; }; +struct col_triangle { + float v0[3]; + float v1[3]; + float v2[3]; + uint8_t surface; +}; + +struct col_cell { + const struct col_triangle *tris; + uint32_t count; +}; + struct timecyc_entry { uint8_t amb[3], dir[3], sky_top[3], sky_bot[3], water[4]; float far_clp, fog_st; @@ -173,6 +185,7 @@ struct tar_index_entry { size_t size; }; +static struct col_cell g_col_grid[GRID_SZ][GRID_SZ]; static struct sys_gfx *g_sys; static struct transform_array g_transforms; static struct mesh **scene_meshes = NULL; @@ -310,6 +323,96 @@ edge_func(float ax, float ay, float bx, float by, float cx, float cy) } static void +closest_point_triangle(const float p[3], const float a[3], const float b[3], const float c[3], float out[3]) +{ + float ab[3], ac[3], ap[3], bp[3], cp[3]; + float d1, d2, d3, d4, d5, d6; + float vc, vb, va, denom, v, w, u; + + ab[0] = b[0] - a[0]; + ab[1] = b[1] - a[1]; + ab[2] = b[2] - a[2]; + + ac[0] = c[0] - a[0]; + ac[1] = c[1] - a[1]; + ac[2] = c[2] - a[2]; + + ap[0] = p[0] - a[0]; + ap[1] = p[1] - a[1]; + ap[2] = p[2] - a[2]; + + d1 = ab[0] * ap[0] + ab[1] * ap[1] + ab[2] * ap[2]; + d2 = ac[0] * ap[0] + ac[1] * ap[1] + ac[2] * ap[2]; + if (d1 <= 0.0f && d2 <= 0.0f) { + out[0] = a[0]; + out[1] = a[1]; + out[2] = a[2]; + return; + } + + bp[0] = p[0] - b[0]; + bp[1] = p[1] - b[1]; + bp[2] = p[2] - b[2]; + + d3 = ab[0] * bp[0] + ab[1] * bp[1] + ab[2] * bp[2]; + d4 = ac[0] * bp[0] + ac[1] * bp[1] + ac[2] * bp[2]; + if (d3 >= 0.0f && d4 <= d3) { + out[0] = b[0]; + out[1] = b[1]; + out[2] = b[2]; + return; + } + + vc = d1 * d4 - d3 * d2; + if (vc <= 0.0f && d1 >= 0.0f && d3 <= 0.0f) { + v = d1 / (d1 - d3); + out[0] = a[0] + v * ab[0]; + out[1] = a[1] + v * ab[1]; + out[2] = a[2] + v * ab[2]; + return; + } + + cp[0] = p[0] - c[0]; + cp[1] = p[1] - c[1]; + cp[2] = p[2] - c[2]; + + d5 = ab[0] * cp[0] + ab[1] * cp[1] + ab[2] * cp[2]; + d6 = ac[0] * cp[0] + ac[1] * cp[1] + ac[2] * cp[2]; + if (d6 >= 0.0f && d5 <= d6) { + out[0] = c[0]; + out[1] = c[1]; + out[2] = c[2]; + return; + } + + vb = d5 * d2 - d1 * d6; + if (vb <= 0.0f && d2 >= 0.0f && d6 <= 0.0f) { + w = d2 / (d2 - d6); + out[0] = a[0] + w * ac[0]; + out[1] = a[1] + w * ac[1]; + out[2] = a[2] + w * ac[2]; + return; + } + + va = d3 * d6 - d5 * d4; + if (va <= 0.0f && (d4 - d3) >= 0.0f && (d5 - d6) >= 0.0f) { + w = (d4 - d3) / ((d4 - d3) + (d5 - d6)); + out[0] = b[0] + w * (c[0] - b[0]); + out[1] = b[1] + w * (c[1] - b[1]); + out[2] = b[2] + w * (c[2] - b[2]); + return; + } + + denom = 1.0f / (va + vb + vc); + v = vb * denom; + w = vc * denom; + u = 1.0f - v - w; + out[0] = u * a[0] + v * b[0] + w * c[0]; + out[1] = u * a[1] + v * b[1] + w * c[1]; + out[2] = u * a[2] + v * b[2] + w * c[2]; +} + +static void draw_triangle_band(const struct render_triangle *tri, int min_y_band, int max_y_band) { struct vertex v0, v1, v2, sv0, sv1, sv2, tmp; @@ -834,6 +937,231 @@ gfx_present(struct sys_gfx *g) XSync(g->dpy, False); } +static int +ray_tri_intersect_y(const struct col_triangle *t, float x, float z, float *out_y) +{ + float x0, z0, x1, z1, x2, z2; + float det, inv_det, w0, w1, w2; + + x0 = t->v0[0]; + z0 = t->v0[2]; + x1 = t->v1[0]; + z1 = t->v1[2]; + x2 = t->v2[0]; + z2 = t->v2[2]; + + det = (z1 - z2) * (x0 - x2) + (x2 - x1) * (z0 - z2); + if (fabsf(det) < 1e-6f) { + return (0); + } + inv_det = 1.0f / det; + + w0 = ((z1 - z2) * (x - x2) + (x2 - x1) * (z - z2)) * inv_det; + w1 = ((z2 - z0) * (x - x2) + (x0 - x2) * (z - z2)) * inv_det; + w2 = 1.0f - w0 - w1; + + if (w0 >= 0.0f && w1 >= 0.0f && w2 >= 0.0f) { + *out_y = w0 * t->v0[1] + w1 * t->v1[1] + w2 * t->v2[1]; + return (1); + } + return (0); +} + +static inline void +tri_normal(const struct col_triangle *t, float n[3]) +{ + float e1[3], e2[3]; + float len; + + e1[0] = t->v1[0] - t->v0[0]; + e1[1] = t->v1[1] - t->v0[1]; + e1[2] = t->v1[2] - t->v0[2]; + + e2[0] = t->v2[0] - t->v0[0]; + e2[1] = t->v2[1] - t->v0[1]; + e2[2] = t->v2[2] - t->v0[2]; + + n[0] = e1[1] * e2[2] - e1[2] * e2[1]; + n[1] = e1[2] * e2[0] - e1[0] * e2[2]; + n[2] = e1[0] * e2[1] - e1[1] * e2[0]; + + len = sqrtf(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]); + if (len > 1e-6f) { + n[0] /= len; + n[1] /= len; + n[2] /= len; + } else { + n[0] = 0.0f; + n[1] = 1.0f; + n[2] = 0.0f; + } +} + +static float +get_ground_height(float px, float pz, float cur_y) +{ + const struct col_cell *c; + float best_below, hit_y, norm[3]; + uint32_t i; + int gx, gz, cx_idx, cz_idx; + + gx = (int)((px - MAP_MIN) / ((MAP_MAX - MAP_MIN) / GRID_SZ)); + gz = (int)((pz - MAP_MIN) / ((MAP_MAX - MAP_MIN) / GRID_SZ)); + if (gx < 0 || gx >= GRID_SZ || gz < 0 || gz >= GRID_SZ) { + return (-1000.0f); + } + + best_below = -1000.0f; + + for (cz_idx = gz - 1; cz_idx <= gz + 1; cz_idx++) { + if (cz_idx < 0 || cz_idx >= GRID_SZ) { + continue; + } + for (cx_idx = gx - 1; cx_idx <= gx + 1; cx_idx++) { + if (cx_idx < 0 || cx_idx >= GRID_SZ) { + continue; + } + c = &g_col_grid[cx_idx][cz_idx]; + for (i = 0; i < c->count; i++) { + tri_normal(&c->tris[i], norm); + if (norm[1] < 0.45f) { + continue; + } + if (ray_tri_intersect_y(&c->tris[i], px, pz, &hit_y)) { + if (hit_y <= cur_y + 0.50f && hit_y > best_below) { + best_below = hit_y; + } + } + } + } + } + return (best_below); +} + +static void +move_and_slide(float *px, float *py, float *pz, float vx, float vz, float dt) +{ + const struct col_cell *c; + float sphere[3][3]; + float closest[3], diff[3], norm[3], push_dir[3]; + float rad, dist_sq, dist, push, h_len, dot, step_x, step_z; + float step_h, ground_y; + int sub, iter, s, i, gx, gz, cx_idx, cz_idx; + + rad = 0.35f; + step_h = 0.5f; + + for (sub = 0; sub < 2; sub++) { + step_x = (vx * dt) * 0.5f; + step_z = (vz * dt) * 0.5f; + + *px += step_x; + *pz += step_z; + + gx = (int)((*px - MAP_MIN) / ((MAP_MAX - MAP_MIN) / GRID_SZ)); + gz = (int)((*pz - MAP_MIN) / ((MAP_MAX - MAP_MIN) / GRID_SZ)); + if (gx < 0 || gx >= GRID_SZ || gz < 0 || gz >= GRID_SZ) { + continue; + } + + for (iter = 0; iter < 3; iter++) { + sphere[0][0] = *px; + sphere[0][1] = *py + step_h + 0.15f; + sphere[0][2] = *pz; + + sphere[1][0] = *px; + sphere[1][1] = *py + step_h + 0.75f; + sphere[1][2] = *pz; + + sphere[2][0] = *px; + sphere[2][1] = *py + step_h + 1.35f; + sphere[2][2] = *pz; + + for (cz_idx = gz - 1; cz_idx <= gz + 1; cz_idx++) { + if (cz_idx < 0 || cz_idx >= GRID_SZ) { + continue; + } + for (cx_idx = gx - 1; cx_idx <= gx + 1; cx_idx++) { + if (cx_idx < 0 || cx_idx >= GRID_SZ) { + continue; + } + c = &g_col_grid[cx_idx][cz_idx]; + + for (i = 0; i < (int)c->count; i++) { + tri_normal(&c->tris[i], norm); + if (norm[1] > 0.50f) { + continue; + } + + for (s = 0; s < 3; s++) { + closest_point_triangle(sphere[s], + c->tris[i].v0, c->tris[i].v1, c->tris[i].v2, + closest); + + diff[0] = sphere[s][0] - closest[0]; + diff[1] = sphere[s][1] - closest[1]; + diff[2] = sphere[s][2] - closest[2]; + + dist_sq = diff[0] * diff[0] + + diff[1] * diff[1] + + diff[2] * diff[2]; + + if (dist_sq < rad * rad) { + dist = sqrtf(dist_sq); + dot = diff[0] * norm[0] + + diff[1] * norm[1] + + diff[2] * norm[2]; + + if (dot < 0.0f || dist < 1e-4f) { + push_dir[0] = norm[0]; + push_dir[2] = norm[2]; + push = rad + dist; + } else { + push_dir[0] = diff[0] / dist; + push_dir[2] = diff[2] / dist; + push = rad - dist; + } + + h_len = sqrtf(push_dir[0] * push_dir[0] + + push_dir[2] * push_dir[2]); + + if (h_len > 1e-4f) { + push_dir[0] /= h_len; + push_dir[2] /= h_len; + *px += push_dir[0] * push; + *pz += push_dir[2] * push; + + dot = vx * push_dir[0] + vz * push_dir[2]; + if (dot < 0.0f) { + vx -= dot * push_dir[0]; + vz -= dot * push_dir[2]; + } + + sphere[0][0] = *px; + sphere[0][2] = *pz; + sphere[1][0] = *px; + sphere[1][2] = *pz; + sphere[2][0] = *px; + sphere[2][2] = *pz; + } + } + } + } + } + } + } + + ground_y = get_ground_height(*px, *pz, *py); + if (ground_y > -900.0f) { + if (ground_y <= *py + step_h) { + if (ground_y > *py) { + *py = ground_y; + } + } + } + } +} + static unsigned int hash_str(const char *str) { @@ -1466,7 +1794,9 @@ ped_lookup_model(int id) } static void -draw_scene(struct sys_gfx *g, const struct mat4 *proj, const struct mat4 *view, float cam_x, float cam_y, float cam_z) +draw_scene(struct sys_gfx *g, const struct mat4 *proj, + const struct mat4 *view, float cam_x, float cam_y, float cam_z, + float player_x, float player_y, float player_z, float player_yaw) { float val, pitch, fov_y, fov_x, far_clp, dx, dy, dz, dist_sq, w, r; int y_horiz, min_gx, max_gx, min_gz, max_gz, id, x, y, gx, gz, i; @@ -1585,13 +1915,17 @@ draw_scene(struct sys_gfx *g, const struct mat4 *proj, const struct mat4 *view, if (skin_model_name) { struct mesh *skin_mesh = cache_get_mesh(g_cache, skin_model_name); if (skin_mesh && skin_mesh->ply_verts) { - struct mat4 rot, trans, world; - mat_identity(&rot); - /* grove street */ - rot.m[0][0] = -1.0f; - rot.m[2][2] = -1.0f; - mat_translate(&trans, 2490.0f, 13.4f, -1670.0f); - mat_mul(&world, &trans, &rot); + struct mat4 rot_y, trans, world; + + mat_identity(&rot_y); + rot_y.m[0][0] = cosf(player_yaw); + rot_y.m[0][2] = sinf(player_yaw); + rot_y.m[2][0] = -sinf(player_yaw); + rot_y.m[2][2] = cosf(player_yaw); + + mat_translate(&trans, player_x, player_y + 1.0f, player_z); + mat_mul(&world, &trans, &rot_y); + mat_mul(&mvp, view, &world); mat_mul(&mvp, proj, &mvp); draw_mesh(skin_mesh, &mvp, 0); @@ -1847,31 +2181,6 @@ draw_infinite_ocean(const struct mat4 *proj, const struct mat4 *view, float cam_ } static void -update_camera(float *cx, float *cy, float *cz, float *cyaw, float *cpitch, const char *keys, int m_dx, int m_dy, float dt) -{ - float speed, rot_speed, cp, sp, cy_val, sy_val; - - speed = 10.0f * dt; - rot_speed = 0.01f * dt; - if (keys[258]) *cyaw -= rot_speed; - if (keys[259]) *cyaw += rot_speed; - if (keys[256]) *cpitch += rot_speed; - if (keys[257]) *cpitch -= rot_speed; - *cyaw += m_dx * 0.002f; - *cpitch -= m_dy * 0.002f; - if (*cpitch > 1.4f) *cpitch = 1.4f; - if (*cpitch < -1.4f) *cpitch = -1.4f; - cp = cosf(*cpitch); - sp = sinf(*cpitch); - cy_val = cosf(*cyaw); - sy_val = sinf(*cyaw); - if (keys['w']) { *cx += sy_val * cp * speed; *cy += sp * speed; *cz += cy_val * cp * speed; } - if (keys['s']) { *cx -= sy_val * cp * speed; *cy -= sp * speed; *cz -= cy_val * cp * speed; } - if (keys['a']) { *cx -= cy_val * speed; *cz += sy_val * speed; } - if (keys['d']) { *cx += cy_val * speed; *cz -= sy_val * speed; } -} - -static void scene_load(struct asset_cache *cache, const uint8_t *data, size_t sz) { char *buf, *line, *next_line, *p; @@ -2007,18 +2316,16 @@ main(void) struct mat4 view; struct timespec ts, t_start, t_end; XEvent sa_event; - float cam_x = 2490.0f, cam_y = 15.0f, cam_z = -1660.0f, cam_yaw = -1.57f, cam_pitch = 0.0f, dt; - int running; - int tar_fd; + float player_x = 2490.0f, player_y = 13.5f, player_z = -1660.0f; + float player_yaw = 0.0f; + float cam_x, cam_y, cam_z; + float cam_yaw = -1.57f, cam_pitch = 0.2f, cam_dist = 3.5f; + float move_forward, move_strafe, move_x, move_z, move_len; + float speed, ground_y, dt; + int running, tar_fd, m_dx, m_dy, gz, gx, last_mx, last_my; struct stat st; - size_t ipl_sz = 0; - const uint8_t *ipl_data; - size_t water_sz = 0; - const uint8_t *water_data; - size_t tc_sz = 0; - const uint8_t *tc_data; - int m_dx, m_dy, gz, gx, i; - int last_mx, last_my; + size_t ipl_sz = 0, water_sz = 0, tc_sz = 0, col_sz = 0; + const uint8_t *ipl_data, *water_data, *tc_data, *col_data; KeyCode code; g_sys = &g; @@ -2031,7 +2338,7 @@ main(void) tar_fd = open("assets.tar", O_RDONLY); if (tar_fd < 0) { - err(1, "failed to open assets.tar. run bin/build first"); + err(1, "failed to open assets.tar"); } fstat(tar_fd, &st); g_tar_size = st.st_size; @@ -2063,13 +2370,33 @@ main(void) memcpy(g.weathers, tc_data + 4, g.num_weathers * 8 * sizeof(struct timecyc_entry)); } + col_data = tar_lookup("assets/collision.bin", &col_sz); + if (col_data && col_sz > 0) { + const uint8_t *ptr = col_data; + for (gz = 0; gz < GRID_SZ; gz++) { + for (gx = 0; gx < GRID_SZ; gx++) { + uint32_t cnt; + memcpy(&cnt, ptr, sizeof(uint32_t)); + ptr += sizeof(uint32_t); + g_col_grid[gx][gz].count = cnt; + if (cnt > 0) { + g_col_grid[gx][gz].tris = (const struct col_triangle *)ptr; + ptr += cnt * sizeof(struct col_triangle); + } else { + g_col_grid[gx][gz].tris = NULL; + } + } + } + } + water_tex = cache_get_tex(g_cache, "waterclear256"); sand_tex = cache_get_tex(g_cache, "sand256"); XMapWindow(g.dpy, g.win); clock_gettime(CLOCK_MONOTONIC, &t_start); XWarpPointer(g.dpy, None, g.win, 0, 0, 0, 0, WIDTH / 2, HEIGHT / 2); - XGrabPointer(g.dpy, g.win, True, PointerMotionMask | ButtonPressMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, g.win, None, CurrentTime); + XGrabPointer(g.dpy, g.win, True, PointerMotionMask | ButtonPressMask | ButtonReleaseMask, + GrabModeAsync, GrabModeAsync, g.win, None, CurrentTime); XSync(g.dpy, False); running = 1; @@ -2081,16 +2408,17 @@ main(void) while (XPending(g.dpy)) { XNextEvent(g.dpy, &sa_event); - if (XFilterEvent(&sa_event, None)) { continue; } - - if (sa_event.type == ClientMessage && (Atom)sa_event.xclient.data.l[0] == g.wm_delete) { + if (sa_event.type == ClientMessage && + (Atom)sa_event.xclient.data.l[0] == g.wm_delete) { running = 0; } if (sa_event.type == FocusIn && !g.in_chat) { - XGrabPointer(g.dpy, g.win, True, PointerMotionMask | ButtonPressMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, g.win, None, CurrentTime); + XGrabPointer(g.dpy, g.win, True, + PointerMotionMask | ButtonPressMask | ButtonReleaseMask, + GrabModeAsync, GrabModeAsync, g.win, None, CurrentTime); } if (sa_event.type == MotionNotify) { last_mx = sa_event.xmotion.x; @@ -2101,7 +2429,9 @@ main(void) if (code == g.key_esc) { if (g.in_chat) { g.in_chat = 0; - XGrabPointer(g.dpy, g.win, True, PointerMotionMask | ButtonPressMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, g.win, None, CurrentTime); + XGrabPointer(g.dpy, g.win, True, + PointerMotionMask | ButtonPressMask | ButtonReleaseMask, + GrabModeAsync, GrabModeAsync, g.win, None, CurrentTime); } else { running = 0; } @@ -2111,9 +2441,10 @@ main(void) KeySym sym; Status status; int n = 0; - + if (g_sys->ic) { - n = Xutf8LookupString(g_sys->ic, &sa_event.xkey, str, sizeof(str), &sym, &status); + n = Xutf8LookupString(g_sys->ic, &sa_event.xkey, str, + sizeof(str), &sym, &status); } else { n = XLookupString(&sa_event.xkey, str, sizeof(str), &sym, NULL); } @@ -2132,7 +2463,9 @@ main(void) } } g.in_chat = 0; - XGrabPointer(g.dpy, g.win, True, PointerMotionMask | ButtonPressMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, g.win, None, CurrentTime); + XGrabPointer(g.dpy, g.win, True, + PointerMotionMask | ButtonPressMask | ButtonReleaseMask, + GrabModeAsync, GrabModeAsync, g.win, None, CurrentTime); } else if (sym == XK_BackSpace) { if (g.chat_len > 0) { while (g.chat_len > 0) { @@ -2143,7 +2476,8 @@ main(void) } g.chat_buf[g.chat_len] = '\0'; } - } else if (n > 0 && (unsigned char)str[0] >= 32 && str[0] != 127 && g.chat_len + n < 127) { + } else if (n > 0 && (unsigned char)str[0] >= 32 && + str[0] != 127 && g.chat_len + n < 127) { memcpy(&g.chat_buf[g.chat_len], str, n); g.chat_len += n; g.chat_buf[g.chat_len] = '\0'; @@ -2153,10 +2487,6 @@ main(void) else if (code == g.key_a) g.keys['a'] = 1; else if (code == g.key_s) g.keys['s'] = 1; else if (code == g.key_d) g.keys['d'] = 1; - else if (code == g.key_up) g.keys[256] = 1; - else if (code == g.key_down) g.keys[257] = 1; - else if (code == g.key_left) g.keys[258] = 1; - else if (code == g.key_right) g.keys[259] = 1; else if (code == g.key_t) { g.in_chat = 1; g.chat_len = 0; @@ -2171,16 +2501,11 @@ main(void) else if (code == g.key_a) g.keys['a'] = 0; else if (code == g.key_s) g.keys['s'] = 0; else if (code == g.key_d) g.keys['d'] = 0; - else if (code == g.key_up) g.keys[256] = 0; - else if (code == g.key_down) g.keys[257] = 0; - else if (code == g.key_left) g.keys[258] = 0; - else if (code == g.key_right) g.keys[259] = 0; } } m_dx = last_mx - WIDTH / 2; m_dy = last_my - HEIGHT / 2; - if (m_dx != 0 || m_dy != 0) { XWarpPointer(g.dpy, None, g.win, 0, 0, 0, 0, WIDTH / 2, HEIGHT / 2); XFlush(g.dpy); @@ -2189,8 +2514,9 @@ main(void) clock_gettime(CLOCK_MONOTONIC, &t_end); dt = (t_end.tv_sec - t_start.tv_sec) + (t_end.tv_nsec - t_start.tv_nsec) * 1e-9f; t_start = t_end; - if (dt > 0.1f) dt = 0.1f; if (dt < 0.001f) dt = 0.001f; - + if (dt > 0.1f) dt = 0.1f; + if (dt < 0.001f) dt = 0.001f; + g.game_time_sec += dt; g.game_time_hours += dt * (24.0f / 1440.0f); if (g.game_time_hours >= 24.0f) { @@ -2198,7 +2524,50 @@ main(void) } if (!g.in_chat) { - update_camera(&cam_x, &cam_y, &cam_z, &cam_yaw, &cam_pitch, g.keys, m_dx, m_dy, dt); + cam_yaw += m_dx * 0.003f; + cam_pitch -= m_dy * 0.003f; + if (cam_pitch > 1.2f) { + cam_pitch = 1.2f; + } + if (cam_pitch < -1.2f) { + cam_pitch = -1.2f; + } + + move_forward = 0.0f; + move_strafe = 0.0f; + if (g.keys['w']) move_forward += 1.0f; + if (g.keys['s']) move_forward -= 1.0f; + if (g.keys['d']) move_strafe += 1.0f; + if (g.keys['a']) move_strafe -= 1.0f; + + move_x = sinf(cam_yaw) * move_forward + cosf(cam_yaw) * move_strafe; + move_z = cosf(cam_yaw) * move_forward - sinf(cam_yaw) * move_strafe; + move_len = sqrtf(move_x * move_x + move_z * move_z); + + if (move_len > 0.001f) { + move_x /= move_len; + move_z /= move_len; + player_yaw = atan2f(move_x, move_z); + speed = 6.0f; + move_and_slide(&player_x, &player_y, &player_z, + move_x * speed, move_z * speed, dt); + } + + ground_y = get_ground_height(player_x, player_z, player_y); + if (ground_y > -900.0f) { + if (player_y > ground_y) { + player_y -= 15.0f * dt; + if (player_y < ground_y) { + player_y = ground_y; + } + } else if (player_y < ground_y) { + player_y = ground_y; + } + } + + cam_x = player_x - sinf(cam_yaw) * cosf(cam_pitch) * cam_dist; + cam_y = player_y + 1.2f - sinf(cam_pitch) * cam_dist; + cam_z = player_z - cosf(cam_yaw) * cosf(cam_pitch) * cam_dist; } update_weather(&g); @@ -2206,53 +2575,15 @@ main(void) memset(g.zbuffer, 0, WIDTH * HEIGHT * sizeof(float)); mat_view(&view, cam_x, cam_y, cam_z, cam_yaw, cam_pitch); - draw_scene(&g, &g.proj, &view, cam_x, cam_y, cam_z); + draw_scene(&g, &g.proj, &view, cam_x, cam_y, cam_z, + player_x, player_y, player_z, player_yaw); draw_water(&g.proj, &view); draw_infinite_ocean(&g.proj, &view, cam_x, cam_z, g.game_time_sec); gfx_present(&g); } + munmap(g_tar_mapped, g_tar_size); close(tar_fd); gfx_cleanup(&g); - - for (i = 0; i < g_cache->mesh_count; i++) { - free(g_cache->meshes[i].ply_verts); - free(g_cache->meshes[i].indices); - free(g_cache->meshes[i].mat_ids); - if (g_cache->meshes[i].num_materials > 0) { - free(g_cache->meshes[i].raw_tex_names); - free(g_cache->meshes[i].textures); - } - } - for (i = 0; i < g_cache->tex_count; i++) { - free(g_cache->textures[i].pixels); - } - free(g_cache); - - for (gz = 0; gz < GRID_SZ; gz++) { - for (gx = 0; gx < GRID_SZ; gx++) { - free(scene_grid[gx][gz].ids); - } - } - - free(g_transforms.pos_x); - free(g_transforms.pos_y); - free(g_transforms.pos_z); - free(g_transforms.rot_qx); - free(g_transforms.rot_qy); - free(g_transforms.rot_qz); - free(g_transforms.rot_qw); - free(g_transforms.world_matrices); - free(g_transforms.sphere_cx); - free(g_transforms.sphere_cy); - free(g_transforms.sphere_cz); - free(g_transforms.sphere_r); - free(g_transforms.time_on); - free(g_transforms.time_off); - - free(scene_meshes); - free(water_faces); - free(draw_list); - return (0); }