commit d3339c3c93cbaa9bc1efc4a0cb3dca9c0953039c
parent d5ad0965b955e167c1def42a448b2384cd667587
Author: averagecoder <averagecoder@noreply.codeberg.org>
Date: Sun, 23 Aug 2026 20:17:42 +0300
feat: add colls, gravity
Diffstat:
| M | src/build.c | | | 481 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- |
| M | src/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);
}