1078 lines
27 KiB
C
1078 lines
27 KiB
C
// Compile with
|
|
// gcc -std=c99 map_packer.c -lm -o map_packer
|
|
|
|
#include <math.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Utils, Vec2/3
|
|
|
|
typedef uint8_t u8;
|
|
typedef int8_t i8;
|
|
typedef uint16_t u16;
|
|
typedef int16_t i16;
|
|
typedef uint32_t u32;
|
|
typedef int32_t i32;
|
|
typedef uint64_t u64;
|
|
typedef int64_t i64;
|
|
|
|
typedef float f32;
|
|
typedef double f64;
|
|
|
|
#define F32_COMPARE_EPSILON 0.001
|
|
|
|
bool equals(f32 a, f32 b) {
|
|
return fabs(a - b) < F32_COMPARE_EPSILON;
|
|
}
|
|
|
|
#define M_PI 3.14159265358
|
|
|
|
f32 deg_to_rad(f32 deg) {
|
|
return (deg/180.0) * M_PI;
|
|
}
|
|
|
|
#define max(a,b) \
|
|
({ __typeof__ (a) _a = (a); \
|
|
__typeof__ (b) _b = (b); \
|
|
_a > _b ? _a : _b; })
|
|
#define min(a,b) \
|
|
({ __typeof__ (a) _a = (a); \
|
|
__typeof__ (b) _b = (b); \
|
|
_a < _b ? _a : _b; })
|
|
|
|
|
|
typedef struct {
|
|
char *ptr;
|
|
i32 length;
|
|
} string_t;
|
|
|
|
#define string(PTR, LENGTH) ((string_t){.ptr = PTR, .length = LENGTH})
|
|
#define s(STR) ((string_t){.ptr = STR, .length = sizeof(STR)-1})
|
|
|
|
bool string_equals(string_t a, string_t b) {
|
|
if (a.length != b.length) {
|
|
return false;
|
|
}
|
|
return memcmp(a.ptr, b.ptr, a.length) == 0;
|
|
}
|
|
|
|
char string_temp[1024];
|
|
char *string_cstring(string_t s) {
|
|
i32 length = min(sizeof(string_temp)-1, s.length);
|
|
memcpy(string_temp, s.ptr, length);
|
|
string_temp[length] = '\0';
|
|
return string_temp;
|
|
}
|
|
|
|
|
|
|
|
typedef struct {
|
|
f32 x, y, z;
|
|
} vec3_t;
|
|
|
|
#define vec3(X, Y, Z) ((vec3_t){.x = X, .y = Y, .z = Z})
|
|
|
|
vec3_t vec3_add(vec3_t a, vec3_t b) {
|
|
return vec3(a.x + b.x, a.y + b.y, a.z + b.z);
|
|
}
|
|
|
|
vec3_t vec3_sub(vec3_t a, vec3_t b) {
|
|
return vec3(a.x - b.x, a.y - b.y, a.z - b.z);
|
|
}
|
|
|
|
vec3_t vec3_mul(vec3_t a, vec3_t b) {
|
|
return vec3(a.x * b.x, a.y * b.y, a.z * b.z);
|
|
}
|
|
|
|
vec3_t vec3_mulf(vec3_t a, f32 f) {
|
|
return vec3(a.x * f, a.y * f, a.z * f);
|
|
}
|
|
|
|
vec3_t vec3_divf(vec3_t a, f32 f) {
|
|
return vec3(a.x / f, a.y / f, a.z / f);
|
|
}
|
|
|
|
bool vec3_equals(vec3_t a, vec3_t b) {
|
|
return (equals(a.x, b.x) && equals(a.y, b.y) && equals(a.z, b.z));
|
|
}
|
|
|
|
f32 vec3_length(vec3_t a) {
|
|
return sqrt(a.x * a.x + a.y * a.y + a.z * a.z);
|
|
}
|
|
|
|
vec3_t vec3_cross(vec3_t a, vec3_t b) {
|
|
return vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x);
|
|
}
|
|
|
|
f32 vec3_dot(vec3_t a, vec3_t b) {
|
|
return a.x * b.x + a.y * b.y + a.z * b.z;
|
|
}
|
|
|
|
vec3_t vec3_normalize(vec3_t a) {
|
|
f32 length = vec3_length(a);
|
|
return vec3(a.x / length, a.y / length, a.z / length);
|
|
}
|
|
|
|
vec3_t vec3_face_normal(vec3_t v0, vec3_t v1, vec3_t v2) {
|
|
vec3_t a = vec3_sub(v0, v1);
|
|
vec3_t b = vec3_sub(v2, v1);
|
|
|
|
vec3_t pn = vec3_cross(a, b);
|
|
return vec3_normalize(pn);
|
|
}
|
|
|
|
|
|
typedef struct {
|
|
f32 x, y;
|
|
} vec2_t;
|
|
|
|
#define vec2(X, Y) ((vec2_t){.x = X, .y = Y})
|
|
|
|
vec2_t vec2_add(vec2_t a, vec2_t b) {
|
|
return vec2(a.x + b.x, a.y + b.y);
|
|
}
|
|
|
|
vec2_t vec2_rotate(vec2_t a, f32 rad) {
|
|
return vec2(a.x * cos(rad) - a.y * sin(rad), a.x * sin(rad) + a.y * cos(rad));
|
|
}
|
|
|
|
vec2_t vec2_sub(vec2_t a, vec2_t b) {
|
|
return vec2(a.x - b.x, a.y - b.y);
|
|
}
|
|
|
|
vec2_t vec2_mul(vec2_t a, vec2_t b) {
|
|
return vec2(a.x * b.x, a.y * b.y);
|
|
}
|
|
|
|
vec2_t vec2_mulf(vec2_t a, float f) {
|
|
return vec2(a.x * f, a.y * f);
|
|
}
|
|
|
|
vec2_t vec2_div(vec2_t a, vec2_t b) {
|
|
return vec2(a.x / b.x, a.y / b.y);
|
|
}
|
|
|
|
vec2_t vec2_divf(vec2_t a, float f) {
|
|
return vec2(a.x / f, a.y / f);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Memvec
|
|
|
|
#define memvec_declare(TYPE) struct { u32 length; u32 capacity; TYPE *data; }
|
|
#define memvec_alloc(MV, c) \
|
|
MV.capacity = max(1, c); \
|
|
MV.length = 0; \
|
|
MV.data = calloc(MV.capacity, sizeof(MV.data[0]));
|
|
|
|
#define memvec_ensure_capacity(MV, CAPACITY) ( \
|
|
(CAPACITY > MV.capacity) \
|
|
? \
|
|
MV.data = realloc(MV.data, sizeof(MV.data[0]) * max(CAPACITY, MV.capacity * 2)), \
|
|
memset(MV.data + MV.capacity, 0, MV.capacity * sizeof(MV.data[0])), \
|
|
MV.capacity *= 2, 0 \
|
|
: 0)
|
|
|
|
#define memvec_get(MV, i) &MV.data[i]
|
|
#define memvec_clear(MV) MV.length = 0
|
|
#define memvec_free(MV) (MV.data ? free(MV.data), 0 : 0);
|
|
#define memvec_push(MV, ...) ( \
|
|
memvec_ensure_capacity(MV, MV.length + 1), \
|
|
MV.data[MV.length] = (__typeof__(*MV.data)) __VA_ARGS__, \
|
|
MV.length++ \
|
|
)
|
|
#define memvec_add_n(MV, n) (memvec_ensure_capacity(MV, MV.length + n), MV.length += n, &MV.data[MV.length-n])
|
|
#define memvec_add(MV) memvec_add_n(MV, 1)
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Winding
|
|
|
|
#define WINDING_MAX_VERTS 32
|
|
|
|
typedef struct {
|
|
vec3_t pos;
|
|
vec2_t uv;
|
|
} winding_vertex_t;
|
|
|
|
typedef struct {
|
|
vec3_t normal;
|
|
u32 num_vertices;
|
|
winding_vertex_t vertices[WINDING_MAX_VERTS];
|
|
} winding_t;
|
|
|
|
void winding_copy(winding_t *dst, winding_t *src) {
|
|
u32 base_size = sizeof(winding_t) - sizeof(winding_vertex_t) * WINDING_MAX_VERTS;
|
|
memcpy(dst, src, base_size + src->num_vertices * sizeof(winding_vertex_t));
|
|
}
|
|
|
|
void winding_add(winding_t *self, vec3_t pos, vec3_t normal, vec2_t uv) {
|
|
// Find duplicate
|
|
for (u32 i = 0; i < self->num_vertices; i++) {
|
|
if (vec3_equals(self->vertices[i].pos, pos)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// We may need one extra vertex when splitting!
|
|
assert(self->num_vertices < WINDING_MAX_VERTS-1);
|
|
|
|
self->normal = normal;
|
|
self->vertices[self->num_vertices++] = (winding_vertex_t){
|
|
.pos = pos,
|
|
.uv = uv
|
|
};
|
|
}
|
|
|
|
vec3_t winding_sort_center;
|
|
vec3_t winding_sort_basis;
|
|
vec3_t winding_sort_normal;
|
|
|
|
i32 winding_compare(const void *vlp, const void *vrp) {
|
|
winding_vertex_t *vl = (winding_vertex_t *)vlp;
|
|
winding_vertex_t *vr = (winding_vertex_t *)vrp;
|
|
|
|
vec3_t u = vec3_normalize(winding_sort_basis);
|
|
vec3_t v = vec3_normalize(vec3_cross(u, winding_sort_normal));
|
|
|
|
vec3_t local_lhs = vec3_sub(vl->pos, winding_sort_center);
|
|
f32 lhs_pu = vec3_dot(local_lhs, u);
|
|
f32 lhs_pv = vec3_dot(local_lhs, v);
|
|
|
|
vec3_t local_rhs = vec3_sub(vr->pos, winding_sort_center);
|
|
f32 rhs_pu = vec3_dot(local_rhs, u);
|
|
f32 rhs_pv = vec3_dot(local_rhs, v);
|
|
|
|
f32 lhs_angle = atan2(lhs_pv, lhs_pu);
|
|
f32 rhs_angle = atan2(rhs_pv, rhs_pu);
|
|
|
|
if (lhs_angle < rhs_angle) {
|
|
return 1;
|
|
}
|
|
else if (lhs_angle > rhs_angle) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void winding_sort_ccw(winding_t *self) {
|
|
winding_sort_normal = self->normal;
|
|
winding_sort_basis = vec3_sub(self->vertices[1].pos, self->vertices[0].pos);
|
|
winding_sort_center = vec3(0, 0, 0);
|
|
for (u32 i = 0; i < self->num_vertices; i++) {
|
|
winding_sort_center = vec3_add(winding_sort_center, self->vertices[i].pos);
|
|
}
|
|
winding_sort_center = vec3_divf(winding_sort_center, self->num_vertices);
|
|
|
|
qsort(self->vertices, self->num_vertices, sizeof(winding_vertex_t), winding_compare);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Interface
|
|
|
|
typedef struct {
|
|
vec3_t pos;
|
|
vec3_t normal;
|
|
vec2_t uv;
|
|
} vertex_t;
|
|
|
|
typedef struct {
|
|
vertex_t vertices[3];
|
|
} face_t;
|
|
|
|
typedef struct {
|
|
vec3_t vertices[3];
|
|
vec3_t normal;
|
|
vec2_t uv_offset;
|
|
vec2_t uv_scale;
|
|
f32 uv_rotation;
|
|
f32 dist;
|
|
string_t texture_name;
|
|
} map_plane_t;
|
|
|
|
typedef struct {
|
|
u32 planes_index;
|
|
u32 planes_length;
|
|
map_plane_t *planes;
|
|
|
|
u32 faces_index;
|
|
u32 faces_length;
|
|
face_t *faces;
|
|
} map_brush_t;
|
|
|
|
typedef struct {
|
|
u32 kvs_index;
|
|
u32 kvs_length;
|
|
|
|
u32 brushes_index;
|
|
u32 brushes_length;
|
|
|
|
u32 faces_index;
|
|
u32 faces_length;
|
|
} map_entity_t;
|
|
|
|
typedef struct {
|
|
string_t key;
|
|
string_t value;
|
|
} entity_kv_t;
|
|
|
|
typedef struct {
|
|
entity_kv_t *kvs;
|
|
u32 kvs_length;
|
|
|
|
map_brush_t *brushes;
|
|
u32 brushes_length;
|
|
|
|
face_t *faces;
|
|
u32 faces_length;
|
|
} entity_t;
|
|
|
|
string_t entity_get(entity_t *entity, string_t key) {
|
|
for (u32 i = 0; i < entity->kvs_length; i++) {
|
|
if (string_equals(key, entity->kvs[i].key)) {
|
|
return entity->kvs[i].value;
|
|
}
|
|
}
|
|
return s("");
|
|
}
|
|
|
|
typedef struct {
|
|
string_t file;
|
|
char *char_ptr;
|
|
|
|
char *error;
|
|
char *error_at;
|
|
|
|
memvec_declare(map_entity_t) map_entities;
|
|
memvec_declare(map_brush_t) brushes;
|
|
memvec_declare(map_plane_t) planes;
|
|
memvec_declare(face_t) faces;
|
|
memvec_declare(entity_kv_t) entity_kvs;
|
|
|
|
entity_t *entities;
|
|
u32 entities_length;
|
|
} map_t;
|
|
|
|
void map_delete(map_t *self);
|
|
void map_parse(map_t *self);
|
|
void map_build(map_t *self);
|
|
|
|
|
|
map_t *map_new(char *file_name, char **error, u32 *error_at) {
|
|
FILE *fh = fopen(file_name, "rb");
|
|
if (!fh) {
|
|
*error = "Couldn't open file";
|
|
*error_at = 0;
|
|
return NULL;
|
|
}
|
|
|
|
map_t *self = calloc(1, sizeof(map_t));
|
|
|
|
fseek(fh, 0, SEEK_END);
|
|
i32 size = ftell(fh);
|
|
fseek(fh, 0, SEEK_SET);
|
|
|
|
self->file = string(calloc(size+1, sizeof(char)), size);
|
|
fread(self->file.ptr, 1, size, fh);
|
|
fclose(fh);
|
|
self->file.ptr[size] = '\0';
|
|
|
|
self->char_ptr = self->file.ptr;
|
|
|
|
// Make some educated guesses of the buffer sizes we might need
|
|
memvec_alloc(self->map_entities, self->file.length / 1000);
|
|
memvec_alloc(self->brushes, self->file.length / 300);
|
|
memvec_alloc(self->planes, self->file.length / 50);
|
|
memvec_alloc(self->entity_kvs, self->map_entities.capacity * 3);
|
|
|
|
map_parse(self);
|
|
|
|
if (self->error) {
|
|
*error = self->error;
|
|
*error_at = self->error_at - self->file.ptr;
|
|
free(self);
|
|
return NULL;
|
|
}
|
|
|
|
memvec_alloc(self->faces, self->planes.length * 3);
|
|
map_build(self);
|
|
|
|
// Fill the entity_desc array for clients, now that we are sure we
|
|
// don't need to realloc any data and pointers stay fixed
|
|
self->entities_length = self->map_entities.length;
|
|
self->entities = calloc(self->entities_length, sizeof(entity_t));
|
|
for (u32 i = 0; i < self->map_entities.length; i++) {
|
|
map_entity_t *entity = memvec_get(self->map_entities, i);
|
|
|
|
self->entities[i] = (entity_t){
|
|
.kvs = (entity->kvs_length > 0)
|
|
? &self->entity_kvs.data[entity->kvs_index]
|
|
: NULL,
|
|
.kvs_length = entity->kvs_length,
|
|
.brushes = (entity->brushes_length > 0)
|
|
? &self->brushes.data[entity->brushes_index]
|
|
: NULL,
|
|
.brushes_length = entity->brushes_length,
|
|
.faces = (entity->faces_length > 0)
|
|
? &self->faces.data[entity->faces_index]
|
|
: NULL,
|
|
.faces_length = entity->faces_length,
|
|
};
|
|
|
|
for (u32 b = 0; b < entity->brushes_length; b++) {
|
|
map_brush_t *brush = &self->brushes.data[entity->brushes_index + b];
|
|
brush->faces = (brush->faces_length > 0)
|
|
? &self->faces.data[brush->faces_index]
|
|
: NULL;
|
|
brush->planes = (brush->planes_length > 0)
|
|
? &self->planes.data[brush->planes_index]
|
|
: NULL;
|
|
}
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
void map_delete(map_t *self) {
|
|
memvec_free(self->brushes);
|
|
memvec_free(self->planes);
|
|
memvec_free(self->map_entities);
|
|
memvec_free(self->entity_kvs);
|
|
memvec_free(self->faces);
|
|
free(self->entities);
|
|
free(self->file.ptr);
|
|
}
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Vertex Builder
|
|
|
|
void map_build_entity(map_t *self, map_entity_t *entity);
|
|
void map_build_brush(map_t *self, map_brush_t *brush);
|
|
void map_build_planes(map_t *self, map_brush_t *brush, u32 i0, u32 i1, u32 i2, winding_t *winding);
|
|
void map_build_faces(map_t *self, map_plane_t *plane, winding_t *winding);
|
|
void map_insert_winding(map_t *self, winding_t *winding);
|
|
|
|
void map_build(map_t *self) {
|
|
|
|
// Load textures
|
|
for (int i = 0; i < self->planes.length; i++) {
|
|
// Unused
|
|
// self->planes.data[i].texture_name
|
|
}
|
|
|
|
// Build all map_entities
|
|
for (int i = 0; i < self->map_entities.length; i++) {
|
|
map_entity_t *entity = memvec_get(self->map_entities, i);
|
|
map_build_entity(self, entity);
|
|
}
|
|
}
|
|
|
|
void map_build_entity(map_t *self, map_entity_t *entity) {
|
|
entity->faces_index = self->faces.length;
|
|
|
|
for (int i = 0; i < entity->brushes_length; i++) {
|
|
map_brush_t *brush = &self->brushes.data[entity->brushes_index + i];
|
|
map_build_brush(self, brush);
|
|
}
|
|
|
|
entity->faces_length = self->faces.length - entity->faces_index;
|
|
}
|
|
|
|
void map_build_brush(map_t *self, map_brush_t *brush) {
|
|
winding_t windings[brush->planes_length];
|
|
memset(windings, 0, sizeof(windings));
|
|
|
|
// Test all plane combinations to find intersection points
|
|
for (u32 i0 = 0; i0 < brush->planes_length; i0++) {
|
|
for (u32 i1 = i0 + 1; i1 < brush->planes_length; i1++) {
|
|
for (u32 i2 = i1 + 1; i2 < brush->planes_length; i2++) {
|
|
map_build_planes(self, brush, i0, i1, i2, windings);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort the raw vertices and fill the self->faces buffer
|
|
brush->faces_index = self->faces.length;
|
|
brush->faces_length = brush->planes_length;
|
|
for (u32 i = 0; i < brush->planes_length; i++) {
|
|
map_plane_t *plane = &self->planes.data[brush->planes_index + i];
|
|
map_build_faces(self, plane, &windings[i]);
|
|
}
|
|
}
|
|
|
|
void map_build_planes(map_t *self, map_brush_t *brush, u32 i0, u32 i1, u32 i2, winding_t *winding) {
|
|
map_plane_t *p0 = &self->planes.data[brush->planes_index + i0];
|
|
map_plane_t *p1 = &self->planes.data[brush->planes_index + i1];
|
|
map_plane_t *p2 = &self->planes.data[brush->planes_index + i2];
|
|
|
|
// Plane intersection
|
|
f32 denom = vec3_dot(vec3_cross(p0->normal, p1->normal), p2->normal);
|
|
if (equals(denom, 0)) {
|
|
return;
|
|
}
|
|
|
|
vec3_t p0d = vec3_mulf(vec3_cross(p1->normal, p2->normal), p0->dist);
|
|
vec3_t p1d = vec3_mulf(vec3_cross(p2->normal, p0->normal), p1->dist);
|
|
vec3_t p2d = vec3_mulf(vec3_cross(p0->normal, p1->normal), p2->dist);
|
|
vec3_t intersection = vec3_divf(vec3_add(vec3_add(p0d, p1d), p2d), denom);
|
|
|
|
// Make sure the produced intersection point is within the hull
|
|
for (u32 i = 0; i < brush->planes_length; i++) {
|
|
map_plane_t *plane = &self->planes.data[brush->planes_index + i];
|
|
f32 proj = vec3_dot(plane->normal, intersection);
|
|
if (proj - plane->dist > F32_COMPARE_EPSILON) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Add the intersection point to all 3 planes
|
|
winding_add(&winding[i0], intersection, p0->normal, vec2(0, 0));
|
|
winding_add(&winding[i1], intersection, p1->normal, vec2(0, 0));
|
|
winding_add(&winding[i2], intersection, p2->normal, vec2(0, 0));
|
|
}
|
|
|
|
void map_build_faces(map_t *self, map_plane_t *plane, winding_t *winding) {
|
|
// Sort vertices by winding
|
|
winding_sort_ccw(winding);
|
|
|
|
// Compute UV coords
|
|
f32 du = fabs(vec3_dot(plane->normal, vec3(0, 0, 1)));
|
|
f32 dr = fabs(vec3_dot(plane->normal, vec3(0, 1, 0)));
|
|
f32 df = fabs(vec3_dot(plane->normal, vec3(1, 0, 0)));
|
|
|
|
vec3_t axis_u, axis_v;
|
|
if (du >= dr && du >= df) { // project z axis
|
|
axis_u = vec3(1, 0, 0);
|
|
axis_v = vec3(0, 1, 0);
|
|
}
|
|
else if (dr > du && dr > df) { // project y axis
|
|
axis_u = vec3(1, 0, 0);
|
|
axis_v = vec3(0, 0, 1);
|
|
}
|
|
else { // (df >= du && df >= dr) project x axis
|
|
axis_u = vec3(0, -1, 0);
|
|
axis_v = vec3(0, 0, 1);
|
|
}
|
|
|
|
vec2_t tsize = vec2(64, 64);
|
|
for (u32 i = 0; i < winding->num_vertices; i++) {
|
|
vec3_t p = winding->vertices[i].pos;
|
|
vec2_t uv = vec2(vec3_dot(p, axis_u), vec3_dot(p, axis_v));
|
|
|
|
uv = vec2_rotate(uv, deg_to_rad(plane->uv_rotation));
|
|
uv = vec2_div(uv, plane->uv_scale);
|
|
uv = vec2_add(uv, plane->uv_offset);
|
|
uv = vec2_div(uv, tsize);
|
|
|
|
winding->vertices[i].uv = uv;
|
|
}
|
|
|
|
map_insert_winding(self, winding);
|
|
}
|
|
|
|
void map_insert_winding(map_t *self, winding_t *winding) {
|
|
for (u32 vi = 2; vi < winding->num_vertices; vi++) {
|
|
face_t *face = memvec_add(self->faces);
|
|
|
|
face->vertices[0] = (vertex_t){
|
|
.pos = winding->vertices[0].pos,
|
|
.normal = winding->normal,
|
|
.uv = winding->vertices[0].uv
|
|
};
|
|
|
|
face->vertices[1] = (vertex_t){
|
|
.pos = winding->vertices[vi-1].pos,
|
|
.normal = winding->normal,
|
|
.uv = winding->vertices[vi-1].uv
|
|
};
|
|
face->vertices[2] = (vertex_t){
|
|
.pos = winding->vertices[vi].pos,
|
|
.normal = winding->normal,
|
|
.uv = winding->vertices[vi].uv
|
|
};
|
|
}
|
|
}
|
|
|
|
// -----------------------------------------------------------------------------
|
|
// Parser
|
|
|
|
#define peek() (*self->char_ptr)
|
|
#define next() (self->char_ptr++)
|
|
#define invalid(msg) self->error = msg; self->error_at = self->char_ptr;
|
|
#define expect(c) if (peek() != c) { invalid("Unexpected char"); } else { next(); }
|
|
#define skip_whitespace()\
|
|
while (\
|
|
peek() == ' ' || peek() == '\n' || peek() == '\r' || peek() == '\t' \
|
|
) { next(); }
|
|
#define skip_until(c) while (peek() && peek() != c) { next(); }
|
|
|
|
void map_parse_entity(map_t *self);
|
|
void map_parse_brush(map_t *self, map_entity_t *entity);
|
|
void map_parse_key_value(map_t *self, map_entity_t *entity);
|
|
string_t map_parse_quoted_string(map_t *self);
|
|
void map_parse_plane(map_t *self, map_brush_t *brush);
|
|
vec3_t map_parse_vec3(map_t *self);
|
|
f32 map_parse_f32(map_t *self);
|
|
string_t map_parse_texture(map_t *self);
|
|
|
|
void map_parse(map_t *self) {
|
|
while (!self->error && peek()) {
|
|
skip_whitespace();
|
|
|
|
switch (peek()) {
|
|
case '/': skip_until('\n'); break;
|
|
case '{': map_parse_entity(self); break;
|
|
case '\0': break;
|
|
default: invalid("Unexpected char in global scope"); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void map_parse_entity(map_t *self) {
|
|
expect('{');
|
|
|
|
map_entity_t *entity = memvec_add(self->map_entities);
|
|
entity->kvs_index = self->entity_kvs.length;
|
|
entity->brushes_index = self->brushes.length;
|
|
|
|
while (!self->error && peek()) {
|
|
skip_whitespace();
|
|
|
|
switch (peek()) {
|
|
case '/': skip_until('\n'); break;
|
|
case '"': map_parse_key_value(self, entity); break;
|
|
case '{': map_parse_brush(self, entity); break;
|
|
case '}': next(); return;
|
|
default: invalid("Unexpected char in entity scope") break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void map_parse_brush(map_t *self, map_entity_t *entity) {
|
|
expect('{');
|
|
entity->brushes_length++;
|
|
|
|
map_brush_t *brush = memvec_add(self->brushes);
|
|
brush->planes_index = self->planes.length;
|
|
|
|
while (!self->error && peek()) {
|
|
skip_whitespace();
|
|
|
|
switch (peek()) {
|
|
case '(': map_parse_plane(self, brush); break;
|
|
case '}': next(); return;
|
|
default: invalid("Unexpected char in brush scope"); break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void map_parse_key_value(map_t *self, map_entity_t *entity) {
|
|
entity->kvs_length++;
|
|
|
|
entity_kv_t *kv = memvec_add(self->entity_kvs);
|
|
kv->key = map_parse_quoted_string(self);
|
|
kv->value = map_parse_quoted_string(self);
|
|
}
|
|
|
|
string_t map_parse_quoted_string(map_t *self) {
|
|
skip_whitespace();
|
|
expect('"');
|
|
|
|
char *start = self->char_ptr;
|
|
skip_until('"');
|
|
u32 length = self->char_ptr - start;
|
|
expect('"');
|
|
return string(start, length);
|
|
}
|
|
|
|
void map_parse_plane(map_t *self, map_brush_t *brush) {
|
|
brush->planes_length++;
|
|
|
|
map_plane_t *plane = memvec_add(self->planes);
|
|
|
|
plane->vertices[0] = map_parse_vec3(self);
|
|
plane->vertices[1] = map_parse_vec3(self);
|
|
plane->vertices[2] = map_parse_vec3(self);
|
|
plane->normal = vec3_face_normal(plane->vertices[0], plane->vertices[1], plane->vertices[2]);
|
|
|
|
plane->texture_name = map_parse_texture(self);
|
|
|
|
plane->uv_offset.x = map_parse_f32(self);
|
|
plane->uv_offset.y = map_parse_f32(self);
|
|
plane->uv_rotation = map_parse_f32(self);
|
|
plane->uv_scale.x = map_parse_f32(self);
|
|
plane->uv_scale.y = map_parse_f32(self);
|
|
plane->dist = vec3_dot(plane->normal, plane->vertices[1]);
|
|
}
|
|
|
|
vec3_t map_parse_vec3(map_t *self) {
|
|
skip_whitespace();
|
|
expect('(');
|
|
|
|
vec3_t v = vec3(
|
|
map_parse_f32(self),
|
|
map_parse_f32(self),
|
|
map_parse_f32(self)
|
|
);
|
|
|
|
skip_whitespace();
|
|
expect(')');
|
|
return v;
|
|
}
|
|
|
|
f32 map_parse_f32(map_t *self) {
|
|
skip_whitespace();
|
|
|
|
char *start = self->char_ptr;
|
|
while ((peek() >= '0' && peek() <= '9') || peek() == '.' || peek() == '-') {
|
|
next();
|
|
}
|
|
u32 length = self->char_ptr - start;
|
|
|
|
if (length == 0 || length > 16) {
|
|
invalid("Expected float")
|
|
return 0;
|
|
}
|
|
|
|
char buf[length+1];
|
|
memcpy((void *)buf, start, length);
|
|
buf[length] = '\0';
|
|
return atof(buf);
|
|
}
|
|
|
|
string_t map_parse_texture(map_t *self) {
|
|
skip_whitespace();
|
|
|
|
char *start = self->char_ptr;
|
|
skip_until(' ');
|
|
u32 length = self->char_ptr - start;
|
|
|
|
return string(start, length);
|
|
}
|
|
|
|
#undef peek
|
|
#undef next
|
|
#undef invalid
|
|
#undef expect
|
|
#undef skip_whitespace
|
|
#undef skip_until
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------------
|
|
|
|
#define BLOCK_RES_XZ 32
|
|
#define BLOCK_RES_Y 16
|
|
|
|
#define BLOCK_POS_MAX_XZ ((1<<8) * BLOCK_RES_XZ)
|
|
#define BLOCK_POS_MAX_Y ((1<<8) * BLOCK_RES_Y)
|
|
|
|
#define BLOCK_SIZE_MAX_XZ ((1<<8) * BLOCK_RES_XZ)
|
|
#define BLOCK_SIZE_MAX_Y ((1<<8) * BLOCK_RES_Y)
|
|
|
|
typedef struct {
|
|
u8 x, y, z;
|
|
u8 sx, sy, sz;
|
|
u8 tex;
|
|
} block_t;
|
|
|
|
typedef struct {
|
|
u8 x, y, z;
|
|
u8 sx, sy, sz;
|
|
} block_out_t;
|
|
|
|
typedef struct {
|
|
u8 sentinel;
|
|
u8 tex;
|
|
} block_texture_t;
|
|
|
|
typedef struct {
|
|
char type;
|
|
u8 x, y, z;
|
|
u8 data1, data2;
|
|
} block_entity_t;
|
|
|
|
u32 brushes_to_blocks(block_t *blocks, map_brush_t *brushes, u32 brushes_length) {
|
|
u32 blocks_length = 0;
|
|
for (u32 b = 0; b < brushes_length; b++) {
|
|
map_brush_t *brush = &brushes[b];
|
|
|
|
// Find min, max vert of this brush
|
|
vec3_t vmin = vec3(INFINITY, INFINITY, INFINITY);
|
|
vec3_t vmax = vec3(-INFINITY, -INFINITY, -INFINITY);
|
|
for (u32 f = 0; f < brush->faces_length; f++) {
|
|
face_t *face = &brush->faces[f];
|
|
|
|
for (u32 v = 0; v < 3; v++) {
|
|
vmin.x = min(vmin.x, round(face->vertices[v].pos.x));
|
|
vmin.y = min(vmin.y, round(face->vertices[v].pos.y));
|
|
vmin.z = min(vmin.z, round(face->vertices[v].pos.z));
|
|
|
|
vmax.x = max(vmax.x, round(face->vertices[v].pos.x));
|
|
vmax.y = max(vmax.y, round(face->vertices[v].pos.y));
|
|
vmax.z = max(vmax.z, round(face->vertices[v].pos.z));
|
|
}
|
|
}
|
|
|
|
vec3_t vsize = vec3_sub(vmax, vmin);
|
|
if (
|
|
(vmin.x < 0 || vmin.y < 0 || vmin.z < 0) ||
|
|
(vmin.x >= BLOCK_POS_MAX_XZ || vmin.y >= BLOCK_POS_MAX_XZ || vmin.z >= BLOCK_POS_MAX_Y) ||
|
|
(vsize.x >= BLOCK_SIZE_MAX_XZ || vsize.y >= BLOCK_SIZE_MAX_XZ || vsize.z >= BLOCK_SIZE_MAX_Y) ||
|
|
(vsize.x < BLOCK_RES_XZ || vsize.y < BLOCK_RES_XZ || vsize.z < BLOCK_RES_Y)
|
|
) {
|
|
printf(
|
|
"Brush %d has unsupported dimensions: pos(%g %g %g) size(%g %g %g)\n",
|
|
b, vmin.x, vmin.y, vmin.z, vsize.x, vsize.y, vsize.z
|
|
);
|
|
continue;
|
|
}
|
|
|
|
// This assumes all textures are name XX.png
|
|
u8 tex = atoi(string_cstring(brush->planes[0].texture_name));
|
|
|
|
// Build block, swap y<>z
|
|
block_t block = (block_t){
|
|
.x = (u8)(round(vmin.x / BLOCK_RES_XZ)),
|
|
.y = (u8)(round(vmin.z / BLOCK_RES_Y)),
|
|
.z = (u8)(round(vmin.y / BLOCK_RES_XZ)),
|
|
|
|
.sx = (u8)(round(vsize.x / BLOCK_RES_XZ)),
|
|
.sy = (u8)(round(vsize.z / BLOCK_RES_Y)),
|
|
.sz = (u8)(round(vsize.y / BLOCK_RES_XZ)),
|
|
|
|
.tex = tex
|
|
};
|
|
blocks[blocks_length++] = block;
|
|
}
|
|
return blocks_length;
|
|
}
|
|
|
|
i32 block_compare(const void *vlp, const void *vrp) {
|
|
block_t *bl = (block_t *)vlp;
|
|
block_t *br = (block_t *)vrp;
|
|
|
|
i32 lt = bl->tex;
|
|
i32 rt = br->tex;
|
|
|
|
return lt == rt ? bl->sx - br->sx : lt - rt;
|
|
}
|
|
|
|
i32 block_entity_compare(const void *vlp, const void *vrp) {
|
|
block_entity_t *bl = (block_entity_t *)vlp;
|
|
block_entity_t *br = (block_entity_t *)vrp;
|
|
|
|
return bl->type - br->type;
|
|
}
|
|
|
|
i32 main(i32 argc, char **argv) {
|
|
if (argc < 3) {
|
|
printf("Usage: ./map_packer infile.map outfile.plblocks\n");
|
|
exit(1);
|
|
}
|
|
|
|
printf("sizeof(block_out_t) = %d\n", sizeof(block_out_t));
|
|
|
|
char *error;
|
|
u32 error_at;
|
|
map_t *map = map_new(argv[1], &error, &error_at);
|
|
if (!map) {
|
|
printf("Error loading %s: %s at %d\n", argv[1], error, error_at);
|
|
exit(1);
|
|
}
|
|
|
|
printf(
|
|
"Loaded %s: %d entities, %d brushes, %d planes, %d faces\n",
|
|
argv[1],
|
|
map->entities_length, map->brushes.length,
|
|
map->planes.length, map->faces.length
|
|
);
|
|
|
|
FILE *fh = fopen(argv[2], "wb");
|
|
if (!fh) {
|
|
printf("Failed to open %s for writing\n", argv[2]);
|
|
exit(1);
|
|
}
|
|
|
|
// Find worldspawn, build brushes
|
|
for (u32 i = 0; i < map->entities_length; i++) {
|
|
entity_t *entity = &map->entities[i];
|
|
string_t classname = entity_get(entity, s("classname"));
|
|
|
|
if (!string_equals(classname, s("worldspawn"))) {
|
|
continue;
|
|
}
|
|
|
|
block_t *blocks = calloc(entity->brushes_length, sizeof(block_t));
|
|
u16 blocks_length = brushes_to_blocks(blocks, entity->brushes, entity->brushes_length);
|
|
|
|
|
|
// Sort blocks by texture index
|
|
qsort(blocks, blocks_length, sizeof(block_t), block_compare);
|
|
|
|
// Write blocks length
|
|
u16 num_textures = 0;
|
|
u8 last_texture_index = 255;
|
|
for (u32 j = 0; j < blocks_length; j++) {
|
|
if (blocks[j].tex != last_texture_index) {
|
|
last_texture_index = blocks[j].tex;
|
|
num_textures++;
|
|
}
|
|
}
|
|
|
|
u32 blocks_size = blocks_length * sizeof(block_out_t) + num_textures * sizeof(block_texture_t);
|
|
printf("%d blocks, size: %d\n", blocks_length, blocks_size);
|
|
fwrite(&blocks_size, sizeof(u16), 1, fh);
|
|
|
|
// Go through all blocks, write the block and the texture_t whenever
|
|
// the texture changes.
|
|
last_texture_index = 255;
|
|
for (u32 j = 0; j < blocks_length; j++) {
|
|
if (blocks[j].tex != last_texture_index) {
|
|
last_texture_index = blocks[j].tex;
|
|
block_texture_t bt = {255, last_texture_index};
|
|
fwrite(&bt, sizeof(block_texture_t), 1, fh);
|
|
}
|
|
block_out_t bo = {
|
|
.x = blocks[j].x,
|
|
.y = blocks[j].y,
|
|
.z = blocks[j].z,
|
|
.sx = blocks[j].sx,
|
|
.sy = blocks[j].sy,
|
|
.sz = blocks[j].sz
|
|
};
|
|
fwrite(&bo, sizeof(block_out_t), 1, fh);
|
|
}
|
|
|
|
free(blocks);
|
|
break;
|
|
}
|
|
|
|
// Gather all entities
|
|
block_entity_t *block_entities = calloc(map->entities_length, sizeof(block_entity_t));
|
|
u16 block_entities_length = 0;
|
|
for (u32 i = 0; i < map->entities_length; i++) {
|
|
entity_t *entity = &map->entities[i];
|
|
string_t classname = entity_get(entity, s("classname"));
|
|
|
|
char type;
|
|
u8 extra_data1 = 0;
|
|
u8 extra_data2 = 0;
|
|
if (string_equals(classname, s("worldspawn"))) {
|
|
continue;
|
|
}
|
|
if (string_equals(classname, s("info_player_start"))) {
|
|
type = 0;
|
|
}
|
|
else if (string_equals(classname, s("enemy_grunt"))) {
|
|
type = 1;
|
|
extra_data1 = atoi(string_cstring(entity_get(entity, s("patrol"))));
|
|
}
|
|
else if (string_equals(classname, s("enemy_enforcer"))) {
|
|
type = 2;
|
|
extra_data1 = atoi(string_cstring(entity_get(entity, s("patrol"))));
|
|
}
|
|
else if (string_equals(classname, s("enemy_ogre"))) {
|
|
type = 3;
|
|
extra_data1 = atoi(string_cstring(entity_get(entity, s("patrol"))));
|
|
}
|
|
else if (string_equals(classname, s("enemy_zombie"))) {
|
|
type = 4;
|
|
extra_data1 = atoi(string_cstring(entity_get(entity, s("patrol"))));
|
|
}
|
|
else if (string_equals(classname, s("enemy_hound"))) {
|
|
type = 5;
|
|
extra_data1 = atoi(string_cstring(entity_get(entity, s("patrol"))));
|
|
}
|
|
else if (string_equals(classname, s("pickup_nailgun"))) {
|
|
type = 6;
|
|
}
|
|
else if (string_equals(classname, s("pickup_grenadelauncher"))) {
|
|
type = 7;
|
|
}
|
|
else if (string_equals(classname, s("pickup_health"))) {
|
|
type = 8;
|
|
}
|
|
else if (string_equals(classname, s("pickup_nails"))) {
|
|
type = 9;
|
|
}
|
|
else if (string_equals(classname, s("pickup_grenades"))) {
|
|
type = 10;
|
|
}
|
|
else if (string_equals(classname, s("barrel"))) {
|
|
type = 11;
|
|
}
|
|
else if (string_equals(classname, s("light"))) {
|
|
type = 12;
|
|
extra_data1 = atoi(string_cstring(entity_get(entity, s("light"))));
|
|
|
|
// Convert 24 bit r g b string into 8 bit color value
|
|
char *color = string_cstring(entity_get(entity, s("color")));
|
|
i32 r, g, b;
|
|
sscanf(color, "%d %d %d", &r, &g, &b);
|
|
|
|
union {
|
|
struct {
|
|
u8 r: 3;
|
|
u8 g: 3;
|
|
u8 b: 2;
|
|
} rgb;
|
|
u8 v;
|
|
} rgb8 = {.rgb = {r >> 5, g >> 5, b >> 6}};
|
|
extra_data2 = rgb8.v;
|
|
}
|
|
else if (string_equals(classname, s("trigger_levelchange"))) {
|
|
type = 13;
|
|
}
|
|
else if (string_equals(classname, s("door"))) {
|
|
type = 14;
|
|
extra_data1 = atoi(string_cstring(entity_get(entity, s("texture"))));
|
|
extra_data2 = atoi(string_cstring(entity_get(entity, s("dir"))));
|
|
}
|
|
else if (string_equals(classname, s("pickup_key"))) {
|
|
type = 15;
|
|
}
|
|
else if (string_equals(classname, s("torch"))) {
|
|
type = 16;
|
|
}
|
|
else {
|
|
printf("Unknown entity %s\n", string_cstring(classname));
|
|
continue;
|
|
}
|
|
|
|
f32 x, y, z;
|
|
char *origin = string_cstring(entity_get(entity, s("origin")));
|
|
sscanf(origin, "%f %f %f", &x, &y, &z);
|
|
|
|
// Build entity, swap y<>z
|
|
block_entity_t be = {
|
|
.type = type,
|
|
.x = (u8)(round(x / BLOCK_RES_XZ)),
|
|
.y = (u8)(round(z / BLOCK_RES_Y)),
|
|
.z = (u8)(round(y / BLOCK_RES_XZ)),
|
|
.data1 = extra_data1,
|
|
.data2 = extra_data2,
|
|
};
|
|
block_entities[block_entities_length++] = be;
|
|
}
|
|
|
|
qsort(block_entities, block_entities_length, sizeof(block_entity_t), block_entity_compare);
|
|
|
|
printf("%d entities, size: %d\n", block_entities_length, block_entities_length * sizeof(block_entity_t));
|
|
fwrite(&block_entities_length, sizeof(u16), 1, fh);
|
|
fwrite(block_entities, sizeof(block_entity_t), block_entities_length, fh);
|
|
|
|
fclose(fh);
|
|
map_delete(map);
|
|
}
|