antizona

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

commit e0a00cc206a04e6b122e1f6c9d8d2ebfda689b9d
Author: averagecoder <averagecoder@noreply.codeberg.org>
Date:   Mon, 29 Jun 2026 05:31:04 +0300

init: software render and asset compiler

Diffstat:
A.gitignore | 3+++
AMakefile | 30++++++++++++++++++++++++++++++
AREADME.md | 76++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/build.c | 2023+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/render.c | 2271+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 4403 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,3 @@ +assets.tar +bin/ +gta/ diff --git a/Makefile b/Makefile @@ -0,0 +1,30 @@ +CC = cc +CFLAGS = -O3 -Wall -Wextra -Wpedantic -std=c99 -pthread -MMD -MP +LDFLAGS = -lm -pthread + +X11_CFLAGS = -I/usr/X11R6/include -I/usr/local/include +X11_LDFLAGS = -L/usr/X11R6/lib -L/usr/local/lib -lX11 -lXext + +BIN_DIR = bin +SRC_DIR = src + +TARGETS = $(BIN_DIR)/build \ + $(BIN_DIR)/render + +all: $(BIN_DIR) $(TARGETS) + +$(BIN_DIR): + mkdir -p $(BIN_DIR) + +$(BIN_DIR)/build: $(SRC_DIR)/build.c + $(CC) $(CFLAGS) -o $@ $(SRC_DIR)/build.c -lm + +$(BIN_DIR)/render: $(SRC_DIR)/render.c + $(CC) $(CFLAGS) $(X11_CFLAGS) -o $@ $(SRC_DIR)/render.c $(LDFLAGS) $(X11_LDFLAGS) + +clean: + rm -rf $(BIN_DIR) assets assets.tar + +.PHONY: all clean + +-include $(SRC_DIR)/*.d diff --git a/README.md b/README.md @@ -0,0 +1,76 @@ +# antizona + +A minimalist, high-performance open-source multiplayer engine and framework +for Grand Theft Auto: San Andreas, written in pure C99. + +Antizona is designed as a lightweight, secure alternative to modern bloated SA-MP +and MTA platforms. Instead of pay-to-win mechanics, heavy server-side scripts, and +unoptimized custom assets, the project focuses on a deep, systemic, player-driven +Roleplay (RP) environment. Every element of the world—from weapon manufacturing and +logistics to crime and law enforcement—is simulated as an interconnected, logical +cycle driven solely by player actions, with zero administrative or donation bloat. + +## Features + +- **Multi-threaded Asset Compiler**: Converts models (DFF) to PLY and textures (TXD) + to TGA directly from GTA3.IMG and GTA_INT.IMG. +- **Two-Pass Compilation**: Fully loads the 14,929 model IDE database before parsing + any IPL map placement, ensuring 100% correct object resolution. +- **Ultra-Lightweight Renderer**: Zero-dependency software rasterizer utilizing an + OpenSSL-secured network and local asset pipeline. +- **Strict POSIX Compliance**: Written in pure C99 with zero third-party dependencies, + ensuring maximum portability and performance. + +## Roadmap + +### Phase 1: Local Collision & Player Physics +- Implement efficient Ray-Triangle intersection using the existing spatial grid `scene_grid` for ground height snapping. +- Build basic player movement physics: standard gravity and simple bounding-sphere world collisions. + +### Phase 2: Asynchronous UDP Networking +- Implement a minimalist asynchronous UDP server (`server.c`) utilizing Linux `epoll` or BSD `kqueue` for sub-millisecond execution. +- Serialize compact binary player state packets (ID, XYZ coordinates, Yaw, active actions). +- Implement local spatial partitioning on the server to broadcast state packets only to visible neighboring players. + +### Phase 3: Player Synchronization & Rendering +- Load dynamic player `.dff` skins (gang members, CJ) at runtime. +- Render other players as dynamic meshes using the existing `draw_mesh` pipeline. +- Implement a lightweight, low-overhead skeletal animation state machine. + +### Phase 4: Core Systemic Interaction +- Low-latency hitscan raycasting calculated against target bounding spheres (`sphere_cx`, `sphere_cy`, `sphere_cz`, `sphere_r`). +- Basic player-to-world interaction (inventory item pickups, server-side state verification). + +## Requirements + +- OpenSSL (libssl, libcrypto) +- X11 (libX11, libXext) +- POSIX threads (pthread) + +## Building + +To compile both the asset compiler and the software rasterizer: + +```bash +make +``` + +## Usage + +1. Compile and pack the game assets: + ```bash + ./bin/build /path/to/gta_sa_directory + ``` + +2. Run the software rasterizer: + ```bash + ./bin/render + ``` + +## Support / Donations + +If you appreciate financial privacy and want to support the development of a fully +independent, decentralized, and s_u_c_k_l_e_s_s GTA SA multiplayer engine, you can donate +via **Monero (XMR)**: + +`83xqB91zW31gZkw9sbeT3vjcFQ22L69nKUyCEWgTnWYYfZ4E7gbEVAX5dWh6hCpk6RGqCatZUgjvzGiDYL9HZ6dVCWoa6Jh` diff --git a/src/build.c b/src/build.c @@ -0,0 +1,2023 @@ +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <ctype.h> +#include <dirent.h> +#include <err.h> +#include <fcntl.h> +#include <math.h> +#include <pthread.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#define MAX_FILES 32768 +#define NAME_SZ 24 +#define PATH_CACHE_SZ 16384 +#define PATH_DB_SZ 65536 + +struct file_list { + char names[MAX_FILES][NAME_SZ]; + int count; +}; + +struct img_header { + char magic[4]; + uint32_t entries; +}; + +struct img_entry { + uint32_t offset; + uint16_t size; + uint16_t size2; + char name[24]; +}; + +struct ide_entry { + int id; + char model[NAME_SZ]; + char txd[NAME_SZ]; + int time_on; + int time_off; +}; + +struct mstream { + const uint8_t *data; + size_t size; + size_t pos; +}; + +struct path_entry { + char key[512]; + char resolved[1024]; +}; + +struct rw_header { + uint32_t type; + uint32_t size; + uint32_t version; +} __attribute__((packed)); + +struct mat4 { float m[4][4]; }; +struct rw_triangle { uint16_t v1, v2, m, v3; }; +struct atomic_entry { uint32_t f, g; }; +struct tex_name { char name[32]; }; + +struct water_face { + float x[4], y[4], z[4]; + float u[4], v[4], h[4]; + int num; +}; + +struct timecyc_entry { + uint8_t amb[3]; + uint8_t dir[3]; + uint8_t sky_top[3]; + uint8_t sky_bot[3]; + uint8_t water[4]; + float far_clp; + float fog_st; +}; + +struct path_cache_entry { + char rel[512]; + char resolved[1024]; +}; + +struct arena { + uint8_t *mem; + size_t pos; + size_t size; +}; + +struct thread_arg { + const uint8_t *img_data; + struct img_entry *entries; + uint32_t start; + uint32_t end; + struct file_list *fl; + struct arena arena; +}; + +struct tga_header { + uint8_t id_length; + uint8_t color_map_type; + uint8_t image_type; + uint16_t color_map_first; + uint16_t color_map_length; + uint8_t color_map_entry_size; + uint16_t x_origin; + uint16_t y_origin; + uint16_t width; + uint16_t height; + uint8_t pixel_depth; + uint8_t image_descriptor; +} __attribute__((packed)); + +struct tar_header { + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char chksum[8]; + char typeflag; + char linkname[100]; + char magic[6]; + char version[2]; + char uname[32]; + char gname[32]; + char devmajor[8]; + char devminor[8]; + char prefix[155]; + char pad[12]; +} __attribute__((packed)); + +static struct ide_entry ide_db[65536]; +static int ide_count = 0; +static struct path_entry path_db[PATH_DB_SZ]; +static int path_db_count = 0; + +static char existing_models[32768][NAME_SZ]; +static int existing_models_count = 0; +static char existing_textures[32768][NAME_SZ]; +static int existing_textures_count = 0; + +static struct water_face water_db[8192]; +static int water_face_count = 0; + +static void +to_lower_str(char *str) +{ + while (*str) { + *str = tolower((unsigned char)*str); + str++; + } +} + +static void +scan_existing_assets(void) +{ + DIR *dir; + struct dirent *de; + char *ext; + size_t len; + + dir = opendir("assets/models"); + if (dir) { + while ((de = readdir(dir))) { + ext = strrchr(de->d_name, '.'); + if (ext && strcasecmp(ext, ".ply") == 0 && + existing_models_count < 32768) { + len = ext - de->d_name; + if (len >= NAME_SZ) { + len = NAME_SZ - 1; + } + memcpy(existing_models[existing_models_count], + de->d_name, len); + existing_models[existing_models_count][len] = '\0'; + to_lower_str(existing_models[existing_models_count]); + existing_models_count++; + } + } + closedir(dir); + } + + dir = opendir("assets/textures"); + if (dir) { + while ((de = readdir(dir))) { + ext = strrchr(de->d_name, '.'); + if (ext && strcasecmp(ext, ".tga") == 0 && + existing_textures_count < 32768) { + len = ext - de->d_name; + if (len >= NAME_SZ) { + len = NAME_SZ - 1; + } + memcpy(existing_textures[existing_textures_count], + de->d_name, len); + existing_textures[existing_textures_count][len] = '\0'; + to_lower_str(existing_textures[existing_textures_count]); + existing_textures_count++; + } + } + closedir(dir); + } +} + +static int +asset_cached(const char *name, int is_model) +{ + char base[NAME_SZ]; + size_t len; + int i; + + len = strlen(name); + if (len < 5) { + return (0); + } + + strncpy(base, name, len - 4); + base[len - 4] = '\0'; + to_lower_str(base); + + if (is_model) { + for (i = 0; i < existing_models_count; i++) { + if (strcmp(existing_models[i], base) == 0) { + return (1); + } + } + } else { + for (i = 0; i < existing_textures_count; i++) { + if (strcmp(existing_textures[i], base) == 0) { + return (1); + } + } + } + return (0); +} + +static int +asset_exists(const char *name) +{ + if (strstr(name, ".dff") || strstr(name, ".DFF")) { + return (asset_cached(name, 1)); + } + if (strstr(name, ".txd") || strstr(name, ".TXD")) { + return (asset_cached(name, 0)); + } + return (0); +} + +static void * +arena_alloc(struct arena *a, size_t sz) +{ + size_t align; + void *ptr; + + align = (sz + 7) & ~7; + if (a->pos + align > a->size) { + errx(1, "OOM arena"); + } + ptr = a->mem + a->pos; + a->pos += align; + return (ptr); +} +#define A_ALLOC(a, type, count) (type *)arena_alloc((a), sizeof(type) * (count)) + +static void +normalize_key(const char *src, char *dst, size_t sz) +{ + size_t i = 0; + while (*src && i < sz - 1) { + if (*src != '/' && *src != '\\') { + dst[i++] = tolower((unsigned char)*src); + } + src++; + } + dst[i] = '\0'; +} + +static void +index_directory_tree(const char *dir_path) +{ + DIR *dir; + struct dirent *de; + char sub_path[1024]; + struct stat st; + + if (!(dir = opendir(dir_path))) { + return; + } + + while ((de = readdir(dir))) { + if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) { + continue; + } + + snprintf(sub_path, sizeof(sub_path), "%s/%s", dir_path, de->d_name); + + if (stat(sub_path, &st) == 0) { + if (S_ISDIR(st.st_mode)) { + index_directory_tree(sub_path); + } else { + if (path_db_count < PATH_DB_SZ) { + normalize_key(sub_path, path_db[path_db_count].key, 512); + strncpy(path_db[path_db_count].resolved, sub_path, 1024); + path_db_count++; + } + } + } + } + closedir(dir); +} + + +static size_t +mread(void *dst, size_t sz, size_t n, struct mstream *s) +{ + size_t bytes; + + bytes = sz * n; + if (s->pos + bytes <= s->size) { + memcpy(dst, s->data + s->pos, bytes); + s->pos += bytes; + return (n); + } + return (0); +} + +static void +mat_identity(struct mat4 *m) +{ + memset(m, 0, sizeof(*m)); + m->m[0][0] = 1.0f; + m->m[1][1] = 1.0f; + m->m[2][2] = 1.0f; + m->m[3][3] = 1.0f; +} + +static inline void +mat_mul(struct mat4 *dst, const struct mat4 *a, const struct mat4 *b) +{ + struct mat4 r; + int i; + + for (i = 0; i < 4; i++) { + r.m[i][0] = a->m[i][0]*b->m[0][0] + a->m[i][1]*b->m[1][0] + + a->m[i][2]*b->m[2][0] + a->m[i][3]*b->m[3][0]; + r.m[i][1] = a->m[i][0]*b->m[0][1] + a->m[i][1]*b->m[1][1] + + a->m[i][2]*b->m[2][1] + a->m[i][3]*b->m[3][1]; + r.m[i][2] = a->m[i][0]*b->m[0][2] + a->m[i][1]*b->m[1][2] + + a->m[i][2]*b->m[2][2] + a->m[i][3]*b->m[3][2]; + r.m[i][3] = a->m[i][0]*b->m[0][3] + a->m[i][1]*b->m[1][3] + + a->m[i][2]*b->m[2][3] + a->m[i][3]*b->m[3][3]; + } + *dst = r; +} + +static int +resolve_ci(const char *root, const char *rel, char *out, size_t out_sz) +{ + char target_key[1024]; + char combined[1536]; + int i; + + snprintf(combined, sizeof(combined), "%s/%s", root, rel); + normalize_key(combined, target_key, sizeof(target_key)); + + for (i = 0; i < path_db_count; i++) { + if (strcmp(path_db[i].key, target_key) == 0) { + strncpy(out, path_db[i].resolved, out_sz - 1); + out[out_sz - 1] = '\0'; + return (1); + } + } + return (0); +} + +static FILE * +fopen_ci(const char *root, const char *rel) +{ + char resolved[1024]; + + if (resolve_ci(root, rel, resolved, sizeof(resolved))) { + return (fopen(resolved, "rb")); + } + return (NULL); +} + +static void +trim_spaces(char *str) +{ + char *start, *end; + + start = str; + while (*start == ' ' || *start == '\t') { + start++; + } + if (start != str) { + memmove(str, start, strlen(start) + 1); + } + end = str + strlen(str) - 1; + while (end > str && (*end == ' ' || *end == '\t' || *end == '\r' || + *end == '\n')) { + *end = '\0'; + end--; + } +} + +static int +list_has(struct file_list *l, const char *name) +{ + int i; + + for (i = 0; i < l->count; i++) { + if (strcasecmp(l->names[i], name) == 0) { + return (1); + } + } + return (0); +} + +static void +list_add(struct file_list *l, const char *name) +{ + size_t len, i; + + if (l->count >= MAX_FILES || list_has(l, name)) { + return; + } + len = strlen(name); + if (len >= NAME_SZ) { + len = NAME_SZ - 1; + } + for (i = 0; i < len; i++) { + l->names[l->count][i] = tolower((unsigned char)name[i]); + } + l->names[l->count][len] = '\0'; + l->count++; +} + +static void +clean_ide_line(char *p) +{ + char *comment; + char *end; + + comment = strchr(p, '#'); + if (comment) { + *comment = '\0'; + } + + end = p + strlen(p) - 1; + while (end >= p && (*end == ' ' || *end == '\t' || *end == '\r' || *end == '\n')) { + *end = '\0'; + end--; + } +} + +static void +preparse_ide(const char *root, const char *rel) +{ + FILE *f; + char line[512]; + char *p; + int in_tobj; + + f = fopen_ci(root, rel); + if (!f) { + return; + } + + in_tobj = 0; + + while (fgets(line, sizeof(line), f)) { + p = line; + line[strcspn(line, "\r\n")] = '\0'; + while (*p == ' ' || *p == '\t') { + p++; + } + if (*p == '\0' || *p == '#') { + continue; + } + + if (strncasecmp(p, "objs", 4) == 0) { + in_tobj = 0; + continue; + } + if (strncasecmp(p, "tobj", 4) == 0) { + in_tobj = 1; + continue; + } + if (strncasecmp(p, "end", 3) == 0) { + in_tobj = 0; + continue; + } + + if (isdigit((unsigned char)p[0]) && ide_count < 65536) { + char model[NAME_SZ], txd[NAME_SZ]; + char *c1, *c2, *c3; + char *last_comma, *prev_comma; + size_t len; + int time_on = 0, time_off = 0; + + c1 = strchr(p, ','); + if (!c1) { + continue; + } + c2 = strchr(c1 + 1, ','); + if (!c2) { + continue; + } + c3 = strchr(c2 + 1, ','); + if (!c3) { + continue; + } + + len = c2 - (c1 + 1); + if (len >= NAME_SZ) { + len = NAME_SZ - 1; + } + memcpy(model, c1 + 1, len); + model[len] = '\0'; + trim_spaces(model); + to_lower_str(model); + + len = c3 - (c2 + 1); + if (len >= NAME_SZ) { + len = NAME_SZ - 1; + } + memcpy(txd, c2 + 1, len); + txd[len] = '\0'; + trim_spaces(txd); + to_lower_str(txd); + + if (in_tobj) { + char temp_line[512]; + strncpy(temp_line, p, sizeof(temp_line) - 1); + temp_line[sizeof(temp_line) - 1] = '\0'; + + /* sanitize trailing spaces and comments first */ + clean_ide_line(temp_line); + + last_comma = strrchr(temp_line, ','); + if (last_comma) { + time_off = atoi(last_comma + 1); + *last_comma = '\0'; + prev_comma = strrchr(temp_line, ','); + if (prev_comma) { + time_on = atoi(prev_comma + 1); + } + *last_comma = ','; + } + } + + ide_db[ide_count].id = atoi(p); + strncpy(ide_db[ide_count].model, model, NAME_SZ); + strncpy(ide_db[ide_count].txd, txd, NAME_SZ); + ide_db[ide_count].time_on = time_on; + ide_db[ide_count].time_off = time_off; + ide_count++; + } + } + fclose(f); +} + +static const struct ide_entry * +ide_lookup(int id) +{ + int i; + + for (i = 0; i < ide_count; i++) { + if (ide_db[i].id == id) { + return (&ide_db[i]); + } + } + return (NULL); +} + +static int +is_lod_name(const char *name) +{ + return (strstr(name, "lod") != NULL); +} + +static void +parse_text_ipl(const char *buf, size_t size, FILE *out, struct file_list *fl) +{ + const char *p, *end, *next; + char line[512]; + char *s, *comma; + size_t len; + int in_inst; + + p = buf; + end = buf + size; + in_inst = 0; + + while (p < end) { + next = memchr(p, '\n', end - p); + len = next ? (size_t)(next - p) : (size_t)(end - p); + if (len >= sizeof(line)) { + len = sizeof(line) - 1; + } + memcpy(line, p, len); + line[len] = '\0'; + p = next ? next + 1 : end; + + s = line; + while (*s == ' ' || *s == '\t' || *s == '\r') { + s++; + } + if (*s == '\0' || *s == '#') { + continue; + } + + if (strncasecmp(s, "inst", 4) == 0 || strncasecmp(s, "tobj", 4) == 0) { + in_inst = 1; + fprintf(out, "\ninst\n"); + continue; + } + if (strncasecmp(s, "end", 3) == 0) { + in_inst = 0; + fprintf(out, "end\n"); + continue; + } + + if (in_inst) { + int id; + const struct ide_entry *ide; + + id = atoi(s); + ide = ide_lookup(id); + if (ide && !is_lod_name(ide->model)) { + char d_name[32], t_name[32]; + + snprintf(d_name, sizeof(d_name), "%s.dff", ide->model); + snprintf(t_name, sizeof(t_name), "%s.txd", ide->txd); + list_add(fl, d_name); + list_add(fl, t_name); + + comma = strchr(s, ','); + if (comma) { + int dummy_id; + char dummy_model[64]; + int interior; + float px, py, pz, rx, ry, rz, rw; + + if (sscanf(s, "%d, %63[^,], %d, %f, " + "%f, %f, %f, %f, %f, %f", + &dummy_id, dummy_model, &interior, + &px, &py, &pz, &rx, &ry, &rz, &rw) == 10) { + fprintf(out, "%d, %s, %d, %f, " + "%f, %f, %f, %f, %f, %f, " + "-1, %d, %d\n", + id, ide->model, interior, + px, py, pz, rx, ry, rz, rw, + ide->time_on, ide->time_off); + } + } + } + } + } +} + +static void +parse_binary_ipl(const uint8_t *buf, size_t size, FILE *out, struct file_list *fl) +{ + uint32_t num, off, i; + + memcpy(&num, buf + 4, 4); + memcpy(&off, buf + 28, 4); + if (off + num * 40 > size) { + return; + } + + fprintf(out, "\ninst\n"); + for (i = 0; i < num; i++) { + struct { + float px, py, pz, rx, ry, rz, rw; + int32_t id, interior, lod; + } inst; + const struct ide_entry *ide; + + memcpy(&inst, buf + off + i * 40, 40); + + ide = ide_lookup(inst.id); + if (ide && !is_lod_name(ide->model)) { + char d_name[32], t_name[32]; + + snprintf(d_name, sizeof(d_name), "%s.dff", ide->model); + snprintf(t_name, sizeof(t_name), "%s.txd", ide->txd); + list_add(fl, d_name); + list_add(fl, t_name); + + fprintf(out, "%d, %s, %d, %f, %f, %f, %f, %f, %f, %f, " + "-1, %d, %d\n", + inst.id, ide->model, inst.interior, + inst.px, inst.py, inst.pz, inst.rx, inst.ry, + inst.rz, inst.rw, ide->time_on, ide->time_off); + } + } + fprintf(out, "end\n"); +} + +static void +blur_pixels(uint32_t *pixels, int w, int h) +{ + uint32_t *temp; + int x, y, kx, ky, px, py, count; + uint32_t r_sum, g_sum, b_sum, a_sum, c; + + temp = malloc(w * h * 4); + if (!temp) { + return; + } + memcpy(temp, pixels, w * h * 4); + + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + r_sum = 0; + g_sum = 0; + b_sum = 0; + a_sum = 0; + count = 0; + for (ky = -1; ky <= 1; ky++) { + for (kx = -1; kx <= 1; kx++) { + px = x + kx; + py = y + ky; + if (px >= 0 && px < w && py >= 0 && + py < h) { + c = temp[py * w + px]; + b_sum += (c & 0xFF); + g_sum += ((c >> 8) & 0xFF); + r_sum += ((c >> 16) & 0xFF); + a_sum += ((c >> 24) & 0xFF); + count++; + } + } + } + pixels[y * w + x] = (b_sum/count) | + ((g_sum/count) << 8) | ((r_sum/count) << 16) | + ((a_sum/count) << 24); + } + } + free(temp); +} + +static int +nearest_pow2(int x) +{ + int p = 1; + + while (p * 2 <= x) { + p *= 2; + } + if (x - p < p * 2 - x) { + return (p); + } + return (p * 2); +} + +static void +convert_txd(const uint8_t *data, size_t size, const char *name, struct arena *a) +{ + struct mstream ms = { data, size, 0 }; + struct rw_header h, sh; + struct { + uint32_t plt, flt; char n[32], m[32]; uint32_t fmt; char fcc[4]; + uint16_t w, h; uint8_t d, lvl, typ, cmp; + } __attribute__((packed)) rh; + uint32_t mip_size, p_size, *pixels, code, cols[4]; + uint8_t *cmp, r0, g0, b0, r1, g1, b1, a_val, *src; + uint16_t c0, c1, *src16; + size_t chunk_end; + int bx, by, is_dxt3, x, y, i, j, k; + char t_name[32], out_name[256]; + FILE *out; + struct tga_header out_hdr; + uint32_t *final_pixels; + int new_w, new_h, sy, sx; + + while (mread(&h, sizeof(h), 1, &ms) == 1) { + chunk_end = ms.pos + h.size; + if (h.type == 0x16) { + continue; + } + + if (h.type == 0x15) { + if (mread(&sh, sizeof(sh), 1, &ms) != 1) { + break; + } + if (mread(&rh, sizeof(rh), 1, &ms) != 1) { + break; + } + + if (rh.plt != 9 && rh.plt != 8) { + ms.pos = chunk_end; + continue; + } + + if (mread(&mip_size, 4, 1, &ms) != 1) { + break; + } + + p_size = rh.w * rh.h; + pixels = A_ALLOC(a, uint32_t, p_size); + memset(pixels, 0, p_size * 4); + + if (memcmp(rh.fcc, "DXT1", 4) == 0 || + memcmp(rh.fcc, "DXT3", 4) == 0) { + cmp = A_ALLOC(a, uint8_t, mip_size); + mread(cmp, 1, mip_size, &ms); + bx = rh.w / 4; + by = rh.h / 4; + is_dxt3 = memcmp(rh.fcc, "DXT3", 4) == 0; + + for (y = 0; y < by; y++) { + for (x = 0; x < bx; x++) { + const uint8_t *blk = cmp + + (y * bx + x) * (is_dxt3 ? 16 : 8); + const uint8_t *cblk = is_dxt3 ? + blk + 8 : blk; + + c0 = cblk[0] | (cblk[1] << 8); + c1 = cblk[2] | (cblk[3] << 8); + code = cblk[4] | (cblk[5] << 8) | + (cblk[6] << 16) | (cblk[7] << 24); + + r0 = ((c0 >> 11) & 0x1F) << 3; + g0 = ((c0 >> 5) & 0x3F) << 2; + b0 = (c0 & 0x1F) << 3; + r1 = ((c1 >> 11) & 0x1F) << 3; + g1 = ((c1 >> 5) & 0x3F) << 2; + b1 = (c1 & 0x1F) << 3; + + cols[0] = b0 | (g0 << 8) | + (r0 << 16) | (0xFFU << 24); + cols[1] = b1 | (g1 << 8) | + (r1 << 16) | (0xFFU << 24); + if (c0 > c1 || is_dxt3) { + cols[2] = ((2*b0+b1)/3) | + (((2*g0+g1)/3)<<8) | + (((2*r0+r1)/3)<<16) | + (0xFFU<<24); + cols[3] = ((b0+2*b1)/3) | + (((g0+2*g1)/3)<<8) | + (((r0+2*r1)/3)<<16) | + (0xFFU<<24); + } else { + cols[2] = ((b0+b1)/2) | + (((g0+g1)/2)<<8) | + (((r0+r1)/2)<<16) | + (0xFFU<<24); + cols[3] = 0; + } + + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + int px_idx = x * 4 + j; + int py_idx = y * 4 + i; + if (px_idx < rh.w && + py_idx < rh.h) { + uint32_t c = cols[(code >> (2 * (i * 4 + j))) & 3]; + if (is_dxt3) { + a_val = (blk[i * 2 + (j / 2)] >> (4 * (j % 2))) & 0x0F; + a_val = (a_val << 4) | a_val; + c = (c & 0x00FFFFFF) | ((uint32_t)a_val << 24); + } + pixels[py_idx * rh.w + px_idx] = c; + } + } + } + } + } + } else if (rh.d == 32) { + mread(pixels, 1, mip_size, &ms); + if (rh.fmt == 22) { + for (i = 0; (uint32_t)i < p_size; i++) { + pixels[i] |= (255U << 24); + } + } + } else if (rh.d == 16) { + src16 = malloc(mip_size); + if (src16) { + mread(src16, 1, mip_size, &ms); + for (i = 0; (uint32_t)i < p_size; i++) { + uint16_t val = src16[i]; + uint8_t r = 0, g = 0, b = 0, a_v = 255; + if (rh.fmt == 26) { + a_v = ((val >> 12) & 0x0F) * 17; + r = ((val >> 8) & 0x0F) * 17; + g = ((val >> 4) & 0x0F) * 17; + b = (val & 0x0F) * 17; + } else if (rh.fmt == 25) { + a_v = ((val >> 15) & 0x01) ? 255 : 0; + r = ((val >> 10) & 0x1F) << 3; + g = ((val >> 5) & 0x1F) << 3; + b = (val & 0x1F) << 3; + r |= (r >> 5); g |= (g >> 5); b |= (b >> 5); + } else if (rh.fmt == 23) { + r = ((val >> 11) & 0x1F) << 3; + g = ((val >> 5) & 0x3F) << 2; + b = (val & 0x1F) << 3; + r |= (r >> 5); g |= (g >> 6); b |= (b >> 5); + a_v = 255; + } + pixels[i] = (a_v << 24) | (r << 16) | (g << 8) | b; + } + free(src16); + } + } else if (rh.d == 24) { + src = malloc(mip_size); + if (src) { + mread(src, 1, mip_size, &ms); + for (i = 0; (uint32_t)i < p_size; i++) { + pixels[i] = (255U << 24) | + (src[i*3+2] << 16) | + (src[i*3+1] << 8) | src[i*3+0]; + } + free(src); + } + } + + for (k = 0; k < 31 && rh.n[k]; k++) { + t_name[k] = tolower((unsigned char)rh.n[k]); + } + t_name[k] = '\0'; + if (t_name[0] == '\0') { + strncpy(t_name, name, 31); + t_name[31] = '\0'; + } + to_lower_str(t_name); + + if (strstr(t_name, "waterclear256")) { + blur_pixels(pixels, rh.w, rh.h); + blur_pixels(pixels, rh.w, rh.h); + } + + /* smart resize to power of two on compiling */ + new_w = nearest_pow2(rh.w); + new_h = nearest_pow2(rh.h); + final_pixels = pixels; + + if (new_w != rh.w || new_h != rh.h) { + final_pixels = malloc(new_w * new_h * 4); + if (final_pixels) { + for (y = 0; y < new_h; y++) { + sy = (y * rh.h) / new_h; + if (sy >= rh.h) { + sy = rh.h - 1; + } + for (x = 0; x < new_w; x++) { + sx = (x * rh.w) / new_w; + if (sx >= rh.w) { + sx = rh.w - 1; + } + final_pixels[y * new_w + x] = pixels[sy * rh.w + sx]; + } + } + } else { + final_pixels = pixels; + new_w = rh.w; + new_h = rh.h; + } + } + + snprintf(out_name, sizeof(out_name), "assets/textures/%s.tga", t_name); + out = fopen(out_name, "wb"); + if (out) { + memset(&out_hdr, 0, sizeof(out_hdr)); + out_hdr.image_type = 2; + out_hdr.width = new_w; + out_hdr.height = new_h; + out_hdr.pixel_depth = 32; + out_hdr.image_descriptor = 8; + + fwrite(&out_hdr, sizeof(out_hdr), 1, out); + fwrite(final_pixels, 4, new_w * new_h, out); + fclose(out); + } + + if (final_pixels != pixels) { + free(final_pixels); + } + } + ms.pos = chunk_end; + } +} + +static void +process_particle_txd(const char *root, struct arena *a) +{ + FILE *f; + size_t sz; + uint8_t *buf; + + f = fopen_ci(root, "models/particle.txd"); + if (!f) { + return; + } + fseek(f, 0, SEEK_END); + sz = ftell(f); + fseek(f, 0, SEEK_SET); + buf = malloc(sz); + if (fread(buf, 1, sz, f) == sz) { + a->pos = 0; + convert_txd(buf, sz, "particle", a); + } + free(buf); + fclose(f); +} + +struct frame { struct mat4 abs; int32_t parent; }; +struct geom { float *v, *u; uint8_t *c; uint16_t *i, *m; uint32_t nv, ni, nm; struct tex_name *t; }; + +static int +is_container(uint32_t type) +{ + switch (type) { + case 0x03: case 0x06: case 0x07: case 0x08: case 0x0E: case 0x0F: + case 0x10: case 0x14: case 0x1A: case 0x1B: + return (1); + } + return (0); +} + +static void +convert_dff(const uint8_t *data, size_t size, const char *name, struct arena *a) +{ + struct mstream ms = { data, size, 0 }; + struct rw_header h; + struct frame *frames = NULL; + struct geom *geoms = NULL; + struct atomic_entry *atomics; + uint32_t n_frames = 0, n_geoms = 0, n_atomics = 0; + uint32_t last_cont = 0; + int32_t cur_g = -1, cur_m = -1, wait_tex = 0; + size_t end; + + atomics = A_ALLOC(a, struct atomic_entry, 256); + + while (mread(&h, sizeof(h), 1, &ms) == 1) { + end = ms.pos + h.size; + + if (is_container(h.type)) { + last_cont = h.type; + if (h.type == 0x0F) cur_g++; + if (h.type == 0x08) cur_m = -1; + if (h.type == 0x07) cur_m++; + if (h.type == 0x06) wait_tex = 1; + continue; + } + + if (h.type == 0x01) { + if (last_cont == 0x0E) { + mread(&n_frames, 4, 1, &ms); + frames = A_ALLOC(a, struct frame, n_frames); + for (uint32_t i = 0; i < n_frames; i++) { + float rot[9], pos[3]; int32_t parent, flags; + mread(rot, 4, 9, &ms); mread(pos, 4, 3, &ms); + mread(&parent, 4, 1, &ms); mread(&flags, 4, 1, &ms); + + struct mat4 l; memset(&l, 0, sizeof(l)); + l.m[0][0] = rot[0]; l.m[0][1] = rot[3]; l.m[0][2] = rot[6]; + l.m[1][0] = rot[1]; l.m[1][1] = rot[4]; l.m[1][2] = rot[7]; + l.m[2][0] = rot[2]; l.m[2][1] = rot[5]; l.m[2][2] = rot[8]; + l.m[0][3] = pos[0]; l.m[1][3] = pos[1]; l.m[2][3] = pos[2]; l.m[3][3] = 1.0f; + + if (parent >= 0 && parent < (int32_t)i) { + mat_mul(&frames[i].abs, &frames[parent].abs, &l); + } else { + float sx = sqrtf(l.m[0][0]*l.m[0][0] + l.m[1][0]*l.m[1][0] + l.m[2][0]*l.m[2][0]); + float sy = sqrtf(l.m[0][1]*l.m[0][1] + l.m[1][1]*l.m[1][1] + l.m[2][1]*l.m[2][1]); + float sz = sqrtf(l.m[0][2]*l.m[0][2] + l.m[1][2]*l.m[1][2] + l.m[2][2]*l.m[2][2]); + mat_identity(&frames[i].abs); + frames[i].abs.m[0][0] = sx; + frames[i].abs.m[1][1] = sy; + frames[i].abs.m[2][2] = sz; + } + } + } else if (last_cont == 0x1A) { + mread(&n_geoms, 4, 1, &ms); + if (n_geoms) { + geoms = A_ALLOC(a, struct geom, n_geoms); + } + cur_g = -1; + } else if (last_cont == 0x0F && cur_g >= 0) { + struct geom *g = &geoms[cur_g]; + uint32_t fmt, morphs; + mread(&fmt, 4, 1, &ms); mread(&g->ni, 4, 1, &ms); + mread(&g->nv, 4, 1, &ms); mread(&morphs, 4, 1, &ms); + + if (g->nv > 0 && g->ni > 0) { + uint16_t flg = fmt & 0xFFFF, tex_c = (fmt & 0x00FF0000) >> 16; + if (tex_c == 0 && (flg & 0x0004)) { + tex_c = 1; + } + + g->c = NULL; + if (flg & 0x0008) { + g->c = A_ALLOC(a, uint8_t, g->nv * 4); + mread(g->c, 1, g->nv * 4, &ms); + } + + g->u = A_ALLOC(a, float, g->nv * 2); + if (tex_c > 0) { + mread(g->u, sizeof(float) * 2, g->nv, &ms); + if (tex_c > 1) { + ms.pos += (tex_c - 1) * sizeof(float) * 2 * g->nv; + } + } + + struct rw_triangle *tris = A_ALLOC(a, struct rw_triangle, g->ni); + mread(tris, sizeof(*tris), g->ni, &ms); + ms.pos += sizeof(float) * 4; + + int32_t has_v, has_n; + mread(&has_v, 4, 1, &ms); mread(&has_n, 4, 1, &ms); + if (has_v) { + g->v = A_ALLOC(a, float, g->nv * 3); + mread(g->v, sizeof(float) * 3, g->nv, &ms); + } + + g->i = A_ALLOC(a, uint16_t, g->ni * 3); + g->m = A_ALLOC(a, uint16_t, g->ni); + for (uint32_t j = 0; j < g->ni; j++) { + g->i[j*3+0] = tris[j].v1; + g->i[j*3+1] = tris[j].v2; + g->i[j*3+2] = tris[j].v3; + g->m[j] = tris[j].m; + } + } + } else if (last_cont == 0x08 && cur_g >= 0) { + mread(&geoms[cur_g].nm, 4, 1, &ms); + if (geoms[cur_g].nm > 0) { + geoms[cur_g].t = A_ALLOC(a, struct tex_name, geoms[cur_g].nm); + } + } else if (last_cont == 0x14) { + uint32_t f_idx, g_idx, flg, unused; + mread(&f_idx, 4, 1, &ms); mread(&g_idx, 4, 1, &ms); + mread(&flg, 4, 1, &ms); mread(&unused, 4, 1, &ms); + if (n_atomics < 256) { + atomics[n_atomics].f = f_idx; + atomics[n_atomics].g = g_idx; + n_atomics++; + } + } + last_cont = 0; + } else if (h.type == 0x02 && wait_tex && cur_g >= 0) { + struct geom *g = &geoms[cur_g]; + if (cur_m >= 0 && (uint32_t)cur_m < g->nm) { + size_t len = h.size < 31 ? h.size : 31; + mread(g->t[cur_m].name, 1, len, &ms); + for (size_t k = 0; k < len; k++) { + g->t[cur_m].name[k] = tolower((unsigned char)g->t[cur_m].name[k]); + } + } + wait_tex = 0; + } + + ms.pos = end; + } + + uint32_t t_nv = 0, t_ni = 0, t_nm = 0; + int has_any_colors = 0; + for (uint32_t i = 0; i < n_atomics; i++) { + if (atomics[i].g < n_geoms) { + t_nv += geoms[atomics[i].g].nv; + t_ni += geoms[atomics[i].g].ni; + t_nm += geoms[atomics[i].g].nm; + if (geoms[atomics[i].g].c) { + has_any_colors = 1; + } + } + } + + if (t_nv > 0 && t_ni > 0) { + float *f_v = A_ALLOC(a, float, t_nv * 3); + float *f_u = A_ALLOC(a, float, t_nv * 2); + uint8_t *f_c = A_ALLOC(a, uint8_t, t_nv * 4); + uint16_t *f_i = A_ALLOC(a, uint16_t, t_ni * 3); + uint16_t *f_m = A_ALLOC(a, uint16_t, t_ni); + struct tex_name *f_t = t_nm > 0 ? A_ALLOC(a, struct tex_name, t_nm) : NULL; + + uint32_t vo = 0, to = 0, mo = 0; + for (uint32_t i = 0; i < n_atomics; i++) { + if (atomics[i].g >= n_geoms || atomics[i].f >= n_frames) { + continue; + } + struct geom *g = &geoms[atomics[i].g]; + struct frame *fr = &frames[atomics[i].f]; + + for (uint32_t j = 0; j < g->nv; j++) { + float x = g->v[j*3+0], y = g->v[j*3+1], z = g->v[j*3+2]; + f_v[(vo+j)*3+0] = fr->abs.m[0][0]*x + fr->abs.m[0][1]*y + fr->abs.m[0][2]*z + fr->abs.m[0][3]; + f_v[(vo+j)*3+1] = fr->abs.m[1][0]*x + fr->abs.m[1][1]*y + fr->abs.m[1][2]*z + fr->abs.m[1][3]; + f_v[(vo+j)*3+2] = fr->abs.m[2][0]*x + fr->abs.m[2][1]*y + fr->abs.m[2][2]*z + fr->abs.m[2][3]; + f_u[(vo+j)*2+0] = g->u[j*2+0]; + f_u[(vo+j)*2+1] = g->u[j*2+1]; + if (g->c) { + f_c[(vo+j)*4+0] = g->c[j*4+0]; + f_c[(vo+j)*4+1] = g->c[j*4+1]; + f_c[(vo+j)*4+2] = g->c[j*4+2]; + f_c[(vo+j)*4+3] = g->c[j*4+3]; + } else { + f_c[(vo+j)*4+0] = 255; + f_c[(vo+j)*4+1] = 255; + f_c[(vo+j)*4+2] = 255; + f_c[(vo+j)*4+3] = 255; + } + } + for (uint32_t j = 0; j < g->ni; j++) { + f_i[(to+j)*3+0] = g->i[j*3+0] + vo; + f_i[(to+j)*3+1] = g->i[j*3+1] + vo; + f_i[(to+j)*3+2] = g->i[j*3+2] + vo; + f_m[to+j] = g->m[j] + mo; + } + for (uint32_t j = 0; j < g->nm; j++) { + memcpy(f_t[mo+j].name, g->t[j].name, 32); + } + vo += g->nv; + to += g->ni; + mo += g->nm; + } + + float b_cx = 0, b_cy = 0, b_cz = 0, b_r = 0; + float minx = f_v[0], maxx = f_v[0], miny = f_v[1], maxy = f_v[1], minz = f_v[2], maxz = f_v[2]; + for (uint32_t i = 1; i < t_nv; i++) { + if (f_v[i*3+0] < minx) minx = f_v[i*3+0]; + if (f_v[i*3+0] > maxx) maxx = f_v[i*3+0]; + if (f_v[i*3+1] < miny) miny = f_v[i*3+1]; + if (f_v[i*3+1] > maxy) maxy = f_v[i*3+1]; + if (f_v[i*3+2] < minz) minz = f_v[i*3+2]; + if (f_v[i*3+2] > maxz) maxz = f_v[i*3+2]; + } + b_cx = (minx+maxx)*0.5f; + b_cy = (miny+maxy)*0.5f; + b_cz = (minz+maxz)*0.5f; + for (uint32_t i = 0; i < t_nv; i++) { + float dx = f_v[i*3+0] - b_cx, dy = f_v[i*3+1] - b_cy, dz = f_v[i*3+2] - b_cz; + float d2 = dx*dx + dy*dy + dz*dz; + if (d2 > b_r) { + b_r = d2; + } + } + b_r = sqrtf(b_r); + + char out_name[256]; + snprintf(out_name, sizeof(out_name), "assets/models/%s.ply", name); + FILE *out = fopen(out_name, "wb"); + if (out) { + fprintf(out, "ply\n"); + fprintf(out, "format binary_little_endian 1.0\n"); + fprintf(out, "comment spr_cx %f\n", b_cx); + fprintf(out, "comment spr_cy %f\n", b_cy); + fprintf(out, "comment spr_cz %f\n", b_cz); + fprintf(out, "comment spr_r %f\n", b_r); + fprintf(out, "comment flags %u\n", has_any_colors ? 1 : 0); + for (uint32_t i = 0; i < t_nm; i++) { + fprintf(out, "comment texture %s\n", f_t[i].name); + } + fprintf(out, "element vertex %u\n", t_nv); + fprintf(out, "property float x\n"); + fprintf(out, "property float y\n"); + fprintf(out, "property float z\n"); + fprintf(out, "property float s\n"); + fprintf(out, "property float t\n"); + fprintf(out, "property uchar red\n"); + fprintf(out, "property uchar green\n"); + fprintf(out, "property uchar blue\n"); + fprintf(out, "property uchar alpha\n"); + fprintf(out, "element face %u\n", t_ni); + fprintf(out, "property list uchar int vertex_indices\n"); + fprintf(out, "property ushort material_index\n"); + fprintf(out, "end_header\n"); + + for (uint32_t i = 0; i < t_nv; i++) { + float vx = f_v[i*3+0], vy = f_v[i*3+1], vz = f_v[i*3+2]; + float tu = f_u[i*2+0], tv = f_u[i*2+1]; + uint8_t cr = f_c[i*4+0], cg = f_c[i*4+1], cb = f_c[i*4+2], ca = f_c[i*4+3]; + fwrite(&vx, 4, 1, out); + fwrite(&vy, 4, 1, out); + fwrite(&vz, 4, 1, out); + fwrite(&tu, 4, 1, out); + fwrite(&tv, 4, 1, out); + fwrite(&cr, 1, 1, out); + fwrite(&cg, 1, 1, out); + fwrite(&cb, 1, 1, out); + fwrite(&ca, 1, 1, out); + } + + for (uint32_t i = 0; i < t_ni; i++) { + uint8_t count = 3; + int32_t idx[3] = { f_i[i*3+0], f_i[i*3+1], f_i[i*3+2] }; + uint16_t mat = f_m[i]; + fwrite(&count, 1, 1, out); + fwrite(idx, 4, 3, out); + fwrite(&mat, 2, 1, out); + } + fclose(out); + } + } +} + +static void +process_local_ipl(const char *root, const char *rel, FILE *out, struct file_list *fl) +{ + FILE *f; + size_t sz; + char *buf; + + f = fopen_ci(root, rel); + if (!f) { + return; + } + fseek(f, 0, SEEK_END); + sz = ftell(f); + fseek(f, 0, SEEK_SET); + buf = malloc(sz); + if (fread(buf, 1, sz, f) == sz) { + parse_text_ipl(buf, sz, out, fl); + } + free(buf); + fclose(f); +} + +static void +process_gta_dat_ide(const char *root, const char *dat_path) +{ + FILE *f; + char line[512]; + char *p; + + f = fopen_ci(root, dat_path); + if (!f) { + return; + } + + while (fgets(line, sizeof(line), f)) { + p = line; + line[strcspn(line, "\r\n")] = '\0'; + while (*p == ' ' || *p == '\t') { + p++; + } + if (*p == '\0' || *p == '#') { + continue; + } + + if (strncasecmp(p, "IDE ", 4) == 0) { + char rel[512]; + char *src = p + 4; + while (*src == ' ' || *src == '\t') { + src++; + } + for (size_t 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); + preparse_ide(root, rel); + } + } + fclose(f); +} + +static void +process_gta_dat_ipl(const char *root, const char *dat_path, FILE *map_out, struct file_list *fl) +{ + FILE *f; + char line[512]; + char *p; + + f = fopen_ci(root, dat_path); + if (!f) { + return; + } + + while (fgets(line, sizeof(line), f)) { + p = line; + line[strcspn(line, "\r\n")] = '\0'; + while (*p == ' ' || *p == '\t') { + p++; + } + if (*p == '\0' || *p == '#') { + continue; + } + + if (strncasecmp(p, "IPL ", 4) == 0) { + char rel[512]; + char *src = p + 4; + while (*src == ' ' || *src == '\t') { + src++; + } + for (size_t 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); + process_local_ipl(root, rel, map_out, fl); + } + } + fclose(f); +} + +static void +process_water(const char *root) +{ + FILE *f, *out; + char line[512], *p, *tok, *save; + char *tokens[32]; + int t_count, i, j, k, collapsed; + uint32_t count; + float limit = 2980.0f; + float ax_min, ax_max, ay_min, ay_max, az; + float bx_min, bx_max, by_min, by_max; + struct water_face *a, *b, *face; + + f = fopen_ci(root, "data/water.dat"); + if (f == NULL) { + return; + } + + out = fopen("assets/water.bin", "wb"); + if (out == NULL) { + fclose(f); + return; + } + + 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') { + p++; + } + if (*p == '\0' || *p == '#' || *p == '*' || *p == '\r' || + *p == '\n' || strncasecmp(p, "processed", 9) == 0) { + continue; + } + + t_count = 0; + tok = strtok_r(p, " \t\r\n", &save); + while (tok && t_count < 32) { + tokens[t_count++] = tok; + tok = strtok_r(NULL, " \t\r\n", &save); + } + + if ((t_count == 29 || t_count == 22) && water_face_count < 8192) { + face = &water_db[water_face_count]; + k = atoi(tokens[t_count - 1]); + if (k == 1 || k == 3) { + face->num = t_count == 29 ? 4 : 3; + for (j = 0; j < face->num; j++) { + 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; + + face->u[j] = strtof(tokens[j * 7 + 3], NULL); + 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; + } + if (face->x[j] < -limit) { + face->x[j] = -limit; + } + if (face->y[j] > limit) { + face->y[j] = limit; + } + if (face->y[j] < -limit) { + face->y[j] = -limit; + } + } + water_face_count++; + } + } + } + + /* phase 2: smart clipping of overlapping internal tiles */ + for (i = 0; i < water_face_count; i++) { + a = &water_db[i]; + if (a->num == 0) { + continue; + } + + ax_min = a->x[0]; + ax_max = a->x[0]; + ay_min = a->y[0]; + ay_max = a->y[0]; + az = a->z[0]; + for (k = 1; k < a->num; k++) { + if (a->x[k] < ax_min) { + ax_min = a->x[k]; + } + if (a->x[k] > ax_max) { + ax_max = a->x[k]; + } + if (a->y[k] < ay_min) { + ay_min = a->y[k]; + } + if (a->y[k] > ay_max) { + ay_max = a->y[k]; + } + } + + for (j = 0; j < water_face_count; j++) { + if (i == j) { + continue; + } + b = &water_db[j]; + if (b->num == 0) { + continue; + } + + if (fabsf(b->z[0] - az) > 0.01f) { + continue; + } + + bx_min = b->x[0]; + bx_max = b->x[0]; + by_min = b->y[0]; + by_max = b->y[0]; + for (k = 1; k < b->num; k++) { + if (b->x[k] < bx_min) { + bx_min = b->x[k]; + } + if (b->x[k] > bx_max) { + bx_max = b->x[k]; + } + if (b->y[k] < by_min) { + by_min = b->y[k]; + } + if (b->y[k] > by_max) { + by_max = b->y[k]; + } + } + + /* 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++) { + if (fabsf(b->x[k] - bx_min) < 0.1f) { + b->x[k] = ax_max; + } + } + } else if (bx_max > ax_min && bx_min < ax_min && bx_max < ax_max) { + for (k = 0; k < b->num; k++) { + if (fabsf(b->x[k] - bx_max) < 0.1f) { + b->x[k] = ax_min; + } + } + } else if (by_min < ay_max && by_max > ay_max && by_min > ay_min) { + for (k = 0; k < b->num; k++) { + if (fabsf(b->y[k] - by_min) < 0.1f) { + b->y[k] = ay_max; + } + } + } else if (by_max > ay_min && by_min < ay_min && by_max < ay_max) { + for (k = 0; k < b->num; k++) { + if (fabsf(b->y[k] - by_max) < 0.1f) { + b->y[k] = ay_min; + } + } + } + } + } + } + + /* 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]; + collapsed = 0; + + if (face->num == 4) { + if (fabsf(face->x[0] - face->x[2]) < 0.1f && fabsf(face->y[0] - face->y[2]) < 0.1f) { + collapsed = 1; + } + } else if (face->num == 3) { + if (fabsf(face->x[0] - face->x[1]) < 0.1f && fabsf(face->y[0] - face->y[1]) < 0.1f) { + collapsed = 1; + } + } + + if (collapsed == 0) { + fwrite(face, sizeof(struct water_face), 1, out); + count++; + } + } + + fseek(out, 0, SEEK_SET); + fwrite(&count, sizeof(count), 1, out); + fclose(out); + fclose(f); +} + +static void +process_timecyc(const char *root) +{ + FILE *f; + FILE *out; + char line[512]; + struct timecyc_entry snaps[32 * 8]; + int w_idx, s_idx, total_weathers; + char *p; + + f = fopen_ci(root, "data/timecycp.dat"); + if (!f) { + f = fopen_ci(root, "data/timecyc.dat"); + } + if (!f) { + return; + } + + out = fopen("assets/timecyc.bin", "wb"); + if (!out) { + fclose(f); + return; + } + + memset(snaps, 0, sizeof(snaps)); + w_idx = 0; + s_idx = 0; + total_weathers = 0; + + while (fgets(line, sizeof(line), f)) { + p = line; + while (*p == ' ' || *p == '\t') { + p++; + } + if (*p == '\0' || *p == '\n' || *p == '\r' || + strncmp(p, "//", 2) == 0) { + continue; + } + + char *tok, *save; + char *tokens[64]; + int t = 0; + + tok = strtok_r(p, " \t\r\n", &save); + while (tok && t < 64) { + tokens[t++] = tok; + tok = strtok_r(NULL, " \t\r\n", &save); + } + + if (t >= 40) { + struct timecyc_entry *e = &snaps[w_idx * 8 + s_idx]; + e->amb[0] = atoi(tokens[3]); + e->amb[1] = atoi(tokens[4]); + e->amb[2] = atoi(tokens[5]); + e->dir[0] = atoi(tokens[6]); + e->dir[1] = atoi(tokens[7]); + e->dir[2] = atoi(tokens[8]); + e->sky_top[0] = atoi(tokens[9]); + e->sky_top[1] = atoi(tokens[10]); + e->sky_top[2] = atoi(tokens[11]); + e->sky_bot[0] = atoi(tokens[12]); + e->sky_bot[1] = atoi(tokens[13]); + e->sky_bot[2] = atoi(tokens[14]); + e->far_clp = strtof(tokens[27], NULL); + e->fog_st = strtof(tokens[28], NULL); + e->water[0] = atoi(tokens[36]); + e->water[1] = atoi(tokens[37]); + e->water[2] = atoi(tokens[38]); + e->water[3] = (t >= 40) ? atoi(tokens[39]) : 255; + + s_idx++; + if (s_idx == 8) { + s_idx = 0; + w_idx++; + if (w_idx > total_weathers) { + total_weathers = w_idx; + } + if (w_idx >= 32) { + break; + } + } + } + } + fwrite(&total_weathers, sizeof(int), 1, out); + fwrite(snaps, sizeof(struct timecyc_entry), total_weathers * 8, out); + fclose(out); + fclose(f); +} + +static void * +build_worker(void *arg) +{ + struct thread_arg *ta; + uint32_t i; + + ta = arg; + ta->arena.size = 128 * 1024 * 1024; + ta->arena.mem = malloc(ta->arena.size); + + for (i = ta->start; i < ta->end; i++) { + char n[NAME_SZ + 1]; + size_t j; + for (j = 0; j < sizeof(ta->entries[i].name) && ta->entries[i].name[j]; j++) { + n[j] = tolower((unsigned char)ta->entries[i].name[j]); + } + n[j] = '\0'; + + if (list_has(ta->fl, n)) { + uint32_t size = ta->entries[i].size * 2048; + const uint8_t *buf = ta->img_data + ta->entries[i].offset * 2048; + char *ext = strrchr(n, '.'); + if (ext) { + *ext = '\0'; + } + + ta->arena.pos = 0; + + if (strstr(ta->entries[i].name, ".dff") || strstr(ta->entries[i].name, ".DFF")) { + convert_dff(buf, size, n, &ta->arena); + } else if (strstr(ta->entries[i].name, ".txd") || strstr(ta->entries[i].name, ".TXD")) { + convert_txd(buf, size, n, &ta->arena); + } + } + } + free(ta->arena.mem); + return (NULL); +} + +static void +tar_add_file(FILE *tar, const char *path, const char *tar_name) +{ + FILE *src; + size_t sz, n, padding; + struct tar_header th; + unsigned int sum; + uint8_t *p; + char buf[4096]; + + src = fopen(path, "rb"); + if (!src) { + return; + } + + fseek(src, 0, SEEK_END); + sz = ftell(src); + fseek(src, 0, SEEK_SET); + + memset(&th, 0, sizeof(th)); + strncpy(th.name, tar_name, sizeof(th.name) - 1); + snprintf(th.mode, sizeof(th.mode), "%07o", 0644); + snprintf(th.size, sizeof(th.size), "%011lo", (unsigned long)sz); + snprintf(th.magic, sizeof(th.magic), "ustar"); + + /* Simple checksum calculation. */ + memset(th.chksum, ' ', 8); + sum = 0; + p = (uint8_t *)&th; + for (size_t i = 0; i < sizeof(th); i++) { + sum += p[i]; + } + snprintf(th.chksum, sizeof(th.chksum), "%06o", sum); + + fwrite(&th, sizeof(th), 1, tar); + + while ((n = fread(buf, 1, sizeof(buf), src)) > 0) { + fwrite(buf, 1, n, tar); + } + fclose(src); + + padding = (512 - (sz % 512)) % 512; + if (padding > 0) { + char pad[512] = {0}; + fwrite(pad, 1, padding, tar); + } +} + +static void +pack_directory(FILE *tar, const char *dir_path, const char *prefix) +{ + DIR *dir; + struct dirent *de; + char path[1024]; + char tar_name[1024]; + struct stat st; + + dir = opendir(dir_path); + if (!dir) { + return; + } + while ((de = readdir(dir))) { + if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) { + continue; + } + snprintf(path, sizeof(path), "%s/%s", dir_path, de->d_name); + snprintf(tar_name, sizeof(tar_name), "%s%s", prefix, de->d_name); + + if (stat(path, &st) == 0) { + if (S_ISDIR(st.st_mode)) { + char new_prefix[1024]; + snprintf(new_prefix, sizeof(new_prefix), "%s/", tar_name); + pack_directory(tar, path, new_prefix); + } else { + tar_add_file(tar, path, tar_name); + } + } + } + closedir(dir); +} + +static void +process_img_file(const char *root, const char *rel_path, struct file_list *fl, int num_threads, pthread_t *threads, struct thread_arg *args) +{ + char img_path[1024]; + int img_fd; + struct stat st; + uint8_t *img_data; + struct img_header *hdr; + struct img_entry *entries; + uint32_t chunk; + int i; + + if (!resolve_ci(root, rel_path, img_path, sizeof(img_path))) { + warnx("failed to resolve %s", rel_path); + return; + } + + img_fd = open(img_path, O_RDONLY); + if (img_fd < 0) { + warn("failed to open %s", rel_path); + return; + } + + if (fstat(img_fd, &st) < 0) { + close(img_fd); + return; + } + + img_data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, img_fd, 0); + if (img_data == MAP_FAILED) { + close(img_fd); + return; + } + + hdr = (struct img_header *)img_data; + if (memcmp(hdr->magic, "VER2", 4) != 0) { + munmap(img_data, st.st_size); + close(img_fd); + return; + } + entries = (struct img_entry *)(img_data + sizeof(struct img_header)); + + printf("build: processing %s (%u entries)...\n", rel_path, hdr->entries); + + chunk = hdr->entries / num_threads; + for (i = 0; i < num_threads; i++) { + args[i].img_data = img_data; + args[i].entries = entries; + args[i].start = i * chunk; + args[i].end = (i == num_threads - 1) ? hdr->entries : (i + 1) * chunk; + args[i].fl = fl; + pthread_create(&threads[i], NULL, build_worker, &args[i]); + } + + for (i = 0; i < num_threads; i++) { + pthread_join(threads[i], NULL); + } + + munmap(img_data, st.st_size); + close(img_fd); +} + +int +main(int argc, char *argv[]) +{ + struct file_list fl; + struct arena shared_arena; + FILE *map_out; + char img_path[1024]; + int img_fd; + struct stat st; + uint8_t *img_data; + struct img_header *hdr; + struct img_entry *entries; + struct file_list missing_fl; + int num_threads; + pthread_t *threads; + struct thread_arg *args; + FILE *tar; + + if (argc < 2) { + return (1); + } + + index_directory_tree(argv[1]); + + mkdir("assets", 0777); + mkdir("assets/models", 0777); + mkdir("assets/textures", 0777); + + scan_existing_assets(); + memset(&fl, 0, sizeof(fl)); + + map_out = fopen("assets/map.ipl", "w"); + if (!map_out) { + err(1, "failed to create map.ipl"); + } + + process_gta_dat_ide(argv[1], "data/default.dat"); + process_gta_dat_ide(argv[1], "data/gta.dat"); + + process_gta_dat_ipl(argv[1], "data/default.dat", map_out, &fl); + process_gta_dat_ipl(argv[1], "data/gta.dat", map_out, &fl); + + if (!resolve_ci(argv[1], "MODELS/GTA3.IMG", img_path, sizeof(img_path))) { + errx(1, "failed to resolve GTA3.IMG"); + } + + img_fd = open(img_path, O_RDONLY); + if (img_fd < 0) { + err(1, "failed to open GTA3.IMG"); + } + + fstat(img_fd, &st); + img_data = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, img_fd, 0); + if (img_data == MAP_FAILED) { + err(1, "mmap failed"); + } + + hdr = (struct img_header *)img_data; + if (memcmp(hdr->magic, "VER2", 4) != 0) { + errx(1, "invalid IMG"); + } + entries = (struct img_entry *)(img_data + sizeof(struct img_header)); + + for (uint32_t i = 0; i < hdr->entries; i++) { + char name_lower[NAME_SZ + 1]; + size_t j; + for (j = 0; j < sizeof(entries[i].name) && entries[i].name[j]; j++) { + name_lower[j] = tolower((unsigned char)entries[i].name[j]); + } + name_lower[j] = '\0'; + + if (strstr(name_lower, ".ipl") != NULL) { + uint32_t size = entries[i].size * 2048; + const uint8_t *buf = img_data + entries[i].offset * 2048; + if (size >= 4 && memcmp(buf, "bnry", 4) == 0) { + parse_binary_ipl(buf, size, map_out, &fl); + } else { + parse_text_ipl((char *)buf, size, map_out, &fl); + } + } + } + fclose(map_out); + + memset(&missing_fl, 0, sizeof(missing_fl)); + for (int i = 0; i < fl.count; i++) { + if (!asset_exists(fl.names[i])) { + for (uint32_t k = 0; k < hdr->entries; k++) { + char n[NAME_SZ + 1]; + size_t x; + for (x = 0; x < 24 && entries[k].name[x]; x++) { + n[x] = tolower((unsigned char)entries[k].name[x]); + } + n[x] = '\0'; + if (strcmp(n, fl.names[i]) == 0) { + list_add(&missing_fl, fl.names[i]); + break; + } + } + } + } + printf("Total map files: %d, missing/rebuilding: %d\n", fl.count, missing_fl.count); + fl = missing_fl; + + shared_arena.size = 128 * 1024 * 1024; + shared_arena.mem = malloc(shared_arena.size); + process_particle_txd(argv[1], &shared_arena); + free(shared_arena.mem); + + process_water(argv[1]); + process_timecyc(argv[1]); + + num_threads = sysconf(_SC_NPROCESSORS_ONLN); + if (num_threads < 1) { + num_threads = 4; + } + + threads = malloc(num_threads * sizeof(pthread_t)); + args = malloc(num_threads * sizeof(struct thread_arg)); + + process_img_file(argv[1], "models/gta3.img", &fl, num_threads, threads, args); + process_img_file(argv[1], "models/gta_int.img", &fl, num_threads, threads, args); + + free(threads); + free(args); + + printf("Packing assets.tar...\n"); + tar = fopen("assets.tar", "wb"); + if (tar) { + pack_directory(tar, "assets", "assets/"); + char empty[1024] = {0}; + fwrite(empty, 1, sizeof(empty), tar); + fclose(tar); + system("rm -rf assets"); + } + + return (0); +} diff --git a/src/render.c b/src/render.c @@ -0,0 +1,2271 @@ +#include <sys/ipc.h> +#include <sys/mman.h> +#include <sys/shm.h> +#include <sys/stat.h> + +#include <X11/Xlib.h> +#include <X11/Xutil.h> +#include <X11/keysym.h> +#include <X11/extensions/XShm.h> + +#include <ctype.h> +#include <err.h> +#include <fcntl.h> +#include <locale.h> +#include <math.h> +#include <pthread.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <time.h> +#include <unistd.h> + +#define WIDTH 640 +#define HEIGHT 480 +#define MAX_CACHE 8192 +#define NAME_SZ 24 +#define MAX_DIST 250.0f +#define GRID_SZ 64 +#define MAP_MIN -3000.0f +#define MAP_MAX 3000.0f +#define MAX_RENDER_TRIS 262144 +#define TAR_INDEX_SIZE 32768 + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +struct vec3 { float x, y, z; }; +struct mat4 { float m[4][4]; }; + +struct timecyc_entry { + uint8_t amb[3], dir[3], sky_top[3], sky_bot[3], water[4]; + float far_clp, fog_st; +}; + +struct sys_gfx { + Display *dpy; + Window win; + GC gc; + XImage *img; + XShmSegmentInfo shm; + uint32_t *pixels; + float *zbuffer; + Atom wm_delete; + struct mat4 proj; + char keys[512]; + float game_time_sec, game_time_hours; + struct timecyc_entry weathers[32][8], cur_weather; + int num_weathers, game_weather; + float far_clip_override; + int in_chat, chat_len; + char chat_buf[128]; + KeyCode key_w, key_a, key_s, key_d, key_t, key_esc; + KeyCode key_up, key_down, key_left, key_right; + XIC ic; + XFontSet font_set; +}; + +struct vertex { float x, y, z, w, u, v, r, g, b, a, f; }; +struct clip_vertex { float x, y, z, w, u, v, r, g, b, a, f; }; + +struct texture { + uint32_t *pixels; + int w, h, has_alpha; + uint32_t *mips[4]; + int mip_w[4]; + int mip_h[4]; +}; + +struct render_triangle { + struct vertex v0, v1, v2; + const struct texture *tex; + int is_water, mip; +}; + +struct ply_vertex { + float x, y, z; + float s, t; + uint8_t r, g, b, a; +}; + +struct mesh { + struct ply_vertex *ply_verts; + uint16_t *indices, *mat_ids; + uint32_t num_verts, num_tris, num_materials; + char (*raw_tex_names)[32]; + struct texture **textures; + float spr_x, spr_y, spr_z, spr_r, total_area; +}; + +struct transform_array { + float *pos_x, *pos_y, *pos_z; + float *rot_qx, *rot_qy, *rot_qz, *rot_qw; + struct mat4 *world_matrices; + float *sphere_cx, *sphere_cy, *sphere_cz, *sphere_r; + int16_t *time_on, *time_off; + int count; +}; + +struct grid_cell { int *ids, count, cap; }; + +struct asset_cache { + struct mesh meshes[MAX_CACHE]; + struct texture textures[MAX_CACHE]; + char mesh_names[MAX_CACHE][NAME_SZ], tex_names[MAX_CACHE][32]; + int mesh_count, tex_count, mesh_hash[MAX_CACHE * 2], tex_hash[MAX_CACHE * 2]; +}; + +struct draw_item { int id; float dist_sq; }; +struct water_face { + float x[4], y[4], z[4]; + float u[4], v[4], h[4]; + int num; +}; + +struct spin_barrier { + int count; + int threshold; + int cycle; +}; + +struct tar_header { + char name[100]; + char mode[8]; + char uid[8]; + char gid[8]; + char size[12]; + char mtime[12]; + char chksum[8]; + char typeflag; + char linkname[100]; + char magic[6]; + char version[2]; + char uname[32]; + char gname[32]; + char devmajor[8]; + char devminor[8]; + char prefix[155]; + char pad[12]; +} __attribute__((packed)); + +struct tar_index_entry { + char name[128]; + const uint8_t *data; + size_t size; +}; + +struct tga_header { + uint8_t id_length; + uint8_t color_map_type; + uint8_t image_type; + uint16_t color_map_first; + uint16_t color_map_length; + uint8_t color_map_entry_size; + uint16_t x_origin; + uint16_t y_origin; + uint16_t width; + uint16_t height; + uint8_t pixel_depth; + uint8_t image_descriptor; +} __attribute__((packed)); + +static struct sys_gfx *g_sys; +static struct transform_array g_transforms; +static struct mesh **scene_meshes = NULL; +static int scene_inst_count = 0, scene_inst_capacity = 0; +static struct grid_cell scene_grid[GRID_SZ][GRID_SZ]; +static struct draw_item *draw_list = NULL; +static int draw_cap = 0, draw_count = 0; +static struct render_triangle render_list[MAX_RENDER_TRIS]; +static int render_count = 0; +static struct water_face *water_faces = NULL; +static uint32_t water_count = 0; +static struct tar_index_entry tar_idx[TAR_INDEX_SIZE]; +static struct texture *water_tex = NULL, *sand_tex = NULL; +static pthread_t *workers = NULL; +static struct spin_barrier barrier_start, barrier_end; +static int num_threads = 1, threads_running = 1; +static struct clip_vertex *v_scratch = NULL; +static size_t v_scratch_cap = 0; + +static uint8_t *g_tar_mapped = NULL; +static size_t g_tar_size = 0; + +static inline void +spin_barrier_init(struct spin_barrier *b, int count) +{ + b->count = 0; + b->threshold = count; + b->cycle = 0; +} + +static inline void +spin_barrier_wait(struct spin_barrier *b) +{ + int cycle; + + cycle = __atomic_load_n(&b->cycle, __ATOMIC_ACQUIRE); + if (__atomic_add_fetch(&b->count, 1, __ATOMIC_ACQ_REL) == b->threshold) { + __atomic_store_n(&b->count, 0, __ATOMIC_RELEASE); + __atomic_store_n(&b->cycle, cycle + 1, __ATOMIC_RELEASE); + } else { + while (__atomic_load_n(&b->cycle, __ATOMIC_ACQUIRE) == cycle) { +#if defined(__i386__) || defined(__x86_64__) + __builtin_ia32_pause(); +#else + __asm__ volatile("":::"memory"); +#endif + } + } +} + +static size_t +get_tar_size(const char *octal) +{ + size_t size = 0; + int i; + + for (i = 0; i < 11; i++) { + if (octal[i] < '0' || octal[i] > '7') { + break; + } + size = size * 8 + (octal[i] - '0'); + } + return (size); +} + +static inline float +edge_func(float ax, float ay, float bx, float by, float cx, float cy) +{ + return ((cx - ax) * (by - ay) - (cy - ay) * (bx - ax)); +} + +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; + const struct texture *tex; + uint32_t *pixels; + float area, inv_area, dy12, dy20, dx12, dx20, dy01; + float p_x_start, p_y_start, w0_start, w1_start; + float dw0_dx, dw0_dy, dw1_dx, dw1_dy; + float u0_w, u1_w, u2_w, v0_w, v1_w, v2_w, f0_w, f1_w, f2_w; + float d_inv_w_dx, d_u_w_dx, d_v_w_dx, d_f_w_dx, d_r_dx, d_g_dx, d_b_dx; + float d_r_dx_255, d_g_dx_255, d_b_dx_255; + float inv_slope_long, inv_slope_short1, inv_slope_short2; + float row_y, x_long, x_short; + float dx, dy, row_w0, row_w1, row_w2; + float inv_w, u_w, v_w, f_w, r, g_val, b; + float wf, f, inv_f; + uint32_t sky_r, sky_g, sky_b, color, wa, fr, fg, fb, bg, tex_r, tex_g, tex_b; + uint8_t wa_tc, wr, wg, wb, tex_a; + int min_x, max_x, min_y, max_y, row_min_x, row_max_x; + int tw, th, tw_mask, th_mask, py, px, idx, ir, ig, ib, tx, ty; + int m; + float min_inv_w; + + v0 = tri->v0; + v1 = tri->v1; + v2 = tri->v2; + + /* prevent near-plane depth extrapolation artifacts */ + min_inv_w = MIN(v0.w, MIN(v1.w, v2.w)); + if (min_inv_w < 0.0001f) { + min_inv_w = 0.0001f; + } + + area = edge_func(v0.x, v0.y, v1.x, v1.y, v2.x, v2.y); + if (fabsf(area) < 1e-5f) { + return; + } + inv_area = 1.0f / area; + + dy12 = v2.y - v1.y; + dy20 = v0.y - v2.y; + dx12 = v2.x - v1.x; + dx20 = v0.x - v2.x; + dy01 = v1.y - v0.y; + + min_x = (int)MIN(v0.x, MIN(v1.x, v2.x)); + max_x = (int)MAX(v0.x, MAX(v1.x, v2.x)); + min_y = (int)MIN(v0.y, MIN(v1.y, v2.y)); + max_y = (int)MAX(v0.y, MAX(v1.y, v2.y)); + + if (min_x < 0) min_x = 0; + if (max_x >= WIDTH) max_x = WIDTH - 1; + if (min_y < min_y_band) min_y = min_y_band; + if (max_y > max_y_band) max_y = max_y_band; + if (min_y > max_y) { + return; + } + + p_x_start = min_x + 0.5f; + p_y_start = min_y + 0.5f; + w0_start = ((p_x_start - v1.x) * dy12 - (p_y_start - v1.y) * dx12) * inv_area; + w1_start = ((p_x_start - v2.x) * dy20 - (p_y_start - v2.y) * dx20) * inv_area; + + dw0_dx = dy12 * inv_area; + dw0_dy = -dx12 * inv_area; + dw1_dx = dy20 * inv_area; + dw1_dy = -dx20 * inv_area; + + u0_w = v0.u * v0.w; u1_w = v1.u * v1.w; u2_w = v2.u * v2.w; + v0_w = v0.v * v0.w; v1_w = v1.v * v1.w; v2_w = v2.v * v2.w; + f0_w = v0.f * v0.w; f1_w = v1.f * v1.w; f2_w = v2.f * v2.w; + + d_inv_w_dx = (dy12 * v0.w + dy20 * v1.w + dy01 * v2.w) * inv_area; + d_u_w_dx = (dy12 * u0_w + dy20 * u1_w + dy01 * u2_w) * inv_area; + d_v_w_dx = (dy12 * v0_w + dy20 * v1_w + dy01 * v2_w) * inv_area; + d_f_w_dx = (dy12 * f0_w + dy20 * f1_w + dy01 * f2_w) * inv_area; + d_r_dx = (dy12 * v0.r + dy20 * v1.r + dy01 * v2.r) * inv_area; + d_g_dx = (dy12 * v0.g + dy20 * v1.g + dy01 * v2.g) * inv_area; + d_b_dx = (dy12 * v0.b + dy20 * v1.b + dy01 * v2.b) * inv_area; + + d_r_dx_255 = d_r_dx * 255.0f; + d_g_dx_255 = d_g_dx * 255.0f; + d_b_dx_255 = d_b_dx * 255.0f; + + sky_r = g_sys->cur_weather.sky_bot[0]; + sky_g = g_sys->cur_weather.sky_bot[1]; + sky_b = g_sys->cur_weather.sky_bot[2]; + + sv0 = v0; + sv1 = v1; + sv2 = v2; + if (sv0.y > sv1.y) { tmp = sv0; sv0 = sv1; sv1 = tmp; } + if (sv0.y > sv2.y) { tmp = sv0; sv0 = sv2; sv2 = tmp; } + if (sv1.y > sv2.y) { tmp = sv1; sv1 = sv2; sv2 = tmp; } + + inv_slope_long = 0.0f; + inv_slope_short1 = 0.0f; + inv_slope_short2 = 0.0f; + + if (sv2.y != sv0.y) { + inv_slope_long = (sv2.x - sv0.x) / (sv2.y - sv0.y); + } + if (sv1.y != sv0.y) { + inv_slope_short1 = (sv1.x - sv0.x) / (sv1.y - sv0.y); + } + if (sv2.y != sv1.y) { + inv_slope_short2 = (sv2.x - sv1.x) / (sv2.y - sv1.y); + } + + if (tri->is_water && tri->tex && (uintptr_t)tri->tex != (uintptr_t)-1) { + tex = tri->tex; + pixels = tex->pixels; + tw = tex->w; + th = tex->h; + tw_mask = tw - 1; + th_mask = th - 1; + wa_tc = g_sys->cur_weather.water[3]; + wr = g_sys->cur_weather.water[0]; + wg = g_sys->cur_weather.water[1]; + wb = g_sys->cur_weather.water[2]; + + for (py = min_y; py <= max_y; py++) { + row_y = (float)py + 0.5f; + x_long = sv0.x; + if (sv2.y != sv0.y) { + x_long += (row_y - sv0.y) * inv_slope_long; + } + + if (row_y < sv1.y) { + x_short = sv0.x; + if (sv1.y != sv0.y) { + x_short += (row_y - sv0.y) * inv_slope_short1; + } + } else { + x_short = sv1.x; + if (sv2.y != sv1.y) { + x_short += (row_y - sv1.y) * inv_slope_short2; + } + } + + float x_min = (x_long < x_short) ? x_long : x_short; + float x_max = (x_long < x_short) ? x_short : x_long; + + row_min_x = (int)ceilf(x_min - 0.5f); + row_max_x = (int)floorf(x_max - 0.5f); + + if (row_min_x < min_x) row_min_x = min_x; + if (row_max_x > max_x) row_max_x = max_x; + if (row_min_x > row_max_x) { + continue; + } + + dx = (row_min_x + 0.5f) - p_x_start; + dy = row_y - p_y_start; + row_w0 = w0_start + dw0_dx * dx + dw0_dy * dy; + row_w1 = w1_start + dw1_dx * dx + dw1_dy * dy; + row_w2 = 1.0f - row_w0 - row_w1; + inv_w = row_w0 * v0.w + row_w1 * v1.w + row_w2 * v2.w; + u_w = row_w0 * u0_w + row_w1 * u1_w + row_w2 * u2_w; + v_w = row_w0 * v0_w + row_w1 * v1_w + row_w2 * v2_w; + f_w = row_w0 * f0_w + row_w1 * f1_w + row_w2 * f2_w; + r = (row_w0 * v0.r + row_w1 * v1.r + row_w2 * v2.r) * 255.0f; + g_val = (row_w0 * v0.g + row_w1 * v1.g + row_w2 * v2.g) * 255.0f; + b = (row_w0 * v0.b + row_w1 * v1.b + row_w2 * v2.b) * 255.0f; + + for (px = row_min_x; px <= row_max_x; px++) { + idx = py * WIDTH + px; + if (inv_w > g_sys->zbuffer[idx] && inv_w >= min_inv_w * 0.99f) { + ir = (int)r; ir = ir > 255 ? 255 : (ir < 0 ? 0 : ir); + ig = (int)g_val; ig = ig > 255 ? 255 : (ig < 0 ? 0 : ig); + ib = (int)b; ib = ib > 255 ? 255 : (ib < 0 ? 0 : ib); + wf = 1.0f / inv_w; + f = f_w * wf; + if (f > 1.0f) f = 1.0f; + if (f < 0.0f) f = 0.0f; + inv_f = 1.0f - f; + tx = (int)(u_w * wf * tw) & tw_mask; + ty = (int)(v_w * wf * th) & th_mask; + color = pixels[ty * tw + tx]; + wa = (wa_tc * ((color >> 24) & 0xFF)) >> 8; + fr = ((((color >> 16) & 0xFF) * ir >> 8) * wr) >> 8; + fg = ((((color >> 8) & 0xFF) * ig >> 8) * wg) >> 8; + fb = ((color & 0xFF) * ib >> 8) * wb >> 8; + fr = (uint32_t)(fr * inv_f + sky_r * f); + fg = (uint32_t)(fg * inv_f + sky_g * f); + fb = (uint32_t)(fb * inv_f + sky_b * f); + bg = g_sys->pixels[idx]; + g_sys->pixels[idx] = (0xFF << 24) | + (((fr * wa + ((bg >> 16) & 0xFF) * (255 - wa)) >> 8) << 16) | + (((fg * wa + ((bg >> 8) & 0xFF) * (255 - wa)) >> 8) << 8) | + ((fb * wa + (bg & 0xFF) * (255 - wa)) >> 8); + } + inv_w += d_inv_w_dx; u_w += d_u_w_dx; v_w += d_v_w_dx; f_w += d_f_w_dx; + r += d_r_dx_255; g_val += d_g_dx_255; b += d_b_dx_255; + } + } + } else if (tri->tex && (uintptr_t)tri->tex != (uintptr_t)-1) { + tex = tri->tex; + m = tri->mip; + if (m < 0) { + m = 0; + } + if (m > 3) { + m = 3; + } + pixels = tex->mips[m]; + tw = tex->mip_w[m]; + th = tex->mip_h[m]; + tw_mask = tw - 1; + th_mask = th - 1; + + for (py = min_y; py <= max_y; py++) { + row_y = (float)py + 0.5f; + x_long = sv0.x; + if (sv2.y != sv0.y) { + x_long += (row_y - sv0.y) * inv_slope_long; + } + + if (row_y < sv1.y) { + x_short = sv0.x; + if (sv1.y != sv0.y) { + x_short += (row_y - sv0.y) * inv_slope_short1; + } + } else { + x_short = sv1.x; + if (sv2.y != sv1.y) { + x_short += (row_y - sv1.y) * inv_slope_short2; + } + } + + float x_min = (x_long < x_short) ? x_long : x_short; + float x_max = (x_long < x_short) ? x_short : x_long; + + row_min_x = (int)ceilf(x_min - 0.5f); + row_max_x = (int)floorf(x_max - 0.5f); + + if (row_min_x < min_x) row_min_x = min_x; + if (row_max_x > max_x) row_max_x = max_x; + if (row_min_x > row_max_x) { + continue; + } + + dx = (row_min_x + 0.5f) - p_x_start; + dy = row_y - p_y_start; + row_w0 = w0_start + dw0_dx * dx + dw0_dy * dy; + row_w1 = w1_start + dw1_dx * dx + dw1_dy * dy; + row_w2 = 1.0f - row_w0 - row_w1; + inv_w = row_w0 * v0.w + row_w1 * v1.w + row_w2 * v2.w; + u_w = row_w0 * u0_w + row_w1 * u1_w + row_w2 * u2_w; + v_w = row_w0 * v0_w + row_w1 * v1_w + row_w2 * v2_w; + f_w = row_w0 * f0_w + row_w1 * f1_w + row_w2 * f2_w; + r = (row_w0 * v0.r + row_w1 * v1.r + row_w2 * v2.r) * 255.0f; + g_val = (row_w0 * v0.g + row_w1 * v1.g + row_w2 * v2.g) * 255.0f; + b = (row_w0 * v0.b + row_w1 * v1.b + row_w2 * v2.b) * 255.0f; + + for (px = row_min_x; px <= row_max_x; px++) { + idx = py * WIDTH + px; + if (inv_w > g_sys->zbuffer[idx] && inv_w >= min_inv_w * 0.99f) { + wf = 1.0f / inv_w; + tx = (int)(u_w * wf * tw) & tw_mask; + ty = (int)(v_w * wf * th) & th_mask; + color = pixels[ty * tw + tx]; + tex_a = (color >> 24) & 0xFF; + if (tex_a > 127) { + ir = (int)r; ir = ir > 255 ? 255 : (ir < 0 ? 0 : ir); + ig = (int)g_val; ig = ig > 255 ? 255 : (ig < 0 ? 0 : ig); + ib = (int)b; ib = ib > 255 ? 255 : (ib < 0 ? 0 : ib); + f = f_w * wf; + if (f > 1.0f) f = 1.0f; + if (f < 0.0f) f = 0.0f; + inv_f = 1.0f - f; + tex_r = ((color >> 16) & 0xFF) * ir >> 8; + tex_g = ((color >> 8) & 0xFF) * ig >> 8; + tex_b = (color & 0xFF) * ib >> 8; + tex_r = (uint32_t)(tex_r * inv_f + sky_r * f); + tex_g = (uint32_t)(tex_g * inv_f + sky_g * f); + tex_b = (uint32_t)(tex_b * inv_f + sky_b * f); + g_sys->pixels[idx] = (tex_a << 24) | + (tex_r << 16) | (tex_g << 8) | tex_b; + g_sys->zbuffer[idx] = inv_w; + } + } + inv_w += d_inv_w_dx; u_w += d_u_w_dx; v_w += d_v_w_dx; f_w += d_f_w_dx; + r += d_r_dx_255; g_val += d_g_dx_255; b += d_b_dx_255; + } + } + } else { + for (py = min_y; py <= max_y; py++) { + row_y = (float)py + 0.5f; + x_long = sv0.x; + if (sv2.y != sv0.y) { + x_long += (row_y - sv0.y) * inv_slope_long; + } + + if (row_y < sv1.y) { + x_short = sv0.x; + if (sv1.y != sv0.y) { + x_short += (row_y - sv0.y) * inv_slope_short1; + } + } else { + x_short = sv1.x; + if (sv2.y != sv1.y) { + x_short += (row_y - sv1.y) * inv_slope_short2; + } + } + + float x_min = (x_long < x_short) ? x_long : x_short; + float x_max = (x_long < x_short) ? x_short : x_long; + + row_min_x = (int)ceilf(x_min - 0.5f); + row_max_x = (int)floorf(x_max - 0.5f); + + if (row_min_x < min_x) row_min_x = min_x; + if (row_max_x > max_x) row_max_x = max_x; + if (row_min_x > row_max_x) { + continue; + } + + dx = (row_min_x + 0.5f) - p_x_start; + dy = row_y - p_y_start; + row_w0 = w0_start + dw0_dx * dx + dw0_dy * dy; + row_w1 = w1_start + dw1_dx * dx + dw1_dy * dy; + row_w2 = 1.0f - row_w0 - row_w1; + inv_w = row_w0 * v0.w + row_w1 * v1.w + row_w2 * v2.w; + f_w = row_w0 * f0_w + row_w1 * f1_w + row_w2 * f2_w; + r = (row_w0 * v0.r + row_w1 * v1.r + row_w2 * v2.r) * 255.0f; + g_val = (row_w0 * v0.g + row_w1 * v1.g + row_w2 * v2.g) * 255.0f; + b = (row_w0 * v0.b + row_w1 * v1.b + row_w2 * v2.b) * 255.0f; + + for (px = row_min_x; px <= row_max_x; px++) { + idx = py * WIDTH + px; + if (inv_w > g_sys->zbuffer[idx] && inv_w >= min_inv_w * 0.99f) { + ir = (int)r; ir = ir > 255 ? 255 : (ir < 0 ? 0 : ir); + ig = (int)g_val; ig = ig > 255 ? 255 : (ig < 0 ? 0 : ig); + ib = (int)b; ib = ib > 255 ? 255 : (ib < 0 ? 0 : ib); + f = f_w / inv_w; + if (f > 1.0f) f = 1.0f; + if (f < 0.0f) f = 0.0f; + inv_f = 1.0f - f; + if (tri->tex == (void *)-1) { + bg = g_sys->pixels[idx]; + wa = (g_sys->cur_weather.water[3] * 100) >> 8; + g_sys->pixels[idx] = (0xFF << 24) | + (((g_sys->cur_weather.water[0] * wa + ((bg >> 16) & 0xFF) * (255 - wa)) >> 8) << 16) | + (((g_sys->cur_weather.water[1] * wa + ((bg >> 8) & 0xFF) * (255 - wa)) >> 8) << 8) | + (((g_sys->cur_weather.water[2] * wa + (bg & 0xFF) * (255 - wa)) >> 8)); + } else { + ir = (uint32_t)(ir * inv_f + sky_r * f); + ig = (uint32_t)(ig * inv_f + sky_g * f); + ib = (uint32_t)(ib * inv_f + sky_b * f); + g_sys->pixels[idx] = (0xFF << 24) | + (ir << 16) | (ig << 8) | ib; + } + g_sys->zbuffer[idx] = inv_w; + } + inv_w += d_inv_w_dx; f_w += d_f_w_dx; + r += d_r_dx * 255.0f; g_val += d_g_dx * 255.0f; b += d_b_dx * 255.0f; + } + } + } +} + +static void * +render_worker(void *arg) +{ + int id, start_y, end_y, i; + + id = (int)(intptr_t)arg; + start_y = id * HEIGHT / num_threads; + end_y = (id == num_threads - 1) ? HEIGHT - 1 : (id + 1) * HEIGHT / num_threads - 1; + + while (1) { + spin_barrier_wait(&barrier_start); + if (!threads_running) { + break; + } + for (i = 0; i < render_count; i++) { + draw_triangle_band(&render_list[i], start_y, end_y); + } + spin_barrier_wait(&barrier_end); + } + return (NULL); +} + +static void +gfx_init(struct sys_gfx *g) +{ + XIM im; + int scr; + XColor black; + Pixmap bm_no; + Cursor no_ptr; + char no_data[] = { 0,0,0,0,0,0,0,0 }; + char **missing; + int nmissing; + char *def; + int i; + + memset(g, 0, sizeof(*g)); + + setlocale(LC_ALL, ""); + XSetLocaleModifiers(""); + + g->dpy = XOpenDisplay(NULL); + if (g->dpy == NULL) { + errx(1, "xopendisplay failed"); + } + if (!XShmQueryExtension(g->dpy)) { + errx(1, "xshm extension not supported"); + } + scr = DefaultScreen(g->dpy); + g->win = XCreateSimpleWindow(g->dpy, DefaultRootWindow(g->dpy), 0, 0, + WIDTH, HEIGHT, 0, 0, 0); + XSelectInput(g->dpy, g->win, KeyPressMask | KeyReleaseMask | + StructureNotifyMask | FocusChangeMask | PointerMotionMask); + + im = XOpenIM(g->dpy, NULL, NULL, NULL); + g->ic = NULL; + if (im) { + g->ic = XCreateIC(im, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, XNClientWindow, g->win, NULL); + } + g->font_set = XCreateFontSet(g->dpy, "-misc-fixed-medium-r-normal--14-*,*", + &missing, &nmissing, &def); + if (nmissing > 0) { + XFreeStringList(missing); + } + + g->gc = XCreateGC(g->dpy, g->win, 0, NULL); + g->img = XShmCreateImage(g->dpy, DefaultVisual(g->dpy, scr), + DefaultDepth(g->dpy, scr), ZPixmap, NULL, &g->shm, WIDTH, HEIGHT); + g->shm.shmid = shmget(IPC_PRIVATE, g->img->bytes_per_line * g->img->height, + IPC_CREAT | 0777); + g->shm.shmaddr = shmat(g->shm.shmid, 0, 0); + g->img->data = g->shm.shmaddr; + g->pixels = (uint32_t *)g->img->data; + g->shm.readOnly = False; + XShmAttach(g->dpy, &g->shm); + shmctl(g->shm.shmid, IPC_RMID, 0); + g->zbuffer = malloc(WIDTH * HEIGHT * sizeof(float)); + g->wm_delete = XInternAtom(g->dpy, "WM_DELETE_WINDOW", False); + XSetWMProtocols(g->dpy, g->win, &g->wm_delete, 1); + + memset(&black, 0, sizeof(black)); + bm_no = XCreateBitmapFromData(g->dpy, g->win, no_data, 8, 8); + no_ptr = XCreatePixmapCursor(g->dpy, bm_no, bm_no, &black, &black, 0, 0); + XDefineCursor(g->dpy, g->win, no_ptr); + XFreeCursor(g->dpy, no_ptr); + XFreePixmap(g->dpy, bm_no); + + g->key_w = XKeysymToKeycode(g->dpy, XK_w); + g->key_a = XKeysymToKeycode(g->dpy, XK_a); + g->key_s = XKeysymToKeycode(g->dpy, XK_s); + g->key_d = XKeysymToKeycode(g->dpy, XK_d); + g->key_t = XKeysymToKeycode(g->dpy, XK_t); + g->key_esc = XKeysymToKeycode(g->dpy, XK_Escape); + g->key_up = XKeysymToKeycode(g->dpy, XK_Up); + g->key_down = XKeysymToKeycode(g->dpy, XK_Down); + g->key_left = XKeysymToKeycode(g->dpy, XK_Left); + g->key_right = XKeysymToKeycode(g->dpy, XK_Right); + + num_threads = sysconf(_SC_NPROCESSORS_ONLN); + if (num_threads < 1) { + num_threads = 4; + } + spin_barrier_init(&barrier_start, num_threads + 1); + spin_barrier_init(&barrier_end, num_threads + 1); + workers = malloc(num_threads * sizeof(pthread_t)); + for (i = 0; i < num_threads; i++) { + pthread_create(&workers[i], NULL, render_worker, (void *)(intptr_t)i); + } + + v_scratch_cap = 16384; + v_scratch = malloc(v_scratch_cap * sizeof(struct clip_vertex)); +} + +static void +gfx_cleanup(struct sys_gfx *g) +{ + int i; + + threads_running = 0; + spin_barrier_wait(&barrier_start); + if (g->font_set) { + XFreeFontSet(g->dpy, g->font_set); + } + for (i = 0; i < num_threads; i++) { + pthread_join(workers[i], NULL); + } + free(workers); + free(v_scratch); + XUngrabPointer(g->dpy, CurrentTime); + XShmDetach(g->dpy, &g->shm); + XDestroyImage(g->img); + shmdt(g->shm.shmaddr); + free(g->zbuffer); + XFreeGC(g->dpy, g->gc); + XDestroyWindow(g->dpy, g->win); + XCloseDisplay(g->dpy); +} + +static void +gfx_present(struct sys_gfx *g) +{ + char txt[256]; + + XShmPutImage(g->dpy, g->win, g->gc, g->img, 0, 0, 0, 0, WIDTH, HEIGHT, False); + if (g->in_chat) { + snprintf(txt, sizeof(txt), "> %s_", g->chat_buf); + XSetForeground(g->dpy, g->gc, 0x00FF00); + XSetBackground(g->dpy, g->gc, 0x000000); + if (g->font_set) { + Xutf8DrawImageString(g->dpy, g->win, g->font_set, g->gc, + 10, HEIGHT - 20, txt, strlen(txt)); + } else { + XDrawImageString(g->dpy, g->win, g->gc, + 10, HEIGHT - 20, txt, strlen(txt)); + } + } + XSync(g->dpy, False); +} + +static unsigned int +hash_str(const char *str) +{ + unsigned int hash = 5381; + int c; + + while ((c = (unsigned char)*str++)) { + hash = ((hash << 5) + hash) + c; + } + return (hash); +} + +static void +mesh_load_ply(struct mesh *m, const uint8_t *data, size_t sz) +{ + const char *header_end; + const char *p; + const uint8_t *bin; + int temp_materials, idx, k; + char *dst; + const char *src; + uint16_t *mat; + int32_t *idx_ptr; + uint32_t i; + + (void)sz; + header_end = strstr((const char *)data, "end_header\n"); + if (!header_end) { + return; + } + header_end += 11; + + memset(m, 0, sizeof(*m)); + + p = (const char *)data; + temp_materials = 0; + while (p < header_end) { + if (strncmp(p, "comment spr_cx", 14) == 0) m->spr_x = strtof(p + 14, NULL); + else if (strncmp(p, "comment spr_cy", 14) == 0) m->spr_y = strtof(p + 14, NULL); + else if (strncmp(p, "comment spr_cz", 14) == 0) m->spr_z = strtof(p + 14, NULL); + else if (strncmp(p, "comment spr_r", 13) == 0) m->spr_r = strtof(p + 13, NULL); + else if (strncmp(p, "element vertex", 14) == 0) m->num_verts = strtoul(p + 14, NULL, 10); + else if (strncmp(p, "element face", 12) == 0) m->num_tris = strtoul(p + 12, NULL, 10); + else if (strncmp(p, "comment texture", 15) == 0) temp_materials++; + p = strchr(p, '\n'); + if (!p) break; + p++; + } + + m->num_materials = temp_materials; + if (m->num_materials > 0) { + m->raw_tex_names = malloc(m->num_materials * 32); + p = (const char *)data; + idx = 0; + while (p < header_end) { + if (strncmp(p, "comment texture", 15) == 0) { + src = p + 16; + dst = m->raw_tex_names[idx]; + k = 0; + while (src[k] != '\n' && src[k] != '\r' && k < 31) { + dst[k] = src[k]; + k++; + } + dst[k] = '\0'; + idx++; + } + p = strchr(p, '\n'); + if (!p) break; + p++; + } + } + + m->ply_verts = malloc(m->num_verts * sizeof(struct ply_vertex)); + m->indices = malloc(m->num_tris * 3 * sizeof(uint16_t)); + m->mat_ids = malloc(m->num_tris * sizeof(uint16_t)); + + bin = (const uint8_t *)header_end; + + memcpy(m->ply_verts, bin, m->num_verts * sizeof(struct ply_vertex)); + bin += m->num_verts * sizeof(struct ply_vertex); + + for (i = 0; i < m->num_tris; i++) { + bin += 1; + idx_ptr = (int32_t *)bin; + m->indices[i*3+0] = idx_ptr[0]; + m->indices[i*3+1] = idx_ptr[1]; + m->indices[i*3+2] = idx_ptr[2]; + bin += 12; + + mat = (uint16_t *)bin; + m->mat_ids[i] = *mat; + bin += 2; + } + + m->total_area = 0.0f; + for (i = 0; i < m->num_tris; i++) { + uint16_t i0 = m->indices[i*3], i1 = m->indices[i*3+1], i2 = m->indices[i*3+2]; + float ux = m->ply_verts[i1].x - m->ply_verts[i0].x; + float uy = m->ply_verts[i1].y - m->ply_verts[i0].y; + float uz = m->ply_verts[i1].z - m->ply_verts[i0].z; + float vx = m->ply_verts[i2].x - m->ply_verts[i0].x; + float vy = m->ply_verts[i2].y - m->ply_verts[i0].y; + float vz = m->ply_verts[i2].z - m->ply_verts[i0].z; + float cx = uy * vz - uz * vy, cy = uz * vx - ux * vz, cz = ux * vy - uy * vx; + m->total_area += 0.5f * sqrtf(cx*cx + cy*cy + cz*cz); + } +} + +static void +tex_load_tga(struct texture *t, const uint8_t *data, size_t sz) +{ + struct tga_header *hdr; + const uint8_t *src_pixels; + uint32_t *dst, *curr_offset, *src; + int w, h, mw, mh, total_pixels, i, level; + int prev_w, prev_h, next_w, next_h, x, y; + uint32_t c00, c10, c01, c11, r, g, b, a; + + memset(t, 0, sizeof(*t)); + if (data == NULL || sz < sizeof(struct tga_header)) { + w = 2; + h = 2; + t->w = w; + t->h = h; + t->has_alpha = 0; + t->pixels = malloc((4 + 1 + 1 + 1) * sizeof(uint32_t)); + if (t->pixels == NULL) { + return; + } + t->pixels[0] = 0xffa0a0a0; + t->pixels[1] = 0xff808080; + t->pixels[2] = 0xff808080; + t->pixels[3] = 0xffa0a0a0; + + t->mips[0] = t->pixels; + t->mip_w[0] = 2; + t->mip_h[0] = 2; + + for (i = 1; i < 4; i++) { + t->mips[i] = t->pixels + 4; + t->mip_w[i] = 1; + t->mip_h[i] = 1; + } + t->mips[1][0] = 0xff909090; + return; + } + + hdr = (struct tga_header *)data; + w = hdr->width; + h = hdr->height; + t->w = w; + t->h = h; + + mw = w; + mh = h; + total_pixels = 0; + for (i = 0; i < 4; i++) { + total_pixels += mw * mh; + mw = mw > 1 ? mw / 2 : 1; + mh = mh > 1 ? mh / 2 : 1; + } + + t->pixels = malloc(total_pixels * sizeof(uint32_t)); + if (t->pixels == NULL) { + return; + } + src_pixels = data + sizeof(struct tga_header) + hdr->id_length; + + if (hdr->pixel_depth == 32) { + memcpy(t->pixels, src_pixels, w * h * 4); + t->has_alpha = 0; + for (i = 0; i < w * h; i++) { + a = (t->pixels[i] >> 24) & 0xFF; + if (a < 250) { + t->has_alpha = 1; + break; + } + } + } else if (hdr->pixel_depth == 24) { + t->has_alpha = 0; + dst = t->pixels; + for (i = 0; i < w * h; i++) { + dst[i] = (255U << 24) | (src_pixels[i*3+2] << 16) | + (src_pixels[i*3+1] << 8) | src_pixels[i*3+0]; + } + } + + t->mips[0] = t->pixels; + t->mip_w[0] = w; + t->mip_h[0] = h; + + curr_offset = t->pixels + (w * h); + for (level = 1; level < 4; level++) { + prev_w = t->mips[level-1] ? t->mip_w[level-1] : 1; + prev_h = t->mips[level-1] ? t->mip_h[level-1] : 1; + next_w = prev_w > 1 ? prev_w / 2 : 1; + next_h = prev_h > 1 ? prev_h / 2 : 1; + + t->mips[level] = curr_offset; + t->mip_w[level] = next_w; + t->mip_h[level] = next_h; + + src = t->mips[level-1]; + dst = t->mips[level]; + + if (prev_w > 1 && prev_h > 1) { + for (y = 0; y < next_h; y++) { + for (x = 0; x < next_w; x++) { + c00 = src[(y*2)*prev_w + (x*2)]; + c10 = src[(y*2)*prev_w + (x*2+1)]; + c01 = src[(y*2+1)*prev_w + (x*2)]; + c11 = src[(y*2+1)*prev_w + (x*2+1)]; + + r = (((c00 >> 16) & 0xFF) + ((c10 >> 16) & 0xFF) + + ((c01 >> 16) & 0xFF) + ((c11 >> 16) & 0xFF)) / 4; + g = (((c00 >> 8) & 0xFF) + ((c10 >> 8) & 0xFF) + + ((c01 >> 8) & 0xFF) + ((c11 >> 8) & 0xFF)) / 4; + b = ((c00 & 0xFF) + (c10 & 0xFF) + + (c01 & 0xFF) + (c11 & 0xFF)) / 4; + a = (((c00 >> 24) & 0xFF) + ((c10 >> 24) & 0xFF) + + ((c01 >> 24) & 0xFF) + ((c11 >> 24) & 0xFF)) / 4; + + dst[y * next_w + x] = (a << 24) | (r << 16) | + (g << 8) | b; + } + } + } else { + for (i = 0; i < next_w * next_h; i++) { + dst[i] = src[0]; + } + } + curr_offset += next_w * next_h; + } +} + +static uint32_t +tar_hash(const char *str) +{ + uint32_t hash = 5381; + int c; + + while ((c = (unsigned char)*str++)) { + hash = ((hash << 5) + hash) + tolower(c); + } + + return (hash); +} + +static const uint8_t * +tar_lookup(const char *filename, size_t *out_size) +{ + uint32_t slot; + + slot = tar_hash(filename) % TAR_INDEX_SIZE; + while (tar_idx[slot].data != NULL) { + if (strcasecmp(tar_idx[slot].name, filename) == 0) { + *out_size = tar_idx[slot].size; + return (tar_idx[slot].data); + } + slot = (slot + 1) % TAR_INDEX_SIZE; + } + return (NULL); +} + +static struct texture * +cache_get_tex(struct asset_cache *c, const char *name) +{ + unsigned int h; + int idx; + char tar_path[1024]; + size_t sz = 0; + const uint8_t *data; + + if (!name || name[0] == '\0') { + name = "missing"; + } + h = hash_str(name) % (MAX_CACHE * 2); + while (c->tex_hash[h] != 0) { + idx = c->tex_hash[h] - 1; + if (strcasecmp(c->tex_names[idx], name) == 0) { + return (&c->textures[idx]); + } + h = (h + 1) % (MAX_CACHE * 2); + } + if (c->tex_count >= MAX_CACHE) { + return (NULL); + } + idx = c->tex_count++; + snprintf(c->tex_names[idx], 32, "%s", name); + c->tex_hash[h] = idx + 1; + + snprintf(tar_path, sizeof(tar_path), "assets/textures/%s.tga", name); + + data = tar_lookup(tar_path, &sz); + tex_load_tga(&c->textures[idx], data, sz); + return (&c->textures[idx]); +} + +static struct mesh * +cache_get_mesh(struct asset_cache *c, const char *name) +{ + unsigned int h; + int idx; + char tar_path[1024]; + size_t sz = 0; + const uint8_t *data; + uint32_t i; + + h = hash_str(name) % (MAX_CACHE * 2); + while (c->mesh_hash[h] != 0) { + idx = c->mesh_hash[h] - 1; + if (strcasecmp(c->mesh_names[idx], name) == 0) { + return (&c->meshes[idx]); + } + h = (h + 1) % (MAX_CACHE * 2); + } + if (c->mesh_count >= MAX_CACHE) { + return (NULL); + } + idx = c->mesh_count++; + snprintf(c->mesh_names[idx], NAME_SZ, "%s", name); + c->mesh_hash[h] = idx + 1; + + snprintf(tar_path, sizeof(tar_path), "assets/models/%s.ply", name); + + data = tar_lookup(tar_path, &sz); + if (data) { + mesh_load_ply(&c->meshes[idx], data, sz); + if (c->meshes[idx].num_materials > 0) { + c->meshes[idx].textures = malloc(c->meshes[idx].num_materials * sizeof(struct texture *)); + for (i = 0; i < c->meshes[idx].num_materials; i++) { + c->meshes[idx].textures[i] = cache_get_tex(c, c->meshes[idx].raw_tex_names[i]); + } + } + } + return (&c->meshes[idx]); +} + +static void +mat_identity(struct mat4 *m) +{ + memset(m, 0, sizeof(*m)); + m->m[0][0] = m->m[1][1] = m->m[2][2] = m->m[3][3] = 1.0f; +} + +static inline void +mat_mul(struct mat4 *dst, const struct mat4 *a, const struct mat4 *b) +{ + struct mat4 r; + int i; + + for (i = 0; i < 4; i++) { + r.m[i][0] = a->m[i][0]*b->m[0][0] + a->m[i][1]*b->m[1][0] + a->m[i][2]*b->m[2][0] + a->m[i][3]*b->m[3][0]; + r.m[i][1] = a->m[i][0]*b->m[0][1] + a->m[i][1]*b->m[1][1] + a->m[i][2]*b->m[2][1] + a->m[i][3]*b->m[3][1]; + r.m[i][2] = a->m[i][0]*b->m[0][2] + a->m[i][1]*b->m[1][2] + a->m[i][2]*b->m[2][2] + a->m[i][3]*b->m[3][2]; + r.m[i][3] = a->m[i][0]*b->m[0][3] + a->m[i][1]*b->m[1][3] + a->m[i][2]*b->m[2][3] + a->m[i][3]*b->m[3][3]; + } + *dst = r; +} + +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 void +mat_projection(struct mat4 *m, float fov, float aspect, float near, float far) +{ + float f; + + f = 1.0f / tanf(fov * 0.5f * 3.14159265f / 180.0f); + memset(m, 0, sizeof(*m)); + m->m[0][0] = f / aspect; + m->m[1][1] = f; + m->m[2][2] = (far + near) / (far - near); + m->m[2][3] = -(2.0f * far * near) / (far - near); + m->m[3][2] = 1.0f; +} + +static void +mat_view(struct mat4 *m, float cx, float cy, float cz, float cyaw, float cpitch) +{ + struct mat4 trans, rot_y, rot_x, rot; + + mat_translate(&trans, -cx, -cy, -cz); + mat_identity(&rot_y); + rot_y.m[0][0] = cosf(-cyaw); + rot_y.m[0][2] = sinf(-cyaw); + rot_y.m[2][0] = -sinf(-cyaw); + rot_y.m[2][2] = cosf(-cyaw); + mat_identity(&rot_x); + rot_x.m[1][1] = cosf(cpitch); + rot_x.m[1][2] = -sinf(cpitch); + rot_x.m[2][1] = sinf(cpitch); + rot_x.m[2][2] = cosf(cpitch); + mat_mul(&rot, &rot_x, &rot_y); + mat_mul(m, &rot, &trans); +} + +static inline void +mat_transform(struct vec3 *dst, float *w_out, const struct mat4 *m, const struct vec3 *v) +{ + dst->x = m->m[0][0] * v->x + m->m[0][1] * v->y + m->m[0][2] * v->z + m->m[0][3]; + dst->y = m->m[1][0] * v->x + m->m[1][1] * v->y + m->m[1][2] * v->z + m->m[1][3]; + dst->z = m->m[2][0] * v->x + m->m[2][1] * v->y + m->m[2][2] * v->z + m->m[2][3]; + *w_out = m->m[3][0] * v->x + m->m[3][1] * v->y + m->m[3][2] * v->z + m->m[3][3]; +} + +static void +flush_render_list(void) +{ + if (render_count == 0) { + return; + } + spin_barrier_wait(&barrier_start); + spin_barrier_wait(&barrier_end); + render_count = 0; +} + +static void +project_and_push(const struct clip_vertex *c0, const struct clip_vertex *c1, const struct clip_vertex *c2, const struct texture *tex, int is_water, int mip) +{ + struct vertex v0, v1, v2; + float area; + int double_sided; + struct vertex tmp; + + v0.w = 1.0f / c0->w; v0.x = (c0->x * v0.w + 1.0f) * WIDTH * 0.5f; v0.y = (1.0f - c0->y * v0.w) * HEIGHT * 0.5f; + v0.z = c0->w; v0.u = c0->u; v0.v = c0->v; v0.r = c0->r; v0.g = c0->g; v0.b = c0->b; v0.a = c0->a; v0.f = c0->f; + v1.w = 1.0f / c1->w; v1.x = (c1->x * v1.w + 1.0f) * WIDTH * 0.5f; v1.y = (1.0f - c1->y * v1.w) * HEIGHT * 0.5f; + v1.z = c1->w; v1.u = c1->u; v1.v = c1->v; v1.r = c1->r; v1.g = c1->g; v1.b = c1->b; v1.a = c1->a; v1.f = c1->f; + v2.w = 1.0f / c2->w; v2.x = (c2->x * v2.w + 1.0f) * WIDTH * 0.5f; v2.y = (1.0f - c2->y * v2.w) * HEIGHT * 0.5f; + v2.z = c2->w; v2.u = c2->u; v2.v = c2->v; v2.r = c2->r; v2.g = c2->g; v2.b = c2->b; v2.a = c2->a; v2.f = c2->f; + + /* check backface */ + area = edge_func(v0.x, v0.y, v1.x, v1.y, v2.x, v2.y); + double_sided = (tex && tex->has_alpha) || is_water; + if (area >= 0.0f) { + if (double_sided) { + tmp = v1; + v1 = v2; + v2 = tmp; + } else { + return; + } + } + + if (render_count >= MAX_RENDER_TRIS) { + flush_render_list(); + } + render_list[render_count].v0 = v0; + render_list[render_count].v1 = v1; + render_list[render_count].v2 = v2; + render_list[render_count].tex = tex; + render_list[render_count].is_water = is_water; + render_list[render_count].mip = mip; + render_count++; +} + +static struct clip_vertex +clip_intersect(const struct clip_vertex *a, const struct clip_vertex *b, float w_clip) +{ + struct clip_vertex out; + float t; + + t = (w_clip - a->w) / (b->w - a->w); + out.x = a->x + t * (b->x - a->x); + out.y = a->y + t * (b->y - a->y); + out.z = a->z + t * (b->z - a->z); + out.w = w_clip; + out.u = a->u + t * (b->u - a->u); + out.v = a->v + t * (b->v - a->v); + out.r = a->r + t * (b->r - a->r); + out.g = a->g + t * (b->g - a->g); + out.b = a->b + t * (b->b - a->b); + out.a = a->a + t * (b->a - a->a); + out.f = a->f + t * (b->f - a->f); + return (out); +} + +static void +clip_and_push_triangle(const struct clip_vertex *c0, const struct clip_vertex *c1, const struct clip_vertex *c2, const struct texture *tex, int is_water, int mip) +{ + struct clip_vertex p[4]; + const struct clip_vertex *v[3]; + const struct clip_vertex *c, *n; + int len = 0; + int i; + + v[0] = c0; + v[1] = c1; + v[2] = c2; + + for (i = 0; i < 3; i++) { + c = v[i]; + n = v[(i + 1) % 3]; + if (c->w >= 0.1f) { + p[len++] = *c; + } + if ((c->w >= 0.1f) != (n->w >= 0.1f)) { + p[len++] = clip_intersect(c, n, 0.1f); + } + } + if (len == 3) { + project_and_push(&p[0], &p[1], &p[2], tex, is_water, mip); + } else if (len == 4) { + project_and_push(&p[0], &p[1], &p[2], tex, is_water, mip); + project_and_push(&p[0], &p[2], &p[3], tex, is_water, mip); + } +} + +static void +draw_mesh(const struct mesh *m, const struct mat4 *mvp, int is_seabed) +{ + struct clip_vertex *cv; + float ar, ag, ab, far_clp, fog_st, min_dist; + int mip; + float scale; + uint32_t i; + struct vec3 in, out; + float w, fog, d; + + if (m->ply_verts == NULL || m->num_verts == 0) { + return; + } + if (m->num_verts > v_scratch_cap) { + v_scratch_cap = m->num_verts * 2; + v_scratch = realloc(v_scratch, v_scratch_cap * sizeof(struct clip_vertex)); + } + cv = v_scratch; + ar = g_sys->cur_weather.amb[0] / 255.0f * 1.2f; + ag = g_sys->cur_weather.amb[1] / 255.0f * 1.2f; + ab = g_sys->cur_weather.amb[2] / 255.0f * 1.2f; + far_clp = g_sys->cur_weather.far_clp; + fog_st = g_sys->cur_weather.fog_st; + if (far_clp <= fog_st) { + far_clp = fog_st + 1.0f; + } + min_dist = 1e9f; + + for (i = 0; i < m->num_verts; i++) { + in.x = m->ply_verts[i].x; + in.y = m->ply_verts[i].y; + in.z = m->ply_verts[i].z; + mat_transform(&out, &w, mvp, &in); + cv[i].x = out.x; + cv[i].y = out.y; + cv[i].z = out.z; + cv[i].w = w; + cv[i].u = m->ply_verts[i].s; + cv[i].v = m->ply_verts[i].t; + fog = (out.z - fog_st) / (far_clp - fog_st); + if (fog < 0.0f) { + fog = 0.0f; + } else if (fog > 1.0f) { + fog = 1.0f; + } + cv[i].f = fog; + + /* override model pre-light colors with uniform seamless underwater tone */ + if (is_seabed) { + cv[i].r = 0.25f * ar; + cv[i].g = 0.28f * ag; + cv[i].b = 0.30f * ab; + } else { + cv[i].r = (m->ply_verts[i].r / 255.0f) * ar; + cv[i].g = (m->ply_verts[i].g / 255.0f) * ag; + cv[i].b = (m->ply_verts[i].b / 255.0f) * ab; + } + cv[i].a = 1.0f; + + d = sqrtf(out.x*out.x + out.y*out.y + out.z*out.z); + if (d < min_dist) { + min_dist = d; + } + } + mip = 0; + scale = sqrtf(m->total_area) * 0.05f; + if (scale < 0.2f) { + scale = 0.2f; + } + if (scale > 5.0f) { + scale = 5.0f; + } + if (min_dist > 150.0f * scale) { + mip = 3; + } else if (min_dist > 80.0f * scale) { + mip = 2; + } else if (min_dist > 30.0f * scale) { + mip = 1; + } + + for (i = 0; i < m->num_tris; i++) { + uint16_t i0 = m->indices[i*3], i1 = m->indices[i*3+1], i2 = m->indices[i*3+2]; + struct texture *tex; + + if (cv[i0].w < 0.1f && cv[i1].w < 0.1f && cv[i2].w < 0.1f) { + continue; + } + if (cv[i0].w > far_clp && cv[i1].w > far_clp && cv[i2].w > far_clp) { + continue; + } + tex = (m->num_materials > 0 && m->textures) ? m->textures[m->mat_ids[i] < m->num_materials ? m->mat_ids[i] : 0] : NULL; + clip_and_push_triangle(&cv[i0], &cv[i1], &cv[i2], tex, 0, mip); + } +} + +static int +is_occluded(struct sys_gfx *g, float cx, float cy, float cz, float r) +{ + (void)g; (void)cx; (void)cy; (void)cz; (void)r; + return (0); +} + +static int +cmp_draw_item(const void *a, const void *b) +{ + float da = ((const struct draw_item *)a)->dist_sq; + float db = ((const struct draw_item *)b)->dist_sq; + + if (da < db) + return (-1); + if (da > db) + return (1); + return (0); +} + +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) +{ + 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; + uint8_t sr, sg, sb, hr, hg, hb; + uint32_t c; + struct mesh *m; + struct vec3 mc, cv; + struct mat4 mvp; + + val = view->m[1][2]; + if (val > 1.0f) { + val = 1.0f; + } else if (val < -1.0f) { + val = -1.0f; + } + pitch = asin(val); + y_horiz = HEIGHT / 2 + (int)(pitch * 400.0f); + sr = g->cur_weather.sky_top[0]; + sg = g->cur_weather.sky_top[1]; + sb = g->cur_weather.sky_top[2]; + hr = g->cur_weather.sky_bot[0]; + hg = g->cur_weather.sky_bot[1]; + hb = g->cur_weather.sky_bot[2]; + + for (y = 0; y < HEIGHT; y++) { + c = (hr << 16) | (hg << 8) | hb; + if (y < y_horiz) { + float f = (float)y / (float)(y_horiz > 0 ? y_horiz : 1); + c = ((sr + (uint8_t)(f * (hr - sr))) << 16) | ((sg + (uint8_t)(f * (hg - sg))) << 8) | (sb + (uint8_t)(f * (hb - sb))); + } + for (x = 0; x < WIDTH; x++) { + g->pixels[y * WIDTH + x] = c; + } + } + far_clp = g->cur_weather.far_clp; + fov_y = tanf(60.0f * 0.5f * 3.14159f / 180.0f); + fov_x = fov_y * ((float)WIDTH / HEIGHT); + min_gx = (int)((cam_x - far_clp - MAP_MIN) / ((MAP_MAX - MAP_MIN) / GRID_SZ)); + max_gx = (int)((cam_x + far_clp - MAP_MIN) / ((MAP_MAX - MAP_MIN) / GRID_SZ)); + min_gz = (int)((cam_z - far_clp - MAP_MIN) / ((MAP_MAX - MAP_MIN) / GRID_SZ)); + max_gz = (int)((cam_z + far_clp - MAP_MIN) / ((MAP_MAX - MAP_MIN) / GRID_SZ)); + + if (min_gx < 0) min_gx = 0; + if (max_gx >= GRID_SZ) max_gx = GRID_SZ - 1; + if (min_gz < 0) min_gz = 0; + if (max_gz >= GRID_SZ) max_gz = GRID_SZ - 1; + + draw_count = 0; + for (gz = min_gz; gz <= max_gz; gz++) { + for (gx = min_gx; gx <= max_gx; gx++) { + for (i = 0; i < scene_grid[gx][gz].count; i++) { + id = scene_grid[gx][gz].ids[i]; + + /* check time-of-day visibility for timed objects */ + int16_t t_on = g_transforms.time_on[id]; + int16_t t_off = g_transforms.time_off[id]; + if (t_on != t_off) { + float cur_h = g->game_time_hours; + int visible = 0; + if (t_on > t_off) { + if (cur_h >= t_on || cur_h < t_off) { + visible = 1; + } + } else { + if (cur_h >= t_on && cur_h < t_off) { + visible = 1; + } + } + if (!visible) { + continue; + } + } + + dx = g_transforms.sphere_cx[id] - cam_x; + dy = g_transforms.sphere_cy[id] - cam_y; + dz = g_transforms.sphere_cz[id] - cam_z; + dist_sq = dx*dx + dy*dy + dz*dz; + if (dist_sq > (far_clp + g_transforms.sphere_r[id])*(far_clp + g_transforms.sphere_r[id])) { + continue; + } + if (draw_count >= draw_cap) { + draw_cap = draw_cap == 0 ? 1024 : draw_cap * 2; + draw_list = realloc(draw_list, draw_cap * sizeof(struct draw_item)); + } + draw_list[draw_count].id = id; + draw_list[draw_count].dist_sq = dist_sq; + draw_count++; + } + } + } + qsort(draw_list, draw_count, sizeof(struct draw_item), cmp_draw_item); + + for (i = 0; i < draw_count; i++) { + id = draw_list[i].id; + m = scene_meshes[id]; + if (!m || !m->ply_verts) { + continue; + } + mc.x = g_transforms.sphere_cx[id]; + mc.y = g_transforms.sphere_cy[id]; + mc.z = g_transforms.sphere_cz[id]; + r = g_transforms.sphere_r[id] * 1.05f; + mat_transform(&cv, &w, view, &mc); + if (cv.z + r < 0.1f || cv.z - r > far_clp || fabsf(cv.x) - r > cv.z * fov_x || fabsf(cv.y) - r > cv.z * fov_y) { + continue; + } + if (is_occluded(g, cv.x, cv.y, cv.z, r)) { + continue; + } + mat_mul(&mvp, view, &g_transforms.world_matrices[id]); + mat_mul(&mvp, proj, &mvp); + draw_mesh(m, &mvp, 0); + } + flush_render_list(); +} + +static void +update_weather(struct sys_gfx *g) +{ + float t, snaps[8] = {0.0f, 5.0f, 6.0f, 7.0f, 12.0f, 19.0f, 20.0f, 22.0f}; + int i0, i1; + float f; + struct timecyc_entry *w0, *w1; + + if (g->num_weathers == 0) { + return; + } + if (g->game_weather >= g->num_weathers) { + g->game_weather = 0; + } + t = g->game_time_hours; + i0 = 7; + i1 = 0; + f = 0.0f; + if (t >= snaps[7] || t < snaps[0]) { + float wt = t; + if (wt < snaps[0]) { + wt += 24.0f; + } + f = (wt - snaps[7]) / (24.0f - snaps[7] + snaps[0]); + } else { + for (int i = 0; i < 7; i++) { + if (t >= snaps[i] && t < snaps[i+1]) { + i0 = i; + i1 = i + 1; + f = (t - snaps[i]) / (snaps[i+1] - snaps[i]); + break; + } + } + } + w0 = &g->weathers[g->game_weather][i0]; + w1 = &g->weathers[g->game_weather][i1]; + #define LERP(c) g->cur_weather.c = w0->c + f * (w1->c - w0->c) + LERP(amb[0]); LERP(amb[1]); LERP(amb[2]); LERP(sky_top[0]); LERP(sky_top[1]); LERP(sky_top[2]); LERP(sky_bot[0]); LERP(sky_bot[1]); LERP(sky_bot[2]); + LERP(water[0]); LERP(water[1]); LERP(water[2]); LERP(water[3]); LERP(far_clp); LERP(fog_st); + #undef LERP + if (g->far_clip_override > 10.0f) { + g->cur_weather.far_clp = g->far_clip_override; + } +} + +static void +draw_water(const struct mat4 *proj, const struct mat4 *view) +{ + struct clip_vertex cv[4]; + struct mat4 mvp; + struct vec3 in, out; + float t, far_clp, fog_st, ar, ag, ab, w, fog, u_tex, v_tex; + float wave_height, wave_phase, wave_speed; + float flow_x, flow_z, len, dir_x, dir_z; + float dx, dy, dz, scroll_u, scroll_v; + uint32_t i; + int j; + + mat_mul(&mvp, proj, view); + t = g_sys->game_time_sec; + far_clp = g_sys->cur_weather.far_clp; + fog_st = g_sys->cur_weather.fog_st; + if (far_clp <= fog_st) { + far_clp = fog_st + 1.0f; + } + ar = g_sys->cur_weather.amb[0] / 255.0f * 1.2f; + ag = g_sys->cur_weather.amb[1] / 255.0f * 1.2f; + ab = g_sys->cur_weather.amb[2] / 255.0f * 1.2f; + + for (i = 0; i < water_count; i++) { + for (j = 0; j < water_faces[i].num; j++) { + flow_x = water_faces[i].u[j]; + flow_z = water_faces[i].v[j]; + wave_height = water_faces[i].h[j]; + + len = sqrtf(flow_x * flow_x + flow_z * flow_z); + dir_x = 0.7071f; + dir_z = 0.7071f; + if (len > 0.001f) { + dir_x = flow_x / len; + dir_z = flow_z / len; + } + + wave_speed = (len > 0.01f) ? (len * 3.0f) : 1.5f; + + wave_phase = t * wave_speed + + (water_faces[i].x[j] * dir_x + water_faces[i].y[j] * dir_z) * 0.04f; + + dy = sinf(wave_phase) * wave_height; + + dx = -cosf(wave_phase) * wave_height * 0.45f * dir_x; + dz = -cosf(wave_phase) * wave_height * 0.45f * dir_z; + + in.x = water_faces[i].x[j] + dx; + in.y = water_faces[i].z[j] + dy; + in.z = water_faces[i].y[j] + dz; + + mat_transform(&out, &w, &mvp, &in); + fog = (out.z - fog_st) / (far_clp - fog_st); + if (fog < 0.0f) { + fog = 0.0f; + } else if (fog > 1.0f) { + fog = 1.0f; + } + + scroll_u = flow_x; + scroll_v = flow_z; + if (fabsf(scroll_u) < 0.001f && fabsf(scroll_v) < 0.001f) { + scroll_u = 0.006f; + scroll_v = 0.004f; + } + + u_tex = (water_faces[i].x[j] - water_faces[i].x[0]) * 0.01f + t * scroll_u + dy * 0.03f; + v_tex = (water_faces[i].y[j] - water_faces[i].y[0]) * 0.01f + t * scroll_v + dy * 0.03f; + + cv[j].x = out.x; + cv[j].y = out.y; + cv[j].z = out.z; + cv[j].w = w; + cv[j].u = u_tex; + cv[j].v = v_tex; + cv[j].r = ar; + cv[j].g = ag; + cv[j].b = ab; + cv[j].a = 1.0f; + cv[j].f = fog; + } + if (water_faces[i].num == 4) { + if (!(cv[0].w < 0.1f && cv[2].w < 0.1f && cv[1].w < 0.1f) && !(cv[0].w > far_clp && cv[2].w > far_clp && cv[1].w > far_clp)) { + clip_and_push_triangle(&cv[0], &cv[2], &cv[1], water_tex, 1, 0); + } + if (!(cv[2].w < 0.1f && cv[3].w < 0.1f && cv[1].w < 0.1f) && !(cv[2].w > far_clp && cv[3].w > far_clp && cv[1].w > far_clp)) { + clip_and_push_triangle(&cv[2], &cv[3], &cv[1], water_tex, 1, 0); + } + } else if (water_faces[i].num == 3) { + if (!(cv[0].w < 0.1f && cv[2].w < 0.1f && cv[1].w < 0.1f) && !(cv[0].w > far_clp && cv[2].w > far_clp && cv[1].w > far_clp)) { + clip_and_push_triangle(&cv[0], &cv[2], &cv[1], water_tex, 1, 0); + } + } + } + flush_render_list(); +} + +static void +draw_ocean_sector(const struct mat4 *mvp, float x0, float x1, float z0, float z1, float height, float t, const struct texture *tex, int is_water) +{ + struct clip_vertex cv[4]; + struct vec3 in, out; + float far_clp, fog_st, ar, ag, ab, w, fog, u_tex, v_tex; + float prelight_r, prelight_g, prelight_b; + float step = 500.0f; + float tx, tz, next_tx, next_tz; + float xs[4], zs[4]; + float wave_height, wave_phase, dx, dy, dz; + float dir_x = 0.7071f, dir_z = 0.7071f; + int i; + + far_clp = g_sys->cur_weather.far_clp; + fog_st = g_sys->cur_weather.fog_st; + if (far_clp <= fog_st) { + far_clp = fog_st + 1.0f; + } + ar = g_sys->cur_weather.amb[0] / 255.0f * 1.2f; + ag = g_sys->cur_weather.amb[1] / 255.0f * 1.2f; + ab = g_sys->cur_weather.amb[2] / 255.0f * 1.2f; + + prelight_r = is_water ? 1.0f : 0.35f; + prelight_g = is_water ? 1.0f : 0.38f; + prelight_b = is_water ? 1.0f : 0.40f; + + wave_height = is_water ? 0.8f : 0.0f; + + for (tx = floorf(x0 / step) * step; tx < x1; tx += step) { + next_tx = tx + step; + if (next_tx > x1) { + next_tx = x1; + } + for (tz = floorf(z0 / step) * step; tz < z1; tz += step) { + next_tz = tz + step; + if (next_tz > z1) { + next_tz = z1; + } + + xs[0] = tx; xs[1] = next_tx; xs[2] = next_tx; xs[3] = tx; + zs[0] = tz; zs[1] = tz; zs[2] = next_tz; zs[3] = next_tz; + + for (i = 0; i < 4; i++) { + dy = 0.0f; + dx = 0.0f; + dz = 0.0f; + + if (is_water) { + wave_phase = t * 1.5f + (xs[i] * dir_x + zs[i] * dir_z) * 0.04f; + dy = sinf(wave_phase) * wave_height; + dx = -cosf(wave_phase) * wave_height * 0.45f * dir_x; + dz = -cosf(wave_phase) * wave_height * 0.45f * dir_z; + } + + in.x = xs[i] + dx; + in.y = height + dy; + in.z = zs[i] + dz; + + mat_transform(&out, &w, mvp, &in); + fog = (out.z - fog_st) / (far_clp - fog_st); + if (fog < 0.0f) { + fog = 0.0f; + } else if (fog > 1.0f) { + fog = 1.0f; + } + + u_tex = xs[i] * 0.01f; + v_tex = zs[i] * 0.01f; + if (is_water) { + u_tex += t * 0.008f + dy * 0.03f; + v_tex += t * 0.006f + dy * 0.03f; + } + + cv[i] = (struct clip_vertex){ + out.x, out.y, out.z, w, u_tex, v_tex, + ar * prelight_r, ag * prelight_g, ab * prelight_b, + 1.0f, fog + }; + } + if (!(cv[0].w < 0.1f && cv[2].w < 0.1f && cv[1].w < 0.1f) && !(cv[0].w > far_clp && cv[2].w > far_clp && cv[1].w > far_clp)) { + clip_and_push_triangle(&cv[0], &cv[2], &cv[1], tex, is_water, 0); + } + if (!(cv[0].w < 0.1f && cv[3].w < 0.1f && cv[2].w < 0.1f) && !(cv[0].w > far_clp && cv[3].w > far_clp && cv[2].w > far_clp)) { + clip_and_push_triangle(&cv[0], &cv[3], &cv[2], tex, is_water, 0); + } + } + } +} + +static void +draw_infinite_ocean(const struct mat4 *proj, const struct mat4 *view, float cam_x, float cam_z, float t) +{ + struct mat4 mvp; + float far_clp, b; + float n_x0, n_x1, n_z0, n_z1; + float s_x0, s_x1, s_z0, s_z1; + float w_x0, w_x1, w_z0, w_z1; + float e_x0, e_x1, e_z0, e_z1; + + mat_mul(&mvp, proj, view); + far_clp = g_sys->cur_weather.far_clp; + b = 2970.0f; /* 10m overlap to close seams */ + + /* north sector */ + n_x0 = cam_x - far_clp * 1.5f; + n_x1 = cam_x + far_clp * 1.5f; + n_z0 = fmaxf(b, cam_z - far_clp * 1.5f); + n_z1 = cam_z + far_clp * 1.5f; + if (n_z1 > n_z0) { + draw_ocean_sector(&mvp, n_x0, n_x1, n_z0, n_z1, -65.0f, t, sand_tex, 0); + draw_ocean_sector(&mvp, n_x0, n_x1, n_z0, n_z1, 0.0f, t, water_tex, 1); + } + + /* south sector */ + s_x0 = cam_x - far_clp * 1.5f; + s_x1 = cam_x + far_clp * 1.5f; + s_z0 = cam_z - far_clp * 1.5f; + s_z1 = fminf(-b, cam_z + far_clp * 1.5f); + if (s_z1 > s_z0) { + draw_ocean_sector(&mvp, s_x0, s_x1, s_z0, s_z1, -65.0f, t, sand_tex, 0); + draw_ocean_sector(&mvp, s_x0, s_x1, s_z0, s_z1, 0.0f, t, water_tex, 1); + } + + /* west sector */ + w_x0 = cam_x - far_clp * 1.5f; + w_x1 = fminf(-b, cam_x + far_clp * 1.5f); + w_z0 = fmaxf(-b, cam_z - far_clp * 1.5f); + w_z1 = fminf(b, cam_z + far_clp * 1.5f); + if (w_x1 > w_x0 && w_z1 > w_z0) { + draw_ocean_sector(&mvp, w_x0, w_x1, w_z0, w_z1, -65.0f, t, sand_tex, 0); + draw_ocean_sector(&mvp, w_x0, w_x1, w_z0, w_z1, 0.0f, t, water_tex, 1); + } + + /* east sector */ + e_x0 = fmaxf(b, cam_x - far_clp * 1.5f); + e_x1 = cam_x + far_clp * 1.5f; + e_z0 = fmaxf(-b, cam_z - far_clp * 1.5f); + e_z1 = fminf(b, cam_z + far_clp * 1.5f); + if (e_x1 > e_x0 && e_z1 > e_z0) { + draw_ocean_sector(&mvp, e_x0, e_x1, e_z0, e_z1, -65.0f, t, sand_tex, 0); + draw_ocean_sector(&mvp, e_x0, e_x1, e_z0, e_z1, 0.0f, t, water_tex, 1); + } + flush_render_list(); +} + +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 = 100.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, *end; + int in_inst, id, interior, idx, gx, gz; + char name[NAME_SZ]; + float px, py, pz, qx, qy, qz, qw, w; + struct mesh *m; + struct mat4 rot, trans, sa2gl; + struct vec3 wc; + struct grid_cell *c; + int lod, time_on, time_off; + + buf = malloc(sz + 1); + if (buf == NULL) { + return; + } + memcpy(buf, data, sz); + buf[sz] = '\0'; + + line = buf; + in_inst = 0; + while (line && *line) { + next_line = strchr(line, '\n'); + if (next_line) { + *next_line = '\0'; + next_line++; + } + p = line; + while (*p == ' ' || *p == '\t') { + p++; + } + if (*p == '\0' || *p == '#') { + line = next_line; + continue; + } + if (strncasecmp(p, "inst", 4) == 0 || strncasecmp(p, "tobj", 4) == 0) { + in_inst = 1; + line = next_line; + continue; + } + if (strncasecmp(p, "end", 3) == 0) { + in_inst = 0; + line = next_line; + continue; + } + if (in_inst) { + /* initialize fields to default values before sscanf */ + 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) { + end = name + strlen(name) - 1; + while (end > name && (*end == ' ' || *end == '\t')) { + *end = '\0'; + end--; + } + for (p = name; *p; p++) { + *p = tolower((unsigned char)*p); + } + + if (scene_inst_count >= scene_inst_capacity) { + scene_inst_capacity = scene_inst_capacity == 0 ? 512 : scene_inst_capacity * 2; + scene_meshes = realloc(scene_meshes, scene_inst_capacity * sizeof(struct mesh *)); + g_transforms.pos_x = realloc(g_transforms.pos_x, scene_inst_capacity * sizeof(float)); + g_transforms.pos_y = realloc(g_transforms.pos_y, scene_inst_capacity * sizeof(float)); + g_transforms.pos_z = realloc(g_transforms.pos_z, scene_inst_capacity * sizeof(float)); + g_transforms.rot_qx = realloc(g_transforms.rot_qx, scene_inst_capacity * sizeof(float)); + g_transforms.rot_qy = realloc(g_transforms.rot_qy, scene_inst_capacity * sizeof(float)); + g_transforms.rot_qz = realloc(g_transforms.rot_qz, scene_inst_capacity * sizeof(float)); + g_transforms.rot_qw = realloc(g_transforms.rot_qw, scene_inst_capacity * sizeof(float)); + g_transforms.world_matrices = realloc(g_transforms.world_matrices, scene_inst_capacity * sizeof(struct mat4)); + g_transforms.sphere_cx = realloc(g_transforms.sphere_cx, scene_inst_capacity * sizeof(float)); + g_transforms.sphere_cy = realloc(g_transforms.sphere_cy, scene_inst_capacity * sizeof(float)); + g_transforms.sphere_cz = realloc(g_transforms.sphere_cz, scene_inst_capacity * sizeof(float)); + g_transforms.sphere_r = realloc(g_transforms.sphere_r, scene_inst_capacity * sizeof(float)); + g_transforms.time_on = realloc(g_transforms.time_on, scene_inst_capacity * sizeof(int16_t)); + g_transforms.time_off = realloc(g_transforms.time_off, scene_inst_capacity * sizeof(int16_t)); + } + + m = cache_get_mesh(cache, name); + if (m) { + idx = scene_inst_count++; + scene_meshes[idx] = m; + g_transforms.pos_x[idx] = px; + g_transforms.pos_y[idx] = py; + g_transforms.pos_z[idx] = pz; + g_transforms.rot_qx[idx] = qx; + g_transforms.rot_qy[idx] = qy; + g_transforms.rot_qz[idx] = qz; + g_transforms.rot_qw[idx] = qw; + + /* store limits for time checking */ + g_transforms.time_on[idx] = (time_on >= 0) ? time_on : 0; + g_transforms.time_off[idx] = (time_off >= 0) ? time_off : 0; + + mat_from_quat(&rot, qx, qy, qz, qw); + mat_translate(&trans, px, py, pz); + mat_mul(&g_transforms.world_matrices[idx], &trans, &rot); + + memset(&sa2gl, 0, sizeof(sa2gl)); + sa2gl.m[0][0] = sa2gl.m[1][2] = sa2gl.m[2][1] = sa2gl.m[3][3] = 1.0f; + mat_mul(&g_transforms.world_matrices[idx], &sa2gl, &g_transforms.world_matrices[idx]); + + mat_transform(&wc, &w, &g_transforms.world_matrices[idx], &(struct vec3){m->spr_x, m->spr_y, m->spr_z}); + g_transforms.sphere_cx[idx] = wc.x; + g_transforms.sphere_cy[idx] = wc.y; + g_transforms.sphere_cz[idx] = wc.z; + g_transforms.sphere_r[idx] = m->spr_r; + + gx = (int)((wc.x - MAP_MIN) / ((MAP_MAX - MAP_MIN) / GRID_SZ)); + gz = (int)((wc.z - MAP_MIN) / ((MAP_MAX - MAP_MIN) / GRID_SZ)); + if (gx < 0) gx = 0; if (gx >= GRID_SZ) gx = GRID_SZ - 1; + if (gz < 0) gz = 0; if (gz >= GRID_SZ) gz = GRID_SZ - 1; + + c = &scene_grid[gx][gz]; + if (c->count >= c->cap) { + c->cap = c->cap == 0 ? 16 : c->cap * 2; + c->ids = realloc(c->ids, c->cap * sizeof(int)); + } + c->ids[c->count++] = idx; + } + } + } + line = next_line; + } + free(buf); +} + +static void +tar_build_index(void) +{ + size_t offset = 0; + struct tar_header *h; + size_t file_sz; + uint32_t slot; + + memset(tar_idx, 0, sizeof(tar_idx)); + + while (offset < g_tar_size) { + h = (struct tar_header *)(g_tar_mapped + offset); + if (h->name[0] == '\0') { + break; + } + file_sz = get_tar_size(h->size); + const uint8_t *file_data = g_tar_mapped + offset + 512; + + slot = tar_hash(h->name) % TAR_INDEX_SIZE; + while (tar_idx[slot].data != NULL) { + if (strcasecmp(tar_idx[slot].name, h->name) == 0) { + goto next_file; + } + slot = (slot + 1) % TAR_INDEX_SIZE; + } + + strncpy(tar_idx[slot].name, h->name, sizeof(tar_idx[slot].name) - 1); + tar_idx[slot].data = file_data; + tar_idx[slot].size = file_sz; + + next_file: + offset += 512 + ((file_sz + 511) & ~511); + } +} + +int +main(void) +{ + struct sys_gfx g; + struct asset_cache *cache; + struct mat4 view; + struct timespec ts, t_start, t_end; + XEvent ev; + 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; + 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; + KeyCode code; + + g_sys = &g; + gfx_init(&g); + cache = calloc(1, sizeof(struct asset_cache)); + g.game_time_sec = 0.0f; + g.game_time_hours = 12.0f; + ts.tv_sec = 0; + ts.tv_nsec = 16666666; + + tar_fd = open("assets.tar", O_RDONLY); + if (tar_fd < 0) { + err(1, "failed to open assets.tar. run bin/build first"); + } + fstat(tar_fd, &st); + g_tar_size = st.st_size; + g_tar_mapped = mmap(NULL, g_tar_size, PROT_READ, MAP_PRIVATE, tar_fd, 0); + if (g_tar_mapped == MAP_FAILED) { + err(1, "failed to mmap assets.tar"); + } + tar_build_index(); + + ipl_data = tar_lookup("assets/map.ipl", &ipl_sz); + if (ipl_data) { + scene_load(cache, ipl_data, ipl_sz); + } + + water_data = tar_lookup("assets/water.bin", &water_sz); + if (water_data && water_sz > 0) { + memcpy(&water_count, water_data, 4); + water_faces = malloc(water_count * sizeof(struct water_face)); + memcpy(water_faces, water_data + 4, water_count * sizeof(struct water_face)); + } + + tc_data = tar_lookup("assets/timecyc.bin", &tc_sz); + if (tc_data && tc_sz > 0) { + memcpy(&g.num_weathers, tc_data, 4); + if (g.num_weathers > 32) { + g.num_weathers = 32; + } + memcpy(g.weathers, tc_data + 4, g.num_weathers * 8 * sizeof(struct timecyc_entry)); + } + + water_tex = cache_get_tex(cache, "waterclear256"); + sand_tex = cache_get_tex(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); + XSync(g.dpy, False); + + running = 1; + while (running) { + last_mx = WIDTH / 2; + last_my = HEIGHT / 2; + m_dx = 0; + m_dy = 0; + + while (XPending(g.dpy)) { + XNextEvent(g.dpy, &ev); + + /* filter dead keys and system layout events */ + if (XFilterEvent(&ev, None)) { + continue; + } + + if (ev.type == ClientMessage && (Atom)ev.xclient.data.l[0] == g.wm_delete) { + running = 0; + } + if (ev.type == FocusIn && !g.in_chat) { + XGrabPointer(g.dpy, g.win, True, PointerMotionMask | ButtonPressMask | ButtonReleaseMask, GrabModeAsync, GrabModeAsync, g.win, None, CurrentTime); + } + if (ev.type == MotionNotify) { + last_mx = ev.xmotion.x; + last_my = ev.xmotion.y; + } + if (ev.type == KeyPress) { + code = ev.xkey.keycode; + 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); + } else { + running = 0; + } + } + if (g.in_chat) { + char str[32]; + KeySym sym; + Status status; + int n = 0; + + if (g_sys->ic) { + n = Xutf8LookupString(g_sys->ic, &ev.xkey, str, sizeof(str), &sym, &status); + } else { + n = XLookupString(&ev.xkey, str, sizeof(str), &sym, NULL); + } + + if (sym == XK_Return) { + if (strncmp(g.chat_buf, "/st ", 4) == 0) { + g.game_time_hours = atof(g.chat_buf + 4); + } else if (strncmp(g.chat_buf, "/sw ", 4) == 0) { + g.game_weather = atoi(g.chat_buf + 4); + } else if (strncmp(g.chat_buf, "/sd ", 4) == 0) { + g.far_clip_override = atof(g.chat_buf + 4); + } + g.in_chat = 0; + 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) { + g.chat_len--; + if ((g.chat_buf[g.chat_len] & 0xC0) != 0x80) { + break; + } + } + g.chat_buf[g.chat_len] = '\0'; + } + } 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'; + } + } else { + if (code == g.key_w) g.keys['w'] = 1; + 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; + g.chat_buf[0] = '\0'; + XUngrabPointer(g.dpy, CurrentTime); + } + } + } + if (ev.type == KeyRelease) { + code = ev.xkey.keycode; + if (code == g.key_w) g.keys['w'] = 0; + 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; + } + } + + /* calculate single precise delta at the end of the frame */ + 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); + } + + 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; + + g.game_time_sec += dt; + g.game_time_hours += dt * (24.0f / 1440.0f); + if (g.game_time_hours >= 24.0f) { + g.game_time_hours -= 24.0f; + } + + if (!g.in_chat) { + update_camera(&cam_x, &cam_y, &cam_z, &cam_yaw, &cam_pitch, g.keys, m_dx, m_dy, dt); + } + + update_weather(&g); + mat_projection(&g.proj, 60.0f, (float)WIDTH / HEIGHT, 0.1f, g.cur_weather.far_clp); + 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_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); + + /* prevent memory leaks */ + for (i = 0; i < cache->mesh_count; i++) { + free(cache->meshes[i].ply_verts); + free(cache->meshes[i].indices); + free(cache->meshes[i].mat_ids); + if (cache->meshes[i].num_materials > 0) { + free(cache->meshes[i].raw_tex_names); + free(cache->meshes[i].textures); + } + } + for (i = 0; i < cache->tex_count; i++) { + free(cache->textures[i].pixels); + } + free(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); +}