1522 lines
40 KiB
C
1522 lines
40 KiB
C
#ifndef UFBXT_TESTING_UTILS_INCLUDED
|
|
#define UFBXT_TESTING_UTILS_INCLUDED
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <math.h>
|
|
|
|
#include "testing_basics.h"
|
|
|
|
#ifndef ufbxt_soft_assert
|
|
#define ufbxt_soft_assert(cond) ufbxt_assert(cond)
|
|
#endif
|
|
|
|
// -- Vector helpers
|
|
|
|
static ufbx_real ufbxt_dot2(ufbx_vec2 a, ufbx_vec2 b)
|
|
{
|
|
return a.x*b.x + a.y*b.y;
|
|
}
|
|
|
|
static ufbx_real ufbxt_dot3(ufbx_vec3 a, ufbx_vec3 b)
|
|
{
|
|
return a.x*b.x + a.y*b.y + a.z*b.z;
|
|
}
|
|
|
|
static ufbx_real ufbxt_length2(ufbx_vec2 a)
|
|
{
|
|
return (ufbx_real)sqrt(ufbxt_dot2(a, a));
|
|
}
|
|
|
|
static ufbx_real ufbxt_length3(ufbx_vec3 a)
|
|
{
|
|
return (ufbx_real)sqrt(ufbxt_dot3(a, a));
|
|
}
|
|
|
|
static ufbx_vec2 ufbxt_add2(ufbx_vec2 a, ufbx_vec2 b)
|
|
{
|
|
ufbx_vec2 v;
|
|
v.x = a.x + b.x;
|
|
v.y = a.y + b.y;
|
|
return v;
|
|
}
|
|
|
|
static ufbx_vec3 ufbxt_add3(ufbx_vec3 a, ufbx_vec3 b)
|
|
{
|
|
ufbx_vec3 v;
|
|
v.x = a.x + b.x;
|
|
v.y = a.y + b.y;
|
|
v.z = a.z + b.z;
|
|
return v;
|
|
}
|
|
|
|
static ufbx_vec2 ufbxt_sub2(ufbx_vec2 a, ufbx_vec2 b)
|
|
{
|
|
ufbx_vec2 v;
|
|
v.x = a.x - b.x;
|
|
v.y = a.y - b.y;
|
|
return v;
|
|
}
|
|
|
|
static ufbx_vec3 ufbxt_sub3(ufbx_vec3 a, ufbx_vec3 b)
|
|
{
|
|
ufbx_vec3 v;
|
|
v.x = a.x - b.x;
|
|
v.y = a.y - b.y;
|
|
v.z = a.z - b.z;
|
|
return v;
|
|
}
|
|
|
|
static ufbx_vec2 ufbxt_mul2(ufbx_vec2 a, ufbx_real b)
|
|
{
|
|
ufbx_vec2 v;
|
|
v.x = a.x * b;
|
|
v.y = a.y * b;
|
|
return v;
|
|
}
|
|
|
|
static ufbx_vec3 ufbxt_mul3(ufbx_vec3 a, ufbx_real b)
|
|
{
|
|
ufbx_vec3 v;
|
|
v.x = a.x * b;
|
|
v.y = a.y * b;
|
|
v.z = a.z * b;
|
|
return v;
|
|
}
|
|
|
|
static ufbx_vec3 ufbxt_cross3(ufbx_vec3 a, ufbx_vec3 b)
|
|
{
|
|
ufbx_vec3 v = { a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x };
|
|
return v;
|
|
}
|
|
|
|
static ufbx_vec3 ufbxt_normalize(ufbx_vec3 a) {
|
|
ufbx_real len = (ufbx_real)sqrt(ufbxt_dot3(a, a));
|
|
if (len != 0.0) {
|
|
return ufbxt_mul3(a, (ufbx_real)1.0 / len);
|
|
} else {
|
|
ufbx_vec3 zero = { (ufbx_real)0 };
|
|
return zero;
|
|
}
|
|
}
|
|
|
|
static ufbx_real ufbxt_min(ufbx_real a, ufbx_real b) { return a < b ? a : b; }
|
|
static ufbx_real ufbxt_max(ufbx_real a, ufbx_real b) { return a < b ? b : a; }
|
|
static ufbx_real ufbxt_clamp(ufbx_real v, ufbx_real min_v, ufbx_real max_v) { return ufbxt_min(ufbxt_max(v, min_v), max_v); }
|
|
|
|
|
|
// -- obj load and diff
|
|
|
|
typedef struct {
|
|
const char **groups;
|
|
const char *original_group;
|
|
size_t num_groups;
|
|
|
|
size_t num_faces;
|
|
size_t num_indices;
|
|
|
|
ufbx_face *faces;
|
|
|
|
ufbx_vertex_vec3 vertex_position;
|
|
ufbx_vertex_vec3 vertex_normal;
|
|
ufbx_vertex_vec2 vertex_uv;
|
|
|
|
} ufbxt_obj_mesh;
|
|
|
|
typedef enum {
|
|
UFBXT_OBJ_EXPORTER_UNKNOWN,
|
|
UFBXT_OBJ_EXPORTER_BLENDER,
|
|
} ufbxt_obj_exporter;
|
|
|
|
typedef struct {
|
|
|
|
ufbxt_obj_mesh *meshes;
|
|
size_t num_meshes;
|
|
|
|
bool bad_normals;
|
|
bool bad_order;
|
|
bool bad_uvs;
|
|
bool bad_faces;
|
|
bool line_faces;
|
|
bool point_faces;
|
|
bool no_subdivision;
|
|
bool root_groups_at_bone;
|
|
bool remove_namespaces;
|
|
bool match_by_order;
|
|
ufbx_real tolerance;
|
|
int32_t animation_frame;
|
|
|
|
bool normalize_units;
|
|
bool ignore_duplicates;
|
|
|
|
ufbxt_obj_exporter exporter;
|
|
|
|
double position_scale;
|
|
|
|
char animation_name[128];
|
|
|
|
} ufbxt_obj_file;
|
|
|
|
typedef struct {
|
|
|
|
const char *name_end_chars;
|
|
|
|
} ufbxt_load_obj_opts;
|
|
|
|
static int ufbxt_cmp_obj_mesh(const void *va, const void *vb)
|
|
{
|
|
const ufbxt_obj_mesh *a = (const ufbxt_obj_mesh*)va, *b = (const ufbxt_obj_mesh*)vb;
|
|
if (a->num_groups < b->num_groups) return -1;
|
|
if (a->num_groups > b->num_groups) return +1;
|
|
return 0;
|
|
}
|
|
|
|
static uint16_t ufbxt_read_u16(const void *ptr) {
|
|
const char *p = (const char*)ptr;
|
|
return (uint16_t)(
|
|
(unsigned)(uint8_t)p[0] << 0u |
|
|
(unsigned)(uint8_t)p[1] << 8u );
|
|
}
|
|
|
|
static uint32_t ufbxt_read_u32(const void *ptr) {
|
|
const char *p = (const char*)ptr;
|
|
return (uint32_t)(
|
|
(unsigned)(uint8_t)p[0] << 0u |
|
|
(unsigned)(uint8_t)p[1] << 8u |
|
|
(unsigned)(uint8_t)p[2] << 16u |
|
|
(unsigned)(uint8_t)p[3] << 24u );
|
|
}
|
|
|
|
static ufbxt_noinline void *ufbxt_decompress_gzip(const void *gz_data, size_t gz_size, size_t *p_size)
|
|
{
|
|
const uint8_t *gz = (const uint8_t*)gz_data;
|
|
if (gz_size < 10) return NULL;
|
|
if (gz[0] != 0x1f || gz[1] != 0x8b || gz[2] != 0x08) return NULL;
|
|
uint8_t flags = gz[3];
|
|
|
|
size_t offset = 10;
|
|
|
|
if (flags & 0x4) {
|
|
// FEXTRA
|
|
if (gz_size - offset < 2) return NULL;
|
|
uint16_t xlen = ufbxt_read_u16(gz + offset);
|
|
offset += 2;
|
|
if (gz_size - offset < xlen) return NULL;
|
|
offset += xlen;
|
|
}
|
|
|
|
if (flags & 0x8) {
|
|
// FNAME
|
|
const uint8_t *end = (const uint8_t*)memchr(gz + offset, 0, gz_size - offset);
|
|
if (!end) return NULL;
|
|
offset = (size_t)(end - gz) + 1;
|
|
}
|
|
|
|
if (flags & 0x10) {
|
|
// FCOMMENT
|
|
const uint8_t *end = (const uint8_t*)memchr(gz + offset, 0, gz_size - offset);
|
|
if (!end) return NULL;
|
|
offset = (size_t)(end - gz) + 1;
|
|
}
|
|
|
|
if (flags & 0x2) {
|
|
// FHCRC
|
|
if (gz_size - offset < 2) return NULL;
|
|
offset += 2;
|
|
}
|
|
|
|
uint32_t isize = ufbxt_read_u32(gz + gz_size - 4);
|
|
|
|
void *dst = malloc(isize + 1);
|
|
if (!dst) return NULL;
|
|
|
|
ufbx_inflate_retain retain;
|
|
retain.initialized = false;
|
|
|
|
ufbx_inflate_input input = { 0 };
|
|
|
|
engine.input.data = gz + offset;
|
|
engine.input.data_size = gz_size - offset;
|
|
|
|
engine.input.total_size = engine.input.data_size;
|
|
|
|
engine.input.no_header = true;
|
|
engine.input.no_checksum = true;
|
|
|
|
ptrdiff_t result = ufbx_inflate(dst, isize, &input, &retain);
|
|
if (result != isize) {
|
|
free(dst);
|
|
return NULL;
|
|
}
|
|
|
|
((char*)dst)[isize] = '\0';
|
|
|
|
*p_size = isize;
|
|
return dst;
|
|
}
|
|
|
|
#define UFBXT_MAX_PARSED_INT
|
|
#define UFBXT_MAX_EXPONENT 325
|
|
|
|
static double ufbxt_pow10_table[UFBXT_MAX_EXPONENT*2 + 1];
|
|
static volatile bool ufbxt_parsing_init_done = false;
|
|
|
|
static void ufbxt_init_parsing()
|
|
{
|
|
if (ufbxt_parsing_init_done) return;
|
|
|
|
for (int32_t i = 0; i <= UFBXT_MAX_EXPONENT*2; i++) {
|
|
ufbxt_pow10_table[i] = pow(10.0, (double)(i - UFBXT_MAX_EXPONENT));
|
|
}
|
|
|
|
ufbxt_parsing_init_done = true;
|
|
}
|
|
|
|
static size_t ufbxt_parse_ints(int32_t *p_result, size_t max_count, const char **str)
|
|
{
|
|
const char *c = *str;
|
|
|
|
size_t num;
|
|
for (num = 0; num < max_count; num++) {
|
|
if (*c == '/') {
|
|
c++;
|
|
p_result[num] = 0;
|
|
continue;
|
|
}
|
|
|
|
int32_t sign = 1;
|
|
if (*c == '+') {
|
|
c++;
|
|
} else if (*c == '-') {
|
|
sign = -1;
|
|
c++;
|
|
}
|
|
|
|
if (!(*c >= '0' && *c <= '9')) return num;
|
|
|
|
const uint32_t max_value = UINT64_C(100000000);
|
|
uint32_t value = 0;
|
|
|
|
do {
|
|
if (value < max_value) {
|
|
value = value * 10 + (uint64_t)(*c++ - '0');
|
|
} else {
|
|
return num;
|
|
}
|
|
} while (*c >= '0' && *c <= '9');
|
|
|
|
p_result[num] = (int32_t)value * sign;
|
|
|
|
if (*c != '/') return num;
|
|
c++;
|
|
}
|
|
|
|
*str = c;
|
|
return num;
|
|
}
|
|
|
|
static bool ufbxt_parse_reals(ufbx_real *p_result, size_t count, const char *str)
|
|
{
|
|
const char *c = str;
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
// Skip separator
|
|
while (*c == ' ') c++;
|
|
|
|
const uint64_t max_value = UINT64_C(1000000000000000000);
|
|
uint64_t value = 0;
|
|
|
|
double sign = 1.0;
|
|
if (*c == '-') {
|
|
sign = -1.0;
|
|
c++;
|
|
} else if (*c == '+') {
|
|
c++;
|
|
}
|
|
|
|
int32_t exponent = 0;
|
|
|
|
if (!(*c >= '0' && *c <= '9')) return false;
|
|
|
|
do {
|
|
if (value < max_value) {
|
|
value = value * 10 + (uint64_t)(*c - '0');
|
|
} else {
|
|
exponent--;
|
|
}
|
|
c++;
|
|
} while (*c >= '0' && *c <= '9');
|
|
|
|
if (*c == '.') {
|
|
c++;
|
|
|
|
while (*c >= '0' && *c <= '9') {
|
|
if (value < max_value) {
|
|
value = value * 10 + (uint64_t)(*c - '0');
|
|
exponent--;
|
|
}
|
|
c++;
|
|
}
|
|
|
|
if ((*c | 0x20) == 'e') {
|
|
c++;
|
|
int32_t expsign = 1;
|
|
if (*c == '+') {
|
|
c++;
|
|
} else if (*c == '-') {
|
|
expsign = -1;
|
|
c++;
|
|
}
|
|
|
|
int32_t exp = 0;
|
|
while (*c >= '0' && *c <= '9') {
|
|
if (exp < 4096) {
|
|
exp = exp * 10 + (int32_t)(*c - '0');
|
|
}
|
|
c++;
|
|
}
|
|
|
|
exponent += expsign * exp;
|
|
}
|
|
}
|
|
|
|
if (exponent >= -UFBXT_MAX_EXPONENT && exponent <= UFBXT_MAX_EXPONENT) {
|
|
p_result[i] = (ufbx_real)(sign * ufbxt_pow10_table[exponent + UFBXT_MAX_EXPONENT] * (double)value);
|
|
} else if (exponent < 0) {
|
|
p_result[i] = 0.0f;
|
|
} else {
|
|
p_result[i] = (ufbx_real)INFINITY;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static char *ufbxt_find_newline(char *src)
|
|
{
|
|
for (;;) {
|
|
char c = *src;
|
|
if (c == '\0') return NULL;
|
|
if (c == '\r' || c == '\n') return src;
|
|
src++;
|
|
}
|
|
}
|
|
|
|
static bool ufbxt_is_space(char c)
|
|
{
|
|
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
|
|
}
|
|
|
|
static ufbxt_noinline ufbxt_obj_file *ufbxt_load_obj(void *obj_data, size_t obj_size, const ufbxt_load_obj_opts *opts)
|
|
{
|
|
ufbxt_load_obj_opts zero_opts;
|
|
if (!opts) {
|
|
memset(&zero_opts, 0, sizeof(zero_opts));
|
|
opts = &zero_opts;
|
|
}
|
|
|
|
ufbxt_init_parsing();
|
|
|
|
size_t num_positions = 0;
|
|
size_t num_normals = 0;
|
|
size_t num_uvs = 0;
|
|
size_t num_faces = 0;
|
|
size_t num_meshes = 0;
|
|
size_t num_indices = 0;
|
|
size_t num_groups = 0;
|
|
size_t total_name_length = 0;
|
|
|
|
bool merge_groups = false;
|
|
|
|
char *line = (char*)obj_data;
|
|
for (;;) {
|
|
char *end = ufbxt_find_newline(line);
|
|
char prev = '\0';
|
|
if (end) {
|
|
prev = *end;
|
|
*end = '\0';
|
|
}
|
|
|
|
if (!strncmp(line, "v ", 2)) num_positions++;
|
|
else if (!strncmp(line, "vt ", 3)) num_uvs++;
|
|
else if (!strncmp(line, "vn ", 3)) num_normals++;
|
|
else if (!strncmp(line, "f ", 2)) {
|
|
num_faces++;
|
|
bool prev_space = false;
|
|
for (char *c = line; *c; c++) {
|
|
bool space = *c == ' ' || *c == '\t';
|
|
if (space && !prev_space) num_indices++;
|
|
prev_space = space;
|
|
}
|
|
}
|
|
else if (!strncmp(line, "g default", 7)) { /* ignore default group */ }
|
|
else if ((!strncmp(line, "g ", 2) && !merge_groups) || !strncmp(line, "o ", 2)) {
|
|
bool prev_space = false;
|
|
num_groups++;
|
|
for (char *c = line; *c; c++) {
|
|
bool space = *c == ' ' || *c == '\t';
|
|
if (space && !prev_space) num_groups++;
|
|
if (!space) total_name_length++;
|
|
total_name_length++;
|
|
prev_space = space;
|
|
}
|
|
total_name_length++;
|
|
num_meshes++;
|
|
} else if (strstr(line, "ufbx:merge_groups")) {
|
|
merge_groups = true;
|
|
}
|
|
|
|
if (end) {
|
|
*end = prev;
|
|
line = end + 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
bool implicit_mesh = false;
|
|
if (num_meshes == 0) {
|
|
num_meshes = 1;
|
|
implicit_mesh = true;
|
|
}
|
|
|
|
total_name_length += num_groups;
|
|
|
|
size_t alloc_size = 0;
|
|
alloc_size += sizeof(ufbxt_obj_file);
|
|
alloc_size += num_meshes * sizeof(ufbxt_obj_mesh);
|
|
alloc_size += num_groups * sizeof(const char*);
|
|
alloc_size += num_positions * sizeof(ufbx_vec3);
|
|
alloc_size += num_normals * sizeof(ufbx_vec3);
|
|
alloc_size += num_uvs * sizeof(ufbx_vec2);
|
|
alloc_size += num_faces * sizeof(ufbx_face);
|
|
alloc_size += num_indices * 3 * sizeof(int32_t);
|
|
alloc_size += total_name_length * sizeof(char);
|
|
|
|
void *data = malloc(alloc_size);
|
|
ufbxt_assert(data);
|
|
|
|
ufbxt_obj_file *obj = (ufbxt_obj_file*)data;
|
|
const char **group_ptrs = (const char**)(obj + 1);
|
|
ufbxt_obj_mesh *meshes = (ufbxt_obj_mesh*)(group_ptrs + num_groups);
|
|
ufbx_vec3 *positions = (ufbx_vec3*)(meshes + num_meshes);
|
|
ufbx_vec3 *normals = (ufbx_vec3*)(positions + num_positions);
|
|
ufbx_vec2 *uvs = (ufbx_vec2*)(normals + num_normals);
|
|
ufbx_face *faces = (ufbx_face*)(uvs + num_uvs);
|
|
int32_t *position_indices = (int32_t*)(faces + num_faces);
|
|
int32_t *normal_indices = (int32_t*)(position_indices + num_indices);
|
|
int32_t *uv_indices = (int32_t*)(normal_indices + num_indices);
|
|
char *name_data = (char*)(uv_indices + num_indices);
|
|
void *data_end = name_data + total_name_length;
|
|
ufbxt_assert((char*)data_end - (char*)data == alloc_size);
|
|
|
|
memset(obj, 0, sizeof(ufbxt_obj_file));
|
|
|
|
ufbx_vec3 *dp = positions;
|
|
ufbx_vec3 *dn = normals;
|
|
ufbx_vec2 *du = uvs;
|
|
ufbxt_obj_mesh *mesh = NULL;
|
|
|
|
int32_t *dpi = position_indices;
|
|
int32_t *dni = normal_indices;
|
|
int32_t *dui = uv_indices;
|
|
|
|
ufbx_face *df = faces;
|
|
|
|
obj->meshes = meshes;
|
|
obj->num_meshes = num_meshes;
|
|
obj->tolerance = 0.001f;
|
|
obj->normalize_units = false;
|
|
obj->animation_frame = -1;
|
|
obj->exporter = UFBXT_OBJ_EXPORTER_UNKNOWN;
|
|
obj->position_scale = 1.0;
|
|
|
|
if (implicit_mesh) {
|
|
mesh = meshes;
|
|
memset(mesh, 0, sizeof(ufbxt_obj_mesh));
|
|
|
|
mesh->faces = df;
|
|
mesh->vertex_position.values.data = positions;
|
|
mesh->vertex_normal.values.data = normals;
|
|
mesh->vertex_uv.values.data = uvs;
|
|
mesh->vertex_position.indices.data = (uint32_t*)dpi;
|
|
mesh->vertex_normal.indices.data = (uint32_t*)dni;
|
|
mesh->vertex_uv.indices.data = (uint32_t*)dui;
|
|
}
|
|
|
|
line = (char*)obj_data;
|
|
for (;;) {
|
|
char *line_end = ufbxt_find_newline(line);
|
|
char prev = '\0';
|
|
if (line_end) {
|
|
prev = *line_end;
|
|
*line_end = '\0';
|
|
}
|
|
|
|
if (!strncmp(line, "v ", 2)) {
|
|
line += 2;
|
|
ufbxt_assert(ufbxt_parse_reals(dp->v, 3, line));
|
|
dp++;
|
|
} else if (!strncmp(line, "vt ", 3)) {
|
|
line += 3;
|
|
ufbxt_assert(ufbxt_parse_reals(du->v, 2, line));
|
|
du++;
|
|
} else if (!strncmp(line, "vn ", 3)) {
|
|
line += 3;
|
|
ufbxt_assert(ufbxt_parse_reals(dn->v, 3, line));
|
|
dn++;
|
|
} else if (!strncmp(line, "f ", 2)) {
|
|
ufbxt_assert(mesh);
|
|
|
|
df->index_begin = (uint32_t)mesh->num_indices;
|
|
df->num_indices = 0;
|
|
|
|
char *begin = line + 2;
|
|
do {
|
|
char *end = strchr(begin, ' ');
|
|
if (end) *end++ = '\0';
|
|
|
|
while (ufbxt_is_space(*begin)) {
|
|
begin++;
|
|
}
|
|
if (*begin == '\0') {
|
|
begin = end;
|
|
continue;
|
|
}
|
|
|
|
int32_t indices[3] = { 0, 0, 0 };
|
|
ufbxt_parse_ints(indices, 3, (const char**)&begin);
|
|
|
|
mesh->vertex_position.indices.count++;
|
|
mesh->vertex_normal.indices.count++;
|
|
mesh->vertex_uv.indices.count++;
|
|
|
|
mesh->vertex_position.values.count = (size_t)(dp - positions);
|
|
mesh->vertex_normal.values.count = (size_t)(dn - normals);
|
|
mesh->vertex_uv.values.count = (size_t)(du - uvs);
|
|
|
|
*dpi++ = indices[0] - 1;
|
|
*dni++ = indices[2] - 1;
|
|
*dui++ = indices[1] - 1;
|
|
mesh->num_indices++;
|
|
df->num_indices++;
|
|
|
|
begin = end;
|
|
} while (begin);
|
|
|
|
mesh->num_faces++;
|
|
df++;
|
|
} else if (!strncmp(line, "g default", 7)) {
|
|
/* ignore default group */
|
|
} else if ((!strncmp(line, "g ", 2) && !merge_groups) || !strncmp(line, "o ", 2)) {
|
|
mesh = mesh ? mesh + 1 : meshes;
|
|
memset(mesh, 0, sizeof(ufbxt_obj_mesh));
|
|
|
|
size_t groups_len = strlen(line + 2);
|
|
memcpy(name_data, line + 2, groups_len + 1);
|
|
mesh->original_group = name_data;
|
|
name_data += groups_len + 1;
|
|
|
|
mesh->groups = group_ptrs;
|
|
|
|
const char *c = line + 2;
|
|
for (;;) {
|
|
while (*c == ' ' || *c == '\t' || *c == '\r') {
|
|
c++;
|
|
}
|
|
|
|
if (*c == '\n' || *c == '\0') break;
|
|
|
|
const char *group_begin = c;
|
|
char *group_copy = name_data;
|
|
char *dst = group_copy;
|
|
|
|
while (*c != ' ' && *c != '\t' && *c != '\r' && *c != '\n' && *c != '\0') {
|
|
if (!strncmp(c, "FBXASC", 6)) {
|
|
c += 6;
|
|
char num[4] = { 0 };
|
|
if (*c != '\0') num[0] = *c++;
|
|
if (*c != '\0') num[1] = *c++;
|
|
if (*c != '\0') num[2] = *c++;
|
|
*dst++ = (char)atoi(num);
|
|
} else {
|
|
*dst++ = *c++;
|
|
}
|
|
}
|
|
*dst++ = '\0';
|
|
name_data = dst;
|
|
|
|
*group_ptrs++ = group_copy;
|
|
}
|
|
|
|
mesh->num_groups = group_ptrs - mesh->groups;
|
|
|
|
mesh->faces = df;
|
|
mesh->vertex_position.values.data = positions;
|
|
mesh->vertex_normal.values.data = normals;
|
|
mesh->vertex_uv.values.data = uvs;
|
|
mesh->vertex_position.indices.data = (uint32_t*)dpi;
|
|
mesh->vertex_normal.indices.data = (uint32_t*)dni;
|
|
mesh->vertex_uv.indices.data = (uint32_t*)dui;
|
|
}
|
|
|
|
if (line[0] == '#') {
|
|
line += 1;
|
|
char *end = line_end;
|
|
while (line < end && (line[0] == ' ' || line[0] == '\t')) {
|
|
line++;
|
|
}
|
|
while (end > line && (end[-1] == ' ' || end[-1] == '\t')) {
|
|
*--end = '\0';
|
|
}
|
|
if (!strcmp(line, "ufbx:bad_normals")) {
|
|
obj->bad_normals = true;
|
|
}
|
|
if (!strcmp(line, "ufbx:bad_order")) {
|
|
obj->bad_order = true;
|
|
}
|
|
if (!strcmp(line, "ufbx:bad_uvs")) {
|
|
obj->bad_uvs = true;
|
|
}
|
|
if (!strcmp(line, "ufbx:bad_faces")) {
|
|
obj->bad_faces = true;
|
|
}
|
|
if (!strcmp(line, "ufbx:line_faces")) {
|
|
obj->line_faces = true;
|
|
}
|
|
if (!strcmp(line, "ufbx:point_faces")) {
|
|
obj->point_faces = true;
|
|
}
|
|
if (!strcmp(line, "ufbx:no_subdivision")) {
|
|
obj->no_subdivision = true;
|
|
}
|
|
if (!strcmp(line, "ufbx:ignore_duplicates")) {
|
|
obj->ignore_duplicates = true;
|
|
}
|
|
if (!strcmp(line, "ufbx:root_groups_at_bone")) {
|
|
obj->root_groups_at_bone = true;
|
|
}
|
|
if (!strcmp(line, "ufbx:remove_namespaces")) {
|
|
obj->remove_namespaces = true;
|
|
}
|
|
if (!strcmp(line, "ufbx:match_by_order")) {
|
|
obj->match_by_order = true;
|
|
}
|
|
if (!strcmp(line, "www.blender.org")) {
|
|
obj->exporter = UFBXT_OBJ_EXPORTER_BLENDER;
|
|
}
|
|
if (!strncmp(line, "ufbx:animation=", 15)) {
|
|
line += 15;
|
|
size_t len = strcspn(line, "\r\n");
|
|
ufbxt_assert(len + 1 < sizeof(obj->animation_name));
|
|
memcpy(obj->animation_name, line, len);
|
|
}
|
|
double tolerance = 0.0;
|
|
if (sscanf(line, "ufbx:tolerance=%lf", &tolerance) == 1) {
|
|
obj->tolerance = (ufbx_real)tolerance;
|
|
}
|
|
double position_scale = 0.0;
|
|
if (sscanf(line, "ufbx:position_scale=%lf", &position_scale) == 1) {
|
|
obj->position_scale = (ufbx_real)position_scale;
|
|
}
|
|
int frame = 0;
|
|
if (sscanf(line, "ufbx:frame=%d", &frame) == 1) {
|
|
obj->animation_frame = (int32_t)frame;
|
|
}
|
|
}
|
|
|
|
if (line_end) {
|
|
*line_end = prev;
|
|
line = line_end + 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
qsort(obj->meshes, obj->num_meshes, sizeof(ufbxt_obj_mesh), ufbxt_cmp_obj_mesh);
|
|
|
|
return obj;
|
|
}
|
|
|
|
static ufbxt_noinline void ufbxt_debug_dump_obj_mesh(const char *file, ufbx_node *node, ufbx_mesh *mesh)
|
|
{
|
|
FILE *f = fopen(file, "wb");
|
|
ufbxt_assert(f);
|
|
|
|
fprintf(f, "s 1\n");
|
|
|
|
for (size_t i = 0; i < mesh->vertex_position.values.count; i++) {
|
|
ufbx_vec3 v = mesh->vertex_position.values.data[i];
|
|
v = ufbx_transform_position(&node->geometry_to_world, v);
|
|
fprintf(f, "v %f %f %f\n", v.x, v.y, v.z);
|
|
}
|
|
for (size_t i = 0; i < mesh->vertex_uv.values.count; i++) {
|
|
ufbx_vec2 v = mesh->vertex_uv.values.data[i];
|
|
fprintf(f, "vt %f %f\n", v.x, v.y);
|
|
}
|
|
|
|
ufbx_matrix mat = ufbx_matrix_for_normals(&node->geometry_to_world);
|
|
for (size_t i = 0; i < mesh->vertex_normal.values.count; i++) {
|
|
ufbx_vec3 v = mesh->vertex_normal.values.data[i];
|
|
v = ufbx_transform_direction(&mat, v);
|
|
fprintf(f, "vn %f %f %f\n", v.x, v.y, v.z);
|
|
}
|
|
|
|
|
|
for (size_t fi = 0; fi < mesh->num_faces; fi++) {
|
|
ufbx_face face = mesh->faces.data[fi];
|
|
fprintf(f, "f");
|
|
for (size_t ci = 0; ci < face.num_indices; ci++) {
|
|
int32_t vi = mesh->vertex_position.indices.data[face.index_begin + ci];
|
|
int32_t ti = mesh->vertex_uv.indices.data[face.index_begin + ci];
|
|
int32_t ni = mesh->vertex_normal.indices.data[face.index_begin + ci];
|
|
fprintf(f, " %d/%d/%d", vi + 1, ti + 1, ni + 1);
|
|
}
|
|
fprintf(f, "\n");
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
static ufbxt_noinline void ufbxt_debug_dump_obj_scene(const char *file, ufbx_scene *scene)
|
|
{
|
|
FILE *f = fopen(file, "wb");
|
|
ufbxt_assert(f);
|
|
|
|
for (size_t mi = 0; mi < scene->meshes.count; mi++) {
|
|
ufbx_mesh *mesh = scene->meshes.data[mi];
|
|
for (size_t ni = 0; ni < mesh->instances.count; ni++) {
|
|
ufbx_node *node = mesh->instances.data[ni];
|
|
|
|
for (size_t i = 0; i < mesh->vertex_position.values.count; i++) {
|
|
ufbx_vec3 v = mesh->skinned_position.values.data[i];
|
|
if (mesh->skinned_is_local) {
|
|
v = ufbx_transform_position(&node->geometry_to_world, v);
|
|
}
|
|
fprintf(f, "v %f %f %f\n", v.x, v.y, v.z);
|
|
}
|
|
|
|
for (size_t i = 0; i < mesh->vertex_uv.values.count; i++) {
|
|
ufbx_vec2 v = mesh->vertex_uv.values.data[i];
|
|
fprintf(f, "vt %f %f\n", v.x, v.y);
|
|
}
|
|
|
|
ufbx_matrix mat = ufbx_matrix_for_normals(&node->geometry_to_world);
|
|
for (size_t i = 0; i < mesh->skinned_normal.values.count; i++) {
|
|
ufbx_vec3 v = mesh->skinned_normal.values.data[i];
|
|
if (mesh->skinned_is_local) {
|
|
v = ufbx_transform_direction(&mat, v);
|
|
}
|
|
fprintf(f, "vn %f %f %f\n", v.x, v.y, v.z);
|
|
}
|
|
|
|
fprintf(f, "\n");
|
|
}
|
|
}
|
|
|
|
int32_t v_off = 0, t_off = 0, n_off = 0;
|
|
for (size_t mi = 0; mi < scene->meshes.count; mi++) {
|
|
ufbx_mesh *mesh = scene->meshes.data[mi];
|
|
for (size_t ni = 0; ni < mesh->instances.count; ni++) {
|
|
ufbx_node *node = mesh->instances.data[ni];
|
|
fprintf(f, "g %s\n", node->name.data);
|
|
|
|
for (size_t fi = 0; fi < mesh->num_faces; fi++) {
|
|
ufbx_face face = mesh->faces.data[fi];
|
|
fprintf(f, "f");
|
|
for (size_t ci = 0; ci < face.num_indices; ci++) {
|
|
int32_t vi = v_off + mesh->skinned_position.indices.data[face.index_begin + ci];
|
|
int32_t ni = n_off + mesh->skinned_normal.indices.data[face.index_begin + ci];
|
|
if (mesh->vertex_uv.exists) {
|
|
int32_t ti = t_off + mesh->vertex_uv.indices.data[face.index_begin + ci];
|
|
fprintf(f, " %d/%d/%d", vi + 1, ti + 1, ni + 1);
|
|
} else {
|
|
fprintf(f, " %d//%d", vi + 1, ni + 1);
|
|
}
|
|
}
|
|
fprintf(f, "\n");
|
|
}
|
|
|
|
fprintf(f, "\n");
|
|
|
|
v_off += (int32_t)mesh->skinned_position.values.count;
|
|
t_off += (int32_t)mesh->vertex_uv.values.count;
|
|
n_off += (int32_t)mesh->skinned_normal.values.count;
|
|
}
|
|
}
|
|
|
|
fclose(f);
|
|
}
|
|
|
|
typedef struct {
|
|
size_t num;
|
|
ufbx_real sum;
|
|
ufbx_real max;
|
|
} ufbxt_diff_error;
|
|
|
|
static void ufbxt_assert_close_real(ufbxt_diff_error *p_err, ufbx_real a, ufbx_real b)
|
|
{
|
|
ufbx_real err = fabs(a - b);
|
|
ufbxt_soft_assert(err < 0.001);
|
|
p_err->num++;
|
|
p_err->sum += err;
|
|
if (err > p_err->max) p_err->max = err;
|
|
}
|
|
|
|
static void ufbxt_assert_close_vec2(ufbxt_diff_error *p_err, ufbx_vec2 a, ufbx_vec2 b)
|
|
{
|
|
ufbxt_assert_close_real(p_err, a.x, b.x);
|
|
ufbxt_assert_close_real(p_err, a.y, b.y);
|
|
}
|
|
|
|
static void ufbxt_assert_close_vec3(ufbxt_diff_error *p_err, ufbx_vec3 a, ufbx_vec3 b)
|
|
{
|
|
ufbxt_assert_close_real(p_err, a.x, b.x);
|
|
ufbxt_assert_close_real(p_err, a.y, b.y);
|
|
ufbxt_assert_close_real(p_err, a.z, b.z);
|
|
}
|
|
|
|
static void ufbxt_assert_close_vec4(ufbxt_diff_error *p_err, ufbx_vec4 a, ufbx_vec4 b)
|
|
{
|
|
ufbxt_assert_close_real(p_err, a.x, b.x);
|
|
ufbxt_assert_close_real(p_err, a.y, b.y);
|
|
ufbxt_assert_close_real(p_err, a.z, b.z);
|
|
ufbxt_assert_close_real(p_err, a.w, b.w);
|
|
}
|
|
|
|
static void ufbxt_assert_close_quat(ufbxt_diff_error *p_err, ufbx_quat a, ufbx_quat b)
|
|
{
|
|
ufbxt_assert_close_real(p_err, a.x, b.x);
|
|
ufbxt_assert_close_real(p_err, a.y, b.y);
|
|
ufbxt_assert_close_real(p_err, a.z, b.z);
|
|
ufbxt_assert_close_real(p_err, a.w, b.w);
|
|
}
|
|
|
|
static void ufbxt_assert_close_matrix(ufbxt_diff_error *p_err, ufbx_matrix a, ufbx_matrix b)
|
|
{
|
|
ufbxt_assert_close_vec3(p_err, a.cols[0], b.cols[0]);
|
|
ufbxt_assert_close_vec3(p_err, a.cols[1], b.cols[1]);
|
|
ufbxt_assert_close_vec3(p_err, a.cols[2], b.cols[2]);
|
|
ufbxt_assert_close_vec3(p_err, a.cols[3], b.cols[3]);
|
|
}
|
|
|
|
static void ufbxt_assert_close_real_threshold(ufbxt_diff_error *p_err, ufbx_real a, ufbx_real b, ufbx_real threshold)
|
|
{
|
|
ufbx_real err = fabs(a - b);
|
|
ufbxt_assert(err < threshold);
|
|
p_err->num++;
|
|
p_err->sum += err;
|
|
if (err > p_err->max) p_err->max = err;
|
|
}
|
|
|
|
static void ufbxt_assert_close_vec2_threshold(ufbxt_diff_error *p_err, ufbx_vec2 a, ufbx_vec2 b, ufbx_real threshold)
|
|
{
|
|
ufbxt_assert_close_real_threshold(p_err, a.x, b.x, threshold);
|
|
ufbxt_assert_close_real_threshold(p_err, a.y, b.y, threshold);
|
|
}
|
|
|
|
static void ufbxt_assert_close_vec3_threshold(ufbxt_diff_error *p_err, ufbx_vec3 a, ufbx_vec3 b, ufbx_real threshold)
|
|
{
|
|
ufbxt_assert_close_real_threshold(p_err, a.x, b.x, threshold);
|
|
ufbxt_assert_close_real_threshold(p_err, a.y, b.y, threshold);
|
|
ufbxt_assert_close_real_threshold(p_err, a.z, b.z, threshold);
|
|
}
|
|
|
|
static void ufbxt_assert_close_vec4_threshold(ufbxt_diff_error *p_err, ufbx_vec4 a, ufbx_vec4 b, ufbx_real threshold)
|
|
{
|
|
ufbxt_assert_close_real_threshold(p_err, a.x, b.x, threshold);
|
|
ufbxt_assert_close_real_threshold(p_err, a.y, b.y, threshold);
|
|
ufbxt_assert_close_real_threshold(p_err, a.z, b.z, threshold);
|
|
ufbxt_assert_close_real_threshold(p_err, a.w, b.w, threshold);
|
|
}
|
|
|
|
static void ufbxt_assert_close_quat_threshold(ufbxt_diff_error *p_err, ufbx_quat a, ufbx_quat b, ufbx_real threshold)
|
|
{
|
|
ufbxt_assert_close_real_threshold(p_err, a.x, b.x, threshold);
|
|
ufbxt_assert_close_real_threshold(p_err, a.y, b.y, threshold);
|
|
ufbxt_assert_close_real_threshold(p_err, a.z, b.z, threshold);
|
|
ufbxt_assert_close_real_threshold(p_err, a.w, b.w, threshold);
|
|
}
|
|
|
|
static ufbxt_noinline void ufbxt_check_source_vertices(ufbx_mesh *mesh, ufbx_mesh *src_mesh, ufbxt_diff_error *p_err)
|
|
{
|
|
ufbx_subdivision_result *sub = mesh->subdivision_result;
|
|
ufbxt_assert(sub);
|
|
|
|
size_t num_vertices = mesh->num_vertices;
|
|
ufbxt_assert(sub->source_vertex_ranges.count == num_vertices);
|
|
for (size_t vi = 0; vi < num_vertices; vi++) {
|
|
ufbx_subdivision_weight_range range = sub->source_vertex_ranges.data[vi];
|
|
|
|
ufbx_vec3 sum = ufbx_zero_vec3;
|
|
for (size_t i = 0; i < range.num_weights; i++) {
|
|
ufbx_subdivision_weight weight = sub->source_vertex_weights.data[range.weight_begin + i];
|
|
ufbx_vec3 v = src_mesh->vertices.data[weight.index];
|
|
sum.x += v.x * weight.weight;
|
|
sum.y += v.y * weight.weight;
|
|
sum.z += v.z * weight.weight;
|
|
}
|
|
|
|
ufbx_vec3 ref = mesh->vertices.data[vi];
|
|
ufbxt_assert_close_vec3(p_err, ref, sum);
|
|
ref = ref;
|
|
}
|
|
}
|
|
|
|
typedef struct {
|
|
ufbx_vec3 pos;
|
|
ufbx_vec3 normal;
|
|
ufbx_vec2 uv;
|
|
} ufbxt_match_vertex;
|
|
|
|
static int ufbxt_cmp_sub_vertex(const void *va, const void *vb)
|
|
{
|
|
const ufbxt_match_vertex *a = (const ufbxt_match_vertex*)va, *b = (const ufbxt_match_vertex*)vb;
|
|
if (a->pos.x != b->pos.x) return a->pos.x < b->pos.x ? -1 : +1;
|
|
if (a->pos.y != b->pos.y) return a->pos.y < b->pos.y ? -1 : +1;
|
|
if (a->pos.z != b->pos.z) return a->pos.z < b->pos.z ? -1 : +1;
|
|
if (a->normal.x != b->normal.x) return a->normal.x < b->normal.x ? -1 : +1;
|
|
if (a->normal.y != b->normal.y) return a->normal.y < b->normal.y ? -1 : +1;
|
|
if (a->normal.z != b->normal.z) return a->normal.z < b->normal.z ? -1 : +1;
|
|
if (a->uv.x != b->uv.x) return a->uv.x < b->uv.x ? -1 : +1;
|
|
if (a->uv.y != b->uv.y) return a->uv.y < b->uv.y ? -1 : +1;
|
|
return 0;
|
|
}
|
|
|
|
static ufbxt_noinline void ufbxt_match_obj_mesh(ufbxt_obj_file *obj, ufbx_node *fbx_node, ufbx_mesh *fbx_mesh, ufbxt_obj_mesh *obj_mesh, ufbxt_diff_error *p_err, double scale)
|
|
{
|
|
ufbx_real tolerance = obj->tolerance;
|
|
|
|
ufbxt_assert(fbx_mesh->num_faces == obj_mesh->num_faces);
|
|
ufbxt_assert(fbx_mesh->num_indices == obj_mesh->num_indices);
|
|
|
|
// Check that all vertices exist, anything more doesn't really make sense
|
|
ufbxt_match_vertex *obj_verts = (ufbxt_match_vertex*)calloc(obj_mesh->num_indices, sizeof(ufbxt_match_vertex));
|
|
ufbxt_match_vertex *fbx_verts = (ufbxt_match_vertex*)calloc(fbx_mesh->num_indices, sizeof(ufbxt_match_vertex));
|
|
ufbxt_assert(obj_verts && fbx_verts);
|
|
|
|
ufbx_matrix norm_mat = ufbx_get_compatible_matrix_for_normals(fbx_node);
|
|
|
|
for (size_t i = 0; i < obj_mesh->num_indices; i++) {
|
|
obj_verts[i].pos = ufbx_get_vertex_vec3(&obj_mesh->vertex_position, i);
|
|
obj_verts[i].normal = ufbx_get_vertex_vec3(&obj_mesh->vertex_normal, i);
|
|
if (obj_mesh->vertex_uv.exists) {
|
|
obj_verts[i].uv = ufbx_get_vertex_vec2(&obj_mesh->vertex_uv, i);
|
|
}
|
|
|
|
if (scale != 1.0) {
|
|
obj_verts[i].pos.x *= scale;
|
|
obj_verts[i].pos.y *= scale;
|
|
obj_verts[i].pos.x *= scale;
|
|
}
|
|
}
|
|
for (size_t i = 0; i < fbx_mesh->num_indices; i++) {
|
|
ufbx_vec3 fp = ufbx_get_vertex_vec3(&fbx_mesh->skinned_position, i);
|
|
ufbx_vec3 fn = fbx_mesh->skinned_normal.exists ? ufbx_get_vertex_vec3(&fbx_mesh->skinned_normal, i) : ufbx_zero_vec3;
|
|
if (fbx_mesh->skinned_is_local) {
|
|
fp = ufbx_transform_position(&fbx_node->geometry_to_world, fp);
|
|
fn = ufbx_transform_direction(&norm_mat, fn);
|
|
fn = ufbxt_normalize(fn);
|
|
}
|
|
fbx_verts[i].pos = fp;
|
|
fbx_verts[i].normal = fn;
|
|
if (obj_mesh->vertex_uv.exists) {
|
|
ufbxt_assert(fbx_mesh->vertex_uv.exists);
|
|
fbx_verts[i].uv = ufbx_get_vertex_vec2(&fbx_mesh->vertex_uv, i);
|
|
}
|
|
|
|
if (scale != 1.0) {
|
|
fbx_verts[i].pos.x *= scale;
|
|
fbx_verts[i].pos.y *= scale;
|
|
fbx_verts[i].pos.x *= scale;
|
|
}
|
|
}
|
|
|
|
qsort(obj_verts, obj_mesh->num_indices, sizeof(ufbxt_match_vertex), &ufbxt_cmp_sub_vertex);
|
|
qsort(fbx_verts, fbx_mesh->num_indices, sizeof(ufbxt_match_vertex), &ufbxt_cmp_sub_vertex);
|
|
|
|
for (int32_t i = (int32_t)fbx_mesh->num_indices - 1; i >= 0; i--) {
|
|
ufbxt_match_vertex v = fbx_verts[i];
|
|
|
|
bool found = false;
|
|
for (int32_t j = i; j >= 0 && obj_verts[j].pos.x >= v.pos.x - tolerance; j--) {
|
|
ufbx_real dx = obj_verts[j].pos.x - v.pos.x;
|
|
ufbx_real dy = obj_verts[j].pos.y - v.pos.y;
|
|
ufbx_real dz = obj_verts[j].pos.z - v.pos.z;
|
|
ufbx_real dnx = obj_verts[j].normal.x - v.normal.x;
|
|
ufbx_real dny = obj_verts[j].normal.y - v.normal.y;
|
|
ufbx_real dnz = obj_verts[j].normal.z - v.normal.z;
|
|
ufbx_real du = obj_verts[j].uv.x - v.uv.x;
|
|
ufbx_real dv = obj_verts[j].uv.y - v.uv.y;
|
|
|
|
if (obj->bad_normals) {
|
|
dnx = 0.0f;
|
|
dny = 0.0f;
|
|
dnz = 0.0f;
|
|
}
|
|
|
|
if (obj->bad_uvs) {
|
|
du = 0.0f;
|
|
dv = 0.0f;
|
|
}
|
|
|
|
ufbxt_assert(dx <= tolerance);
|
|
ufbx_real err = (ufbx_real)sqrt(dx*dx + dy*dy + dz*dz + dnx*dnx + dny*dny + dnz*dnz + du*du + dv*dv);
|
|
if (err < tolerance) {
|
|
if (err > p_err->max) p_err->max = err;
|
|
p_err->sum += err;
|
|
p_err->num++;
|
|
|
|
obj_verts[j] = obj_verts[i];
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
ufbxt_assert(found);
|
|
}
|
|
|
|
free(obj_verts);
|
|
free(fbx_verts);
|
|
|
|
}
|
|
|
|
typedef struct {
|
|
const char *cat_name;
|
|
ufbx_node *node;
|
|
} ufbxt_obj_node;
|
|
|
|
static int ufbxt_cmp_string(const void *va, const void *vb)
|
|
{
|
|
const char **a = (const char**)va, **b = (const char**)vb;
|
|
return strcmp(*a, *b);
|
|
}
|
|
|
|
static int ufbxt_cmp_obj_node(const void *va, const void *vb)
|
|
{
|
|
const ufbxt_obj_node *a = (const ufbxt_obj_node*)va, *b = (const ufbxt_obj_node*)vb;
|
|
return strcmp(a->cat_name, b->cat_name);
|
|
}
|
|
|
|
static ufbxt_noinline size_t ufbxt_obj_group_key(char *cat_buf, size_t cat_cap, const char **groups, size_t num_groups, bool remove_namespaces)
|
|
{
|
|
ufbxt_assert(num_groups > 0);
|
|
qsort((void*)groups, num_groups, sizeof(const char*), &ufbxt_cmp_string);
|
|
|
|
char *cat_ptr = cat_buf, *cat_end = cat_buf + cat_cap;
|
|
for (size_t i = 0; i < num_groups; i++) {
|
|
const char *group = groups[i];
|
|
|
|
if (remove_namespaces) {
|
|
for (;;) {
|
|
const char *next = strchr(group, ':');
|
|
if (!next) break;
|
|
group = next + 1;
|
|
}
|
|
}
|
|
|
|
size_t space = (size_t)(cat_end - cat_ptr);
|
|
int res = snprintf(cat_ptr, space, "%s\n", group);
|
|
ufbxt_assert(res >= 0 && (size_t)res < space);
|
|
cat_ptr += res;
|
|
}
|
|
|
|
*--cat_ptr = '\0';
|
|
return cat_ptr - cat_buf;
|
|
}
|
|
|
|
static ufbxt_noinline bool ufbxt_face_has_duplicate_vertex(ufbx_mesh *mesh, ufbx_face face)
|
|
{
|
|
for (size_t i = 0; i < face.num_indices; i++) {
|
|
uint32_t ix = mesh->vertex_indices.data[face.index_begin + i];
|
|
for (size_t j = 0; j < i; j++) {
|
|
uint32_t jx = mesh->vertex_indices.data[face.index_begin + j];
|
|
if (ix == jx) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
enum {
|
|
UFBXT_OBJ_DIFF_FLAG_CHECK_DEFORMED_NORMALS = 0x1,
|
|
UFBXT_OBJ_DIFF_FLAG_IGNORE_NORMALS = 0x2,
|
|
};
|
|
|
|
static ufbxt_noinline void ufbxt_diff_to_obj(ufbx_scene *scene, ufbxt_obj_file *obj, ufbxt_diff_error *p_err, uint32_t flags)
|
|
{
|
|
ufbx_node **used_nodes = (ufbx_node**)malloc(obj->num_meshes * sizeof(ufbx_node*));
|
|
ufbxt_assert(used_nodes);
|
|
|
|
size_t num_used_nodes = 0;
|
|
|
|
char cat_name[4096];
|
|
const char *groups[64];
|
|
|
|
char *cat_name_data = NULL;
|
|
size_t cat_name_offset = 0;
|
|
|
|
ufbxt_obj_node *obj_nodes = (ufbxt_obj_node*)calloc(scene->nodes.count, sizeof(ufbxt_obj_node));
|
|
ufbxt_assert(obj_nodes);
|
|
|
|
size_t num_obj_nodes = 0;
|
|
|
|
for (int step = 0; step < 2; step++) {
|
|
for (size_t node_i = 0; node_i < scene->nodes.count; node_i++) {
|
|
ufbx_node *node = scene->nodes.data[node_i];
|
|
if (!node->name.length) continue;
|
|
|
|
size_t num_groups = 0;
|
|
ufbx_node *parent = node;
|
|
while (parent && !parent->is_root) {
|
|
if (obj->root_groups_at_bone && parent->attrib_type == UFBX_ELEMENT_BONE) break;
|
|
|
|
ufbxt_assert(num_groups < ufbxt_arraycount(groups));
|
|
groups[num_groups++] = parent->name.data;
|
|
parent = parent->parent;
|
|
}
|
|
|
|
if (num_groups == 0) continue;
|
|
|
|
size_t cat_len = ufbxt_obj_group_key(cat_name, sizeof(cat_name), groups, num_groups, obj->remove_namespaces);
|
|
if (cat_name_data) {
|
|
char *dst = cat_name_data + cat_name_offset;
|
|
memcpy(dst, cat_name, cat_len + 1);
|
|
|
|
ufbxt_obj_node *obj_node = &obj_nodes[num_obj_nodes++];
|
|
obj_node->cat_name = dst;
|
|
obj_node->node = node;
|
|
}
|
|
cat_name_offset += cat_len + 1;
|
|
}
|
|
|
|
if (cat_name_data) break;
|
|
|
|
cat_name_data = (char*)malloc(cat_name_offset);
|
|
ufbxt_assert(cat_name_data);
|
|
cat_name_offset = 0;
|
|
}
|
|
|
|
qsort(obj_nodes, num_obj_nodes, sizeof(ufbxt_obj_node), &ufbxt_cmp_obj_node);
|
|
|
|
for (size_t mesh_i = 0; mesh_i < obj->num_meshes; mesh_i++) {
|
|
ufbxt_obj_mesh *obj_mesh = &obj->meshes[mesh_i];
|
|
if (obj_mesh->num_indices == 0) continue;
|
|
|
|
ufbx_node *node = NULL;
|
|
|
|
if (obj->match_by_order) {
|
|
ufbxt_assert(mesh_i + 1 < scene->nodes.count);
|
|
node = scene->nodes.data[mesh_i + 1];
|
|
}
|
|
|
|
// Search for a node containing all the groups
|
|
if (!node && obj_mesh->num_groups > 0) {
|
|
ufbxt_obj_group_key(cat_name, sizeof(cat_name), obj_mesh->groups, obj_mesh->num_groups, obj->remove_namespaces);
|
|
ufbxt_obj_node key = { cat_name };
|
|
ufbxt_obj_node *found = (ufbxt_obj_node*)bsearch(&key, obj_nodes, num_obj_nodes,
|
|
sizeof(ufbxt_obj_node), &ufbxt_cmp_obj_node);
|
|
|
|
if (found) {
|
|
if (found->node && found->node->mesh) {
|
|
bool seen = false;
|
|
for (size_t i = 0; i < num_used_nodes; i++) {
|
|
if (used_nodes[i] == found->node) {
|
|
seen = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!seen) {
|
|
node = found->node;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!node && obj_mesh->original_group && *obj_mesh->original_group) {
|
|
node = ufbx_find_node(scene, obj_mesh->original_group);
|
|
}
|
|
|
|
if (!node) {
|
|
for (size_t group_i = 0; group_i < obj_mesh->num_groups; group_i++) {
|
|
const char *name = obj_mesh->groups[group_i];
|
|
|
|
node = ufbx_find_node(scene, name);
|
|
if (!node && obj->exporter == UFBXT_OBJ_EXPORTER_BLENDER) {
|
|
// Blender concatenates _Material to names
|
|
size_t name_len = strcspn(name, "_");
|
|
node = ufbx_find_node_len(scene, name, name_len);
|
|
}
|
|
|
|
if (node && node->mesh) {
|
|
bool seen = false;
|
|
for (size_t i = 0; i < num_used_nodes; i++) {
|
|
if (used_nodes[i] == node) {
|
|
seen = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!seen) break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (obj->ignore_duplicates) {
|
|
if (!node) {
|
|
bool is_ncl = false;
|
|
for (size_t i = 0; i < obj_mesh->num_groups; i++) {
|
|
if (strstr(obj_mesh->groups[i], "_ncl1_")) {
|
|
is_ncl = true;
|
|
break;
|
|
}
|
|
}
|
|
if (is_ncl) continue;
|
|
}
|
|
|
|
if (node && node->parent) {
|
|
bool duplicate_name = false;
|
|
ufbx_node *parent = node->parent;
|
|
for (size_t i = 0; i < parent->children.count; i++) {
|
|
ufbx_node *child = parent->children.data[i];
|
|
if (child == node) continue;
|
|
if (!strcmp(child->name.data, node->name.data)) {
|
|
duplicate_name = true;
|
|
break;
|
|
}
|
|
}
|
|
if (duplicate_name) continue;
|
|
}
|
|
}
|
|
|
|
if (!node) {
|
|
ufbxt_assert(scene->meshes.count == 1);
|
|
ufbxt_assert(scene->meshes.data[0]->instances.count == 1);
|
|
node = scene->meshes.data[0]->instances.data[0];
|
|
}
|
|
|
|
ufbxt_assert(node);
|
|
if (node->geometry_transform_helper) {
|
|
node = node->geometry_transform_helper;
|
|
}
|
|
|
|
ufbx_mesh *mesh = node->mesh;
|
|
|
|
double scale = 1.0;
|
|
|
|
if (obj->normalize_units && scene->settings.unit_meters != 1.0) {
|
|
scale *= scene->settings.unit_meters;
|
|
}
|
|
|
|
if (obj->position_scale != 1.0) {
|
|
scale *= obj->position_scale;
|
|
}
|
|
|
|
used_nodes[num_used_nodes++] = node;
|
|
|
|
if (!mesh && node->attrib_type == UFBX_ELEMENT_NURBS_SURFACE) {
|
|
ufbx_nurbs_surface *surface = (ufbx_nurbs_surface*)node->attrib;
|
|
ufbx_tessellate_surface_opts opts = { 0 };
|
|
opts.span_subdivision_u = surface->span_subdivision_u;
|
|
opts.span_subdivision_v = surface->span_subdivision_v;
|
|
ufbx_mesh *tess_mesh = ufbx_tessellate_nurbs_surface(surface, &opts, NULL);
|
|
ufbxt_assert(tess_mesh);
|
|
|
|
// ufbxt_debug_dump_obj_mesh("test.obj", node, tess_mesh);
|
|
|
|
ufbxt_check_mesh(scene, tess_mesh);
|
|
ufbxt_match_obj_mesh(obj, node, tess_mesh, obj_mesh, p_err, scale);
|
|
ufbx_free_mesh(tess_mesh);
|
|
|
|
continue;
|
|
}
|
|
|
|
ufbxt_assert(mesh);
|
|
|
|
// Multiple skin deformers are not supported
|
|
if (mesh->skin_deformers.count > 1) continue;
|
|
|
|
ufbx_matrix *mat = &node->geometry_to_world;
|
|
ufbx_matrix norm_mat = ufbx_get_compatible_matrix_for_normals(node);
|
|
|
|
if (!obj->no_subdivision && (mesh->subdivision_display_mode == UFBX_SUBDIVISION_DISPLAY_SMOOTH || mesh->subdivision_display_mode == UFBX_SUBDIVISION_DISPLAY_HULL_AND_SMOOTH)) {
|
|
ufbx_subdivide_opts opts = { 0 };
|
|
opts.evaluate_source_vertices = true;
|
|
opts.evaluate_skin_weights = true;
|
|
|
|
ufbx_mesh *sub_mesh = ufbx_subdivide_mesh(mesh, mesh->subdivision_preview_levels, &opts, NULL);
|
|
ufbxt_assert(sub_mesh);
|
|
|
|
// Check that we didn't break the original mesh
|
|
ufbxt_check_mesh(scene, mesh);
|
|
|
|
ufbxt_check_source_vertices(sub_mesh, mesh, p_err);
|
|
|
|
ufbxt_check_mesh(scene, sub_mesh);
|
|
ufbxt_match_obj_mesh(obj, node, sub_mesh, obj_mesh, p_err, scale);
|
|
ufbx_free_mesh(sub_mesh);
|
|
|
|
continue;
|
|
}
|
|
|
|
if (!(obj->bad_faces || obj->line_faces || obj->point_faces)) {
|
|
ufbxt_assert(obj_mesh->num_faces == mesh->num_faces);
|
|
ufbxt_assert(obj_mesh->num_indices == mesh->num_indices);
|
|
}
|
|
|
|
bool check_normals = true;
|
|
if (obj->bad_normals) check_normals = false;
|
|
if ((flags & UFBXT_OBJ_DIFF_FLAG_CHECK_DEFORMED_NORMALS) == 0 && mesh->all_deformers.count > 0) check_normals = false;
|
|
if ((flags & UFBXT_OBJ_DIFF_FLAG_IGNORE_NORMALS) != 0) check_normals = false;
|
|
|
|
if (obj->bad_order) {
|
|
ufbxt_match_obj_mesh(obj, node, mesh, obj_mesh, p_err, scale);
|
|
} else {
|
|
size_t obj_face_ix = 0;
|
|
|
|
// Assume that the indices are in the same order!
|
|
for (size_t face_ix = 0; face_ix < mesh->num_faces; face_ix++) {
|
|
ufbx_face obj_face = obj_mesh->faces[obj_face_ix];
|
|
ufbx_face face = mesh->faces.data[face_ix];
|
|
|
|
if (obj->bad_faces && ufbxt_face_has_duplicate_vertex(mesh, face)) {
|
|
continue;
|
|
} else if (obj->line_faces && face.num_indices == 2) {
|
|
continue;
|
|
} else if (obj->point_faces && face.num_indices == 1) {
|
|
continue;
|
|
} else if (!obj->bad_faces) {
|
|
ufbxt_assert(obj_face.index_begin == face.index_begin);
|
|
}
|
|
|
|
ufbxt_assert(obj_face.num_indices == face.num_indices);
|
|
obj_face_ix++;
|
|
|
|
for (size_t i = 0; i < face.num_indices; i++) {
|
|
size_t oix = obj_face.index_begin + i;
|
|
size_t fix = face.index_begin + i;
|
|
|
|
ufbx_vec3 op = ufbx_get_vertex_vec3(&obj_mesh->vertex_position, oix);
|
|
ufbx_vec3 fp = ufbx_get_vertex_vec3(&mesh->skinned_position, fix);
|
|
ufbx_vec3 on = check_normals ? ufbx_get_vertex_vec3(&obj_mesh->vertex_normal, oix) : ufbx_zero_vec3;
|
|
ufbx_vec3 fn = check_normals ? ufbx_get_vertex_vec3(&mesh->skinned_normal, fix) : ufbx_zero_vec3;
|
|
|
|
if (mesh->skinned_is_local) {
|
|
fp = ufbx_transform_position(mat, fp);
|
|
fn = ufbx_transform_direction(&norm_mat, fn);
|
|
|
|
ufbx_real fn_len = (ufbx_real)sqrt(fn.x*fn.x + fn.y*fn.y + fn.z*fn.z);
|
|
if (fn_len > 0.00001f) {
|
|
fn.x /= fn_len;
|
|
fn.y /= fn_len;
|
|
fn.z /= fn_len;
|
|
}
|
|
|
|
ufbx_real on_len = (ufbx_real)sqrt(on.x*on.x + on.y*on.y + on.z*on.z);
|
|
if (on_len > 0.00001f) {
|
|
on.x /= on_len;
|
|
on.y /= on_len;
|
|
on.z /= on_len;
|
|
}
|
|
}
|
|
|
|
if (scale != 1.0) {
|
|
fp.x *= (ufbx_real)scale;
|
|
fp.y *= (ufbx_real)scale;
|
|
fp.z *= (ufbx_real)scale;
|
|
op.x *= (ufbx_real)scale;
|
|
op.y *= (ufbx_real)scale;
|
|
op.z *= (ufbx_real)scale;
|
|
}
|
|
|
|
ufbxt_assert_close_vec3(p_err, op, fp);
|
|
|
|
if (check_normals && !mesh->generated_normals) {
|
|
ufbx_real on2 = on.x*on.x + on.y*on.y + on.z*on.z;
|
|
ufbx_real fn2 = fn.x*fn.x + fn.y*fn.y + fn.z*fn.z;
|
|
if (on2 > 0.01f && fn2 > 0.01f) {
|
|
ufbxt_assert_close_vec3(p_err, on, fn);
|
|
}
|
|
}
|
|
|
|
if (obj_mesh->vertex_uv.exists && !obj->bad_uvs) {
|
|
ufbxt_assert(mesh->vertex_uv.exists);
|
|
ufbx_vec2 ou = ufbx_get_vertex_vec2(&obj_mesh->vertex_uv, oix);
|
|
ufbx_vec2 fu = ufbx_get_vertex_vec2(&mesh->vertex_uv, fix);
|
|
ufbxt_assert_close_vec2(p_err, ou, fu);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
free(obj_nodes);
|
|
free(cat_name_data);
|
|
free(used_nodes);
|
|
}
|
|
|
|
// -- IO
|
|
|
|
static ufbxt_noinline size_t ufbxt_file_size(const char *name)
|
|
{
|
|
FILE *file = fopen(name, "rb");
|
|
if (!file) return 0;
|
|
fseek(file, 0, SEEK_END);
|
|
#if defined(_WIN32)
|
|
size_t size = (size_t)_ftelli64(file);
|
|
#else
|
|
size_t size = (size_t)ftell(file);
|
|
#endif
|
|
fclose(file);
|
|
return size;
|
|
}
|
|
|
|
static ufbxt_noinline void *ufbxt_read_file(const char *name, size_t *p_size)
|
|
{
|
|
FILE *file = fopen(name, "rb");
|
|
if (!file) return NULL;
|
|
|
|
fseek(file, 0, SEEK_END);
|
|
#if defined(_WIN32)
|
|
size_t size = (size_t)_ftelli64(file);
|
|
#else
|
|
size_t size = (size_t)ftell(file);
|
|
#endif
|
|
fseek(file, 0, SEEK_SET);
|
|
|
|
char *data = malloc(size + 1);
|
|
ufbxt_assert(data != NULL);
|
|
size_t num_read = fread(data, 1, size, file);
|
|
fclose(file);
|
|
|
|
data[size] = '\0';
|
|
|
|
if (num_read != size) {
|
|
ufbxt_assert_fail(__FILE__, __LINE__, "Failed to load file");
|
|
}
|
|
|
|
*p_size = size;
|
|
return data;
|
|
}
|
|
|
|
static ufbxt_noinline void *ufbxt_read_file_ex(const char *name, size_t *p_size)
|
|
{
|
|
size_t name_len = strlen(name);
|
|
if (name_len >= 3 && !memcmp(name + name_len - 3, ".gz", 3)) {
|
|
size_t gz_size = 0;
|
|
void *gz_data = ufbxt_read_file(name, &gz_size);
|
|
if (!gz_data) return NULL;
|
|
|
|
void *result = ufbxt_decompress_gzip(gz_data, gz_size, p_size);
|
|
free(gz_data);
|
|
|
|
return result;
|
|
} else {
|
|
return ufbxt_read_file(name, p_size);
|
|
}
|
|
}
|
|
|
|
#endif
|