1584 lines
34 KiB
C
1584 lines
34 KiB
C
|
/*
|
||
|
* fast_obj
|
||
|
*
|
||
|
* Version 1.3
|
||
|
*
|
||
|
* MIT License
|
||
|
*
|
||
|
* Copyright (c) 2018-2021 Richard Knight
|
||
|
*
|
||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||
|
* of this software and associated documentation files (the "Software"), to deal
|
||
|
* in the Software without restriction, including without limitation the rights
|
||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||
|
* copies of the Software, and to permit persons to whom the Software is
|
||
|
* furnished to do so, subject to the following conditions:
|
||
|
*
|
||
|
* The above copyright notice and this permission notice shall be included in all
|
||
|
* copies or substantial portions of the Software.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||
|
* SOFTWARE.
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#ifndef FAST_OBJ_HDR
|
||
|
#define FAST_OBJ_HDR
|
||
|
|
||
|
#define FAST_OBJ_VERSION_MAJOR 1
|
||
|
#define FAST_OBJ_VERSION_MINOR 3
|
||
|
#define FAST_OBJ_VERSION ((FAST_OBJ_VERSION_MAJOR << 8) | FAST_OBJ_VERSION_MINOR)
|
||
|
|
||
|
#include <stdlib.h>
|
||
|
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
/* Texture name from .mtl file */
|
||
|
char* name;
|
||
|
|
||
|
/* Resolved path to texture */
|
||
|
char* path;
|
||
|
|
||
|
} fastObjTexture;
|
||
|
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
/* Material name */
|
||
|
char* name;
|
||
|
|
||
|
/* Parameters */
|
||
|
float Ka[3]; /* Ambient */
|
||
|
float Kd[3]; /* Diffuse */
|
||
|
float Ks[3]; /* Specular */
|
||
|
float Ke[3]; /* Emission */
|
||
|
float Kt[3]; /* Transmittance */
|
||
|
float Ns; /* Shininess */
|
||
|
float Ni; /* Index of refraction */
|
||
|
float Tf[3]; /* Transmission filter */
|
||
|
float d; /* Disolve (alpha) */
|
||
|
int illum; /* Illumination model */
|
||
|
|
||
|
/* Set for materials that don't come from the associated mtllib */
|
||
|
int fallback;
|
||
|
|
||
|
/* Texture map indices in fastObjMesh textures array */
|
||
|
unsigned int map_Ka;
|
||
|
unsigned int map_Kd;
|
||
|
unsigned int map_Ks;
|
||
|
unsigned int map_Ke;
|
||
|
unsigned int map_Kt;
|
||
|
unsigned int map_Ns;
|
||
|
unsigned int map_Ni;
|
||
|
unsigned int map_d;
|
||
|
unsigned int map_bump;
|
||
|
|
||
|
} fastObjMaterial;
|
||
|
|
||
|
/* Allows user override to bigger indexable array */
|
||
|
#ifndef FAST_OBJ_UINT_TYPE
|
||
|
#define FAST_OBJ_UINT_TYPE unsigned int
|
||
|
#endif
|
||
|
|
||
|
typedef FAST_OBJ_UINT_TYPE fastObjUInt;
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
fastObjUInt p;
|
||
|
fastObjUInt t;
|
||
|
fastObjUInt n;
|
||
|
|
||
|
} fastObjIndex;
|
||
|
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
/* Group name */
|
||
|
char* name;
|
||
|
|
||
|
/* Number of faces */
|
||
|
unsigned int face_count;
|
||
|
|
||
|
/* First face in fastObjMesh face_* arrays */
|
||
|
unsigned int face_offset;
|
||
|
|
||
|
/* First index in fastObjMesh indices array */
|
||
|
unsigned int index_offset;
|
||
|
|
||
|
} fastObjGroup;
|
||
|
|
||
|
|
||
|
/* Note: a dummy zero-initialized value is added to the first index
|
||
|
of the positions, texcoords, normals and textures arrays. Hence,
|
||
|
valid indices into these arrays start from 1, with an index of 0
|
||
|
indicating that the attribute is not present. */
|
||
|
typedef struct
|
||
|
{
|
||
|
/* Vertex data */
|
||
|
unsigned int position_count;
|
||
|
float* positions;
|
||
|
|
||
|
unsigned int texcoord_count;
|
||
|
float* texcoords;
|
||
|
|
||
|
unsigned int normal_count;
|
||
|
float* normals;
|
||
|
|
||
|
unsigned int color_count;
|
||
|
float* colors;
|
||
|
|
||
|
/* Face data: one element for each face */
|
||
|
unsigned int face_count;
|
||
|
unsigned int* face_vertices;
|
||
|
unsigned int* face_materials;
|
||
|
|
||
|
/* Index data: one element for each face vertex */
|
||
|
unsigned int index_count;
|
||
|
fastObjIndex* indices;
|
||
|
|
||
|
/* Materials */
|
||
|
unsigned int material_count;
|
||
|
fastObjMaterial* materials;
|
||
|
|
||
|
/* Texture maps */
|
||
|
unsigned int texture_count;
|
||
|
fastObjTexture* textures;
|
||
|
|
||
|
/* Mesh objects ('o' tag in .obj file) */
|
||
|
unsigned int object_count;
|
||
|
fastObjGroup* objects;
|
||
|
|
||
|
/* Mesh groups ('g' tag in .obj file) */
|
||
|
unsigned int group_count;
|
||
|
fastObjGroup* groups;
|
||
|
|
||
|
} fastObjMesh;
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
void* (*file_open)(const char* path, void* user_data);
|
||
|
void (*file_close)(void* file, void* user_data);
|
||
|
size_t (*file_read)(void* file, void* dst, size_t bytes, void* user_data);
|
||
|
unsigned long (*file_size)(void* file, void* user_data);
|
||
|
} fastObjCallbacks;
|
||
|
|
||
|
#ifdef __cplusplus
|
||
|
extern "C" {
|
||
|
#endif
|
||
|
|
||
|
fastObjMesh* fast_obj_read(const char* path);
|
||
|
fastObjMesh* fast_obj_read_with_callbacks(const char* path, const fastObjCallbacks* callbacks, void* user_data);
|
||
|
void fast_obj_destroy(fastObjMesh* mesh);
|
||
|
|
||
|
#ifdef __cplusplus
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
#endif
|
||
|
|
||
|
|
||
|
#ifdef FAST_OBJ_IMPLEMENTATION
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#ifndef FAST_OBJ_REALLOC
|
||
|
#define FAST_OBJ_REALLOC realloc
|
||
|
#endif
|
||
|
|
||
|
#ifndef FAST_OBJ_FREE
|
||
|
#define FAST_OBJ_FREE free
|
||
|
#endif
|
||
|
|
||
|
#ifdef _WIN32
|
||
|
#define FAST_OBJ_SEPARATOR '\\'
|
||
|
#define FAST_OBJ_OTHER_SEP '/'
|
||
|
#else
|
||
|
#define FAST_OBJ_SEPARATOR '/'
|
||
|
#define FAST_OBJ_OTHER_SEP '\\'
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/* Size of buffer to read into */
|
||
|
#define BUFFER_SIZE 65536
|
||
|
|
||
|
/* Max supported power when parsing float */
|
||
|
#define MAX_POWER 20
|
||
|
|
||
|
typedef struct
|
||
|
{
|
||
|
/* Final mesh */
|
||
|
fastObjMesh* mesh;
|
||
|
|
||
|
/* Current object/group */
|
||
|
fastObjGroup object;
|
||
|
fastObjGroup group;
|
||
|
|
||
|
/* Current material index */
|
||
|
unsigned int material;
|
||
|
|
||
|
/* Current line in file */
|
||
|
unsigned int line;
|
||
|
|
||
|
/* Base path for materials/textures */
|
||
|
char* base;
|
||
|
|
||
|
} fastObjData;
|
||
|
|
||
|
|
||
|
static const
|
||
|
double POWER_10_POS[MAX_POWER] =
|
||
|
{
|
||
|
1.0e0, 1.0e1, 1.0e2, 1.0e3, 1.0e4, 1.0e5, 1.0e6, 1.0e7, 1.0e8, 1.0e9,
|
||
|
1.0e10, 1.0e11, 1.0e12, 1.0e13, 1.0e14, 1.0e15, 1.0e16, 1.0e17, 1.0e18, 1.0e19,
|
||
|
};
|
||
|
|
||
|
static const
|
||
|
double POWER_10_NEG[MAX_POWER] =
|
||
|
{
|
||
|
1.0e0, 1.0e-1, 1.0e-2, 1.0e-3, 1.0e-4, 1.0e-5, 1.0e-6, 1.0e-7, 1.0e-8, 1.0e-9,
|
||
|
1.0e-10, 1.0e-11, 1.0e-12, 1.0e-13, 1.0e-14, 1.0e-15, 1.0e-16, 1.0e-17, 1.0e-18, 1.0e-19,
|
||
|
};
|
||
|
|
||
|
|
||
|
static void* memory_realloc(void* ptr, size_t bytes)
|
||
|
{
|
||
|
return FAST_OBJ_REALLOC(ptr, bytes);
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
void memory_dealloc(void* ptr)
|
||
|
{
|
||
|
FAST_OBJ_FREE(ptr);
|
||
|
}
|
||
|
|
||
|
|
||
|
#define array_clean(_arr) ((_arr) ? memory_dealloc(_array_header(_arr)), 0 : 0)
|
||
|
#define array_push(_arr, _val) (_array_mgrow(_arr, 1) ? ((_arr)[_array_size(_arr)++] = (_val), _array_size(_arr) - 1) : 0)
|
||
|
#define array_size(_arr) ((_arr) ? _array_size(_arr) : 0)
|
||
|
#define array_capacity(_arr) ((_arr) ? _array_capacity(_arr) : 0)
|
||
|
#define array_empty(_arr) (array_size(_arr) == 0)
|
||
|
|
||
|
#define _array_header(_arr) ((fastObjUInt*)(_arr)-2)
|
||
|
#define _array_size(_arr) (_array_header(_arr)[0])
|
||
|
#define _array_capacity(_arr) (_array_header(_arr)[1])
|
||
|
#define _array_ngrow(_arr, _n) ((_arr) == 0 || (_array_size(_arr) + (_n) >= _array_capacity(_arr)))
|
||
|
#define _array_mgrow(_arr, _n) (_array_ngrow(_arr, _n) ? (_array_grow(_arr, _n) != 0) : 1)
|
||
|
#define _array_grow(_arr, _n) (*((void**)&(_arr)) = array_realloc(_arr, _n, sizeof(*(_arr))))
|
||
|
|
||
|
|
||
|
static void* array_realloc(void* ptr, fastObjUInt n, fastObjUInt b)
|
||
|
{
|
||
|
fastObjUInt sz = array_size(ptr);
|
||
|
fastObjUInt nsz = sz + n;
|
||
|
fastObjUInt cap = array_capacity(ptr);
|
||
|
fastObjUInt ncap = cap + cap / 2;
|
||
|
fastObjUInt* r;
|
||
|
|
||
|
if (ncap < nsz)
|
||
|
ncap = nsz;
|
||
|
ncap = (ncap + 15) & ~15u;
|
||
|
|
||
|
r = (fastObjUInt*)(memory_realloc(ptr ? _array_header(ptr) : 0, (size_t)b * ncap + 2 * sizeof(fastObjUInt)));
|
||
|
if (!r)
|
||
|
return 0;
|
||
|
|
||
|
r[0] = sz;
|
||
|
r[1] = ncap;
|
||
|
|
||
|
return (r + 2);
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
void* file_open(const char* path, void* user_data)
|
||
|
{
|
||
|
(void)(user_data);
|
||
|
return fopen(path, "rb");
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
void file_close(void* file, void* user_data)
|
||
|
{
|
||
|
FILE* f;
|
||
|
(void)(user_data);
|
||
|
|
||
|
f = (FILE*)(file);
|
||
|
fclose(f);
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
size_t file_read(void* file, void* dst, size_t bytes, void* user_data)
|
||
|
{
|
||
|
FILE* f;
|
||
|
(void)(user_data);
|
||
|
|
||
|
f = (FILE*)(file);
|
||
|
return fread(dst, 1, bytes, f);
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
unsigned long file_size(void* file, void* user_data)
|
||
|
{
|
||
|
FILE* f;
|
||
|
long p;
|
||
|
long n;
|
||
|
(void)(user_data);
|
||
|
|
||
|
f = (FILE*)(file);
|
||
|
|
||
|
p = ftell(f);
|
||
|
fseek(f, 0, SEEK_END);
|
||
|
n = ftell(f);
|
||
|
fseek(f, p, SEEK_SET);
|
||
|
|
||
|
if (n > 0)
|
||
|
return (unsigned long)(n);
|
||
|
else
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
char* string_copy(const char* s, const char* e)
|
||
|
{
|
||
|
size_t n;
|
||
|
char* p;
|
||
|
|
||
|
n = (size_t)(e - s);
|
||
|
p = (char*)(memory_realloc(0, n + 1));
|
||
|
if (p)
|
||
|
{
|
||
|
memcpy(p, s, n);
|
||
|
p[n] = '\0';
|
||
|
}
|
||
|
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
char* string_substr(const char* s, size_t a, size_t b)
|
||
|
{
|
||
|
return string_copy(s + a, s + b);
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
char* string_concat(const char* a, const char* s, const char* e)
|
||
|
{
|
||
|
size_t an;
|
||
|
size_t sn;
|
||
|
char* p;
|
||
|
|
||
|
an = a ? strlen(a) : 0;
|
||
|
sn = (size_t)(e - s);
|
||
|
p = (char*)(memory_realloc(0, an + sn + 1));
|
||
|
if (p)
|
||
|
{
|
||
|
if (a)
|
||
|
memcpy(p, a, an);
|
||
|
memcpy(p + an, s, sn);
|
||
|
p[an + sn] = '\0';
|
||
|
}
|
||
|
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
int string_equal(const char* a, const char* s, const char* e)
|
||
|
{
|
||
|
size_t an = strlen(a);
|
||
|
size_t sn = (size_t)(e - s);
|
||
|
|
||
|
return an == sn && memcmp(a, s, an) == 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
void string_fix_separators(char* s)
|
||
|
{
|
||
|
while (*s)
|
||
|
{
|
||
|
if (*s == FAST_OBJ_OTHER_SEP)
|
||
|
*s = FAST_OBJ_SEPARATOR;
|
||
|
s++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
int is_whitespace(char c)
|
||
|
{
|
||
|
return (c == ' ' || c == '\t' || c == '\r');
|
||
|
}
|
||
|
|
||
|
static
|
||
|
int is_newline(char c)
|
||
|
{
|
||
|
return (c == '\n');
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
int is_digit(char c)
|
||
|
{
|
||
|
return (c >= '0' && c <= '9');
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
int is_exponent(char c)
|
||
|
{
|
||
|
return (c == 'e' || c == 'E');
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
const char* skip_name(const char* ptr)
|
||
|
{
|
||
|
const char* s = ptr;
|
||
|
|
||
|
while (!is_newline(*ptr))
|
||
|
ptr++;
|
||
|
|
||
|
while (ptr > s && is_whitespace(*(ptr - 1)))
|
||
|
ptr--;
|
||
|
|
||
|
return ptr;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
const char* skip_whitespace(const char* ptr)
|
||
|
{
|
||
|
while (is_whitespace(*ptr))
|
||
|
ptr++;
|
||
|
|
||
|
return ptr;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
const char* skip_line(const char* ptr)
|
||
|
{
|
||
|
while (!is_newline(*ptr++))
|
||
|
;
|
||
|
|
||
|
return ptr;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
fastObjGroup object_default(void)
|
||
|
{
|
||
|
fastObjGroup object;
|
||
|
|
||
|
object.name = 0;
|
||
|
object.face_count = 0;
|
||
|
object.face_offset = 0;
|
||
|
object.index_offset = 0;
|
||
|
|
||
|
return object;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
void object_clean(fastObjGroup* object)
|
||
|
{
|
||
|
memory_dealloc(object->name);
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
void flush_object(fastObjData* data)
|
||
|
{
|
||
|
/* Add object if not empty */
|
||
|
if (data->object.face_count > 0)
|
||
|
array_push(data->mesh->objects, data->object);
|
||
|
else
|
||
|
object_clean(&data->object);
|
||
|
|
||
|
/* Reset for more data */
|
||
|
data->object = object_default();
|
||
|
data->object.face_offset = array_size(data->mesh->face_vertices);
|
||
|
data->object.index_offset = array_size(data->mesh->indices);
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
static
|
||
|
fastObjGroup group_default(void)
|
||
|
{
|
||
|
fastObjGroup group;
|
||
|
|
||
|
group.name = 0;
|
||
|
group.face_count = 0;
|
||
|
group.face_offset = 0;
|
||
|
group.index_offset = 0;
|
||
|
|
||
|
return group;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
void group_clean(fastObjGroup* group)
|
||
|
{
|
||
|
memory_dealloc(group->name);
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
void flush_group(fastObjData* data)
|
||
|
{
|
||
|
/* Add group if not empty */
|
||
|
if (data->group.face_count > 0)
|
||
|
array_push(data->mesh->groups, data->group);
|
||
|
else
|
||
|
group_clean(&data->group);
|
||
|
|
||
|
/* Reset for more data */
|
||
|
data->group = group_default();
|
||
|
data->group.face_offset = array_size(data->mesh->face_vertices);
|
||
|
data->group.index_offset = array_size(data->mesh->indices);
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
const char* parse_int(const char* ptr, int* val)
|
||
|
{
|
||
|
int sign;
|
||
|
int num;
|
||
|
|
||
|
|
||
|
if (*ptr == '-')
|
||
|
{
|
||
|
sign = -1;
|
||
|
ptr++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
sign = +1;
|
||
|
}
|
||
|
|
||
|
num = 0;
|
||
|
while (is_digit(*ptr))
|
||
|
num = 10 * num + (*ptr++ - '0');
|
||
|
|
||
|
*val = sign * num;
|
||
|
|
||
|
return ptr;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
const char* parse_float(const char* ptr, float* val)
|
||
|
{
|
||
|
double sign;
|
||
|
double num;
|
||
|
double fra;
|
||
|
double div;
|
||
|
unsigned int eval;
|
||
|
const double* powers;
|
||
|
|
||
|
|
||
|
ptr = skip_whitespace(ptr);
|
||
|
|
||
|
switch (*ptr)
|
||
|
{
|
||
|
case '+':
|
||
|
sign = 1.0;
|
||
|
ptr++;
|
||
|
break;
|
||
|
|
||
|
case '-':
|
||
|
sign = -1.0;
|
||
|
ptr++;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
sign = 1.0;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
|
||
|
num = 0.0;
|
||
|
while (is_digit(*ptr))
|
||
|
num = 10.0 * num + (double)(*ptr++ - '0');
|
||
|
|
||
|
if (*ptr == '.')
|
||
|
ptr++;
|
||
|
|
||
|
fra = 0.0;
|
||
|
div = 1.0;
|
||
|
|
||
|
while (is_digit(*ptr))
|
||
|
{
|
||
|
fra = 10.0 * fra + (double)(*ptr++ - '0');
|
||
|
div *= 10.0;
|
||
|
}
|
||
|
|
||
|
num += fra / div;
|
||
|
|
||
|
if (is_exponent(*ptr))
|
||
|
{
|
||
|
ptr++;
|
||
|
|
||
|
switch (*ptr)
|
||
|
{
|
||
|
case '+':
|
||
|
powers = POWER_10_POS;
|
||
|
ptr++;
|
||
|
break;
|
||
|
|
||
|
case '-':
|
||
|
powers = POWER_10_NEG;
|
||
|
ptr++;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
powers = POWER_10_POS;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
eval = 0;
|
||
|
while (is_digit(*ptr))
|
||
|
eval = 10 * eval + (*ptr++ - '0');
|
||
|
|
||
|
num *= (eval >= MAX_POWER) ? 0.0 : powers[eval];
|
||
|
}
|
||
|
|
||
|
*val = (float)(sign * num);
|
||
|
|
||
|
return ptr;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
const char* parse_vertex(fastObjData* data, const char* ptr)
|
||
|
{
|
||
|
unsigned int ii;
|
||
|
float v;
|
||
|
|
||
|
|
||
|
for (ii = 0; ii < 3; ii++)
|
||
|
{
|
||
|
ptr = parse_float(ptr, &v);
|
||
|
array_push(data->mesh->positions, v);
|
||
|
}
|
||
|
|
||
|
|
||
|
ptr = skip_whitespace(ptr);
|
||
|
if (!is_newline(*ptr))
|
||
|
{
|
||
|
/* Fill the colors array until it matches the size of the positions array */
|
||
|
for (ii = array_size(data->mesh->colors); ii < array_size(data->mesh->positions) - 3; ++ii)
|
||
|
{
|
||
|
array_push(data->mesh->colors, 1.0f);
|
||
|
}
|
||
|
|
||
|
for (ii = 0; ii < 3; ++ii)
|
||
|
{
|
||
|
ptr = parse_float(ptr, &v);
|
||
|
array_push(data->mesh->colors, v);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return ptr;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
const char* parse_texcoord(fastObjData* data, const char* ptr)
|
||
|
{
|
||
|
unsigned int ii;
|
||
|
float v;
|
||
|
|
||
|
|
||
|
for (ii = 0; ii < 2; ii++)
|
||
|
{
|
||
|
ptr = parse_float(ptr, &v);
|
||
|
array_push(data->mesh->texcoords, v);
|
||
|
}
|
||
|
|
||
|
return ptr;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
const char* parse_normal(fastObjData* data, const char* ptr)
|
||
|
{
|
||
|
unsigned int ii;
|
||
|
float v;
|
||
|
|
||
|
|
||
|
for (ii = 0; ii < 3; ii++)
|
||
|
{
|
||
|
ptr = parse_float(ptr, &v);
|
||
|
array_push(data->mesh->normals, v);
|
||
|
}
|
||
|
|
||
|
return ptr;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
const char* parse_face(fastObjData* data, const char* ptr)
|
||
|
{
|
||
|
unsigned int count;
|
||
|
fastObjIndex vn;
|
||
|
int v;
|
||
|
int t;
|
||
|
int n;
|
||
|
|
||
|
|
||
|
ptr = skip_whitespace(ptr);
|
||
|
|
||
|
count = 0;
|
||
|
while (!is_newline(*ptr))
|
||
|
{
|
||
|
v = 0;
|
||
|
t = 0;
|
||
|
n = 0;
|
||
|
|
||
|
ptr = parse_int(ptr, &v);
|
||
|
if (*ptr == '/')
|
||
|
{
|
||
|
ptr++;
|
||
|
if (*ptr != '/')
|
||
|
ptr = parse_int(ptr, &t);
|
||
|
|
||
|
if (*ptr == '/')
|
||
|
{
|
||
|
ptr++;
|
||
|
ptr = parse_int(ptr, &n);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (v < 0)
|
||
|
vn.p = (array_size(data->mesh->positions) / 3) - (fastObjUInt)(-v);
|
||
|
else if (v > 0)
|
||
|
vn.p = (fastObjUInt)(v);
|
||
|
else
|
||
|
return ptr; /* Skip lines with no valid vertex index */
|
||
|
|
||
|
if (t < 0)
|
||
|
vn.t = (array_size(data->mesh->texcoords) / 2) - (fastObjUInt)(-t);
|
||
|
else if (t > 0)
|
||
|
vn.t = (fastObjUInt)(t);
|
||
|
else
|
||
|
vn.t = 0;
|
||
|
|
||
|
if (n < 0)
|
||
|
vn.n = (array_size(data->mesh->normals) / 3) - (fastObjUInt)(-n);
|
||
|
else if (n > 0)
|
||
|
vn.n = (fastObjUInt)(n);
|
||
|
else
|
||
|
vn.n = 0;
|
||
|
|
||
|
array_push(data->mesh->indices, vn);
|
||
|
count++;
|
||
|
|
||
|
ptr = skip_whitespace(ptr);
|
||
|
}
|
||
|
|
||
|
array_push(data->mesh->face_vertices, count);
|
||
|
array_push(data->mesh->face_materials, data->material);
|
||
|
|
||
|
data->group.face_count++;
|
||
|
data->object.face_count++;
|
||
|
|
||
|
return ptr;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
const char* parse_object(fastObjData* data, const char* ptr)
|
||
|
{
|
||
|
const char* s;
|
||
|
const char* e;
|
||
|
|
||
|
|
||
|
ptr = skip_whitespace(ptr);
|
||
|
|
||
|
s = ptr;
|
||
|
ptr = skip_name(ptr);
|
||
|
e = ptr;
|
||
|
|
||
|
flush_object(data);
|
||
|
data->object.name = string_copy(s, e);
|
||
|
|
||
|
return ptr;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
const char* parse_group(fastObjData* data, const char* ptr)
|
||
|
{
|
||
|
const char* s;
|
||
|
const char* e;
|
||
|
|
||
|
|
||
|
ptr = skip_whitespace(ptr);
|
||
|
|
||
|
s = ptr;
|
||
|
ptr = skip_name(ptr);
|
||
|
e = ptr;
|
||
|
|
||
|
flush_group(data);
|
||
|
data->group.name = string_copy(s, e);
|
||
|
|
||
|
return ptr;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
fastObjTexture map_default(void)
|
||
|
{
|
||
|
fastObjTexture map;
|
||
|
|
||
|
map.name = 0;
|
||
|
map.path = 0;
|
||
|
|
||
|
return map;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
fastObjMaterial mtl_default(void)
|
||
|
{
|
||
|
fastObjMaterial mtl;
|
||
|
|
||
|
mtl.name = 0;
|
||
|
|
||
|
mtl.Ka[0] = 0.0;
|
||
|
mtl.Ka[1] = 0.0;
|
||
|
mtl.Ka[2] = 0.0;
|
||
|
mtl.Kd[0] = 1.0;
|
||
|
mtl.Kd[1] = 1.0;
|
||
|
mtl.Kd[2] = 1.0;
|
||
|
mtl.Ks[0] = 0.0;
|
||
|
mtl.Ks[1] = 0.0;
|
||
|
mtl.Ks[2] = 0.0;
|
||
|
mtl.Ke[0] = 0.0;
|
||
|
mtl.Ke[1] = 0.0;
|
||
|
mtl.Ke[2] = 0.0;
|
||
|
mtl.Kt[0] = 0.0;
|
||
|
mtl.Kt[1] = 0.0;
|
||
|
mtl.Kt[2] = 0.0;
|
||
|
mtl.Ns = 1.0;
|
||
|
mtl.Ni = 1.0;
|
||
|
mtl.Tf[0] = 1.0;
|
||
|
mtl.Tf[1] = 1.0;
|
||
|
mtl.Tf[2] = 1.0;
|
||
|
mtl.d = 1.0;
|
||
|
mtl.illum = 1;
|
||
|
|
||
|
mtl.fallback = 0;
|
||
|
|
||
|
mtl.map_Ka = 0;
|
||
|
mtl.map_Kd = 0;
|
||
|
mtl.map_Ks = 0;
|
||
|
mtl.map_Ke = 0;
|
||
|
mtl.map_Kt = 0;
|
||
|
mtl.map_Ns = 0;
|
||
|
mtl.map_Ni = 0;
|
||
|
mtl.map_d = 0;
|
||
|
mtl.map_bump = 0;
|
||
|
|
||
|
return mtl;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
const char* parse_usemtl(fastObjData* data, const char* ptr)
|
||
|
{
|
||
|
const char* s;
|
||
|
const char* e;
|
||
|
unsigned int idx;
|
||
|
fastObjMaterial* mtl;
|
||
|
|
||
|
|
||
|
ptr = skip_whitespace(ptr);
|
||
|
|
||
|
/* Parse the material name */
|
||
|
s = ptr;
|
||
|
ptr = skip_name(ptr);
|
||
|
e = ptr;
|
||
|
|
||
|
/* Find an existing material with the same name */
|
||
|
idx = 0;
|
||
|
while (idx < array_size(data->mesh->materials))
|
||
|
{
|
||
|
mtl = &data->mesh->materials[idx];
|
||
|
if (mtl->name && string_equal(mtl->name, s, e))
|
||
|
break;
|
||
|
|
||
|
idx++;
|
||
|
}
|
||
|
|
||
|
/* If doesn't exist, create a default one with this name
|
||
|
Note: this case happens when OBJ doesn't have its MTL */
|
||
|
if (idx == array_size(data->mesh->materials))
|
||
|
{
|
||
|
fastObjMaterial new_mtl = mtl_default();
|
||
|
new_mtl.name = string_copy(s, e);
|
||
|
new_mtl.fallback = 1;
|
||
|
array_push(data->mesh->materials, new_mtl);
|
||
|
}
|
||
|
|
||
|
data->material = idx;
|
||
|
|
||
|
return ptr;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
void map_clean(fastObjTexture* map)
|
||
|
{
|
||
|
memory_dealloc(map->name);
|
||
|
memory_dealloc(map->path);
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
void mtl_clean(fastObjMaterial* mtl)
|
||
|
{
|
||
|
memory_dealloc(mtl->name);
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
const char* read_mtl_int(const char* p, int* v)
|
||
|
{
|
||
|
return parse_int(p, v);
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
const char* read_mtl_single(const char* p, float* v)
|
||
|
{
|
||
|
return parse_float(p, v);
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
const char* read_mtl_triple(const char* p, float v[3])
|
||
|
{
|
||
|
p = read_mtl_single(p, &v[0]);
|
||
|
p = read_mtl_single(p, &v[1]);
|
||
|
p = read_mtl_single(p, &v[2]);
|
||
|
|
||
|
return p;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
const char* read_map(fastObjData* data, const char* ptr, unsigned int* idx)
|
||
|
{
|
||
|
const char* s;
|
||
|
const char* e;
|
||
|
fastObjTexture* map;
|
||
|
|
||
|
|
||
|
ptr = skip_whitespace(ptr);
|
||
|
|
||
|
/* Don't support options at present */
|
||
|
if (*ptr == '-')
|
||
|
return ptr;
|
||
|
|
||
|
|
||
|
/* Read name */
|
||
|
s = ptr;
|
||
|
ptr = skip_name(ptr);
|
||
|
e = ptr;
|
||
|
|
||
|
/* Try to find an existing texture map with the same name */
|
||
|
*idx = 1; /* skip dummy at index 0 */
|
||
|
while (*idx < array_size(data->mesh->textures))
|
||
|
{
|
||
|
map = &data->mesh->textures[*idx];
|
||
|
if (map->name && string_equal(map->name, s, e))
|
||
|
break;
|
||
|
|
||
|
(*idx)++;
|
||
|
}
|
||
|
|
||
|
/* Add it to the texture array if it didn't already exist */
|
||
|
if (*idx == array_size(data->mesh->textures))
|
||
|
{
|
||
|
fastObjTexture new_map = map_default();
|
||
|
new_map.name = string_copy(s, e);
|
||
|
new_map.path = string_concat(data->base, s, e);
|
||
|
string_fix_separators(new_map.path);
|
||
|
array_push(data->mesh->textures, new_map);
|
||
|
}
|
||
|
|
||
|
return e;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
int read_mtllib(fastObjData* data, void* file, const fastObjCallbacks* callbacks, void* user_data)
|
||
|
{
|
||
|
unsigned long n;
|
||
|
const char* s;
|
||
|
char* contents;
|
||
|
size_t l;
|
||
|
const char* p;
|
||
|
const char* e;
|
||
|
int found_d;
|
||
|
fastObjMaterial mtl;
|
||
|
|
||
|
|
||
|
/* Read entire file */
|
||
|
n = callbacks->file_size(file, user_data);
|
||
|
|
||
|
contents = (char*)(memory_realloc(0, n + 1));
|
||
|
if (!contents)
|
||
|
return 0;
|
||
|
|
||
|
l = callbacks->file_read(file, contents, n, user_data);
|
||
|
contents[l] = '\n';
|
||
|
|
||
|
mtl = mtl_default();
|
||
|
|
||
|
found_d = 0;
|
||
|
|
||
|
p = contents;
|
||
|
e = contents + l;
|
||
|
while (p < e)
|
||
|
{
|
||
|
p = skip_whitespace(p);
|
||
|
|
||
|
switch (*p)
|
||
|
{
|
||
|
case 'n':
|
||
|
p++;
|
||
|
if (p[0] == 'e' &&
|
||
|
p[1] == 'w' &&
|
||
|
p[2] == 'm' &&
|
||
|
p[3] == 't' &&
|
||
|
p[4] == 'l' &&
|
||
|
is_whitespace(p[5]))
|
||
|
{
|
||
|
/* Push previous material (if there is one) */
|
||
|
if (mtl.name)
|
||
|
{
|
||
|
array_push(data->mesh->materials, mtl);
|
||
|
mtl = mtl_default();
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Read name */
|
||
|
p += 5;
|
||
|
|
||
|
while (is_whitespace(*p))
|
||
|
p++;
|
||
|
|
||
|
s = p;
|
||
|
p = skip_name(p);
|
||
|
|
||
|
mtl.name = string_copy(s, p);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'K':
|
||
|
if (p[1] == 'a')
|
||
|
p = read_mtl_triple(p + 2, mtl.Ka);
|
||
|
else if (p[1] == 'd')
|
||
|
p = read_mtl_triple(p + 2, mtl.Kd);
|
||
|
else if (p[1] == 's')
|
||
|
p = read_mtl_triple(p + 2, mtl.Ks);
|
||
|
else if (p[1] == 'e')
|
||
|
p = read_mtl_triple(p + 2, mtl.Ke);
|
||
|
else if (p[1] == 't')
|
||
|
p = read_mtl_triple(p + 2, mtl.Kt);
|
||
|
break;
|
||
|
|
||
|
case 'N':
|
||
|
if (p[1] == 's')
|
||
|
p = read_mtl_single(p + 2, &mtl.Ns);
|
||
|
else if (p[1] == 'i')
|
||
|
p = read_mtl_single(p + 2, &mtl.Ni);
|
||
|
break;
|
||
|
|
||
|
case 'T':
|
||
|
if (p[1] == 'r')
|
||
|
{
|
||
|
float Tr;
|
||
|
p = read_mtl_single(p + 2, &Tr);
|
||
|
if (!found_d)
|
||
|
{
|
||
|
/* Ignore Tr if we've already read d */
|
||
|
mtl.d = 1.0f - Tr;
|
||
|
}
|
||
|
}
|
||
|
else if (p[1] == 'f')
|
||
|
p = read_mtl_triple(p + 2, mtl.Tf);
|
||
|
break;
|
||
|
|
||
|
case 'd':
|
||
|
if (is_whitespace(p[1]))
|
||
|
{
|
||
|
p = read_mtl_single(p + 1, &mtl.d);
|
||
|
found_d = 1;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'i':
|
||
|
p++;
|
||
|
if (p[0] == 'l' &&
|
||
|
p[1] == 'l' &&
|
||
|
p[2] == 'u' &&
|
||
|
p[3] == 'm' &&
|
||
|
is_whitespace(p[4]))
|
||
|
{
|
||
|
p = read_mtl_int(p + 4, &mtl.illum);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'm':
|
||
|
p++;
|
||
|
if (p[0] == 'a' &&
|
||
|
p[1] == 'p' &&
|
||
|
p[2] == '_')
|
||
|
{
|
||
|
p += 3;
|
||
|
if (*p == 'K')
|
||
|
{
|
||
|
p++;
|
||
|
if (is_whitespace(p[1]))
|
||
|
{
|
||
|
if (*p == 'a')
|
||
|
p = read_map(data, p + 1, &mtl.map_Ka);
|
||
|
else if (*p == 'd')
|
||
|
p = read_map(data, p + 1, &mtl.map_Kd);
|
||
|
else if (*p == 's')
|
||
|
p = read_map(data, p + 1, &mtl.map_Ks);
|
||
|
else if (*p == 'e')
|
||
|
p = read_map(data, p + 1, &mtl.map_Ke);
|
||
|
else if (*p == 't')
|
||
|
p = read_map(data, p + 1, &mtl.map_Kt);
|
||
|
}
|
||
|
}
|
||
|
else if (*p == 'N')
|
||
|
{
|
||
|
p++;
|
||
|
if (is_whitespace(p[1]))
|
||
|
{
|
||
|
if (*p == 's')
|
||
|
p = read_map(data, p + 1, &mtl.map_Ns);
|
||
|
else if (*p == 'i')
|
||
|
p = read_map(data, p + 1, &mtl.map_Ni);
|
||
|
}
|
||
|
}
|
||
|
else if (*p == 'd')
|
||
|
{
|
||
|
p++;
|
||
|
if (is_whitespace(*p))
|
||
|
p = read_map(data, p, &mtl.map_d);
|
||
|
}
|
||
|
else if ((p[0] == 'b' || p[0] == 'B') &&
|
||
|
p[1] == 'u' &&
|
||
|
p[2] == 'm' &&
|
||
|
p[3] == 'p' &&
|
||
|
is_whitespace(p[4]))
|
||
|
{
|
||
|
p = read_map(data, p + 4, &mtl.map_bump);
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case '#':
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
p = skip_line(p);
|
||
|
}
|
||
|
|
||
|
/* Push final material */
|
||
|
if (mtl.name)
|
||
|
array_push(data->mesh->materials, mtl);
|
||
|
|
||
|
memory_dealloc(contents);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
const char* parse_mtllib(fastObjData* data, const char* ptr, const fastObjCallbacks* callbacks, void* user_data)
|
||
|
{
|
||
|
const char* s;
|
||
|
const char* e;
|
||
|
char* lib;
|
||
|
void* file;
|
||
|
|
||
|
|
||
|
ptr = skip_whitespace(ptr);
|
||
|
|
||
|
s = ptr;
|
||
|
ptr = skip_name(ptr);
|
||
|
e = ptr;
|
||
|
|
||
|
lib = string_concat(data->base, s, e);
|
||
|
if (lib)
|
||
|
{
|
||
|
string_fix_separators(lib);
|
||
|
|
||
|
file = callbacks->file_open(lib, user_data);
|
||
|
if (file)
|
||
|
{
|
||
|
read_mtllib(data, file, callbacks, user_data);
|
||
|
callbacks->file_close(file, user_data);
|
||
|
}
|
||
|
|
||
|
memory_dealloc(lib);
|
||
|
}
|
||
|
|
||
|
return ptr;
|
||
|
}
|
||
|
|
||
|
|
||
|
static
|
||
|
void parse_buffer(fastObjData* data, const char* ptr, const char* end, const fastObjCallbacks* callbacks, void* user_data)
|
||
|
{
|
||
|
const char* p;
|
||
|
|
||
|
|
||
|
p = ptr;
|
||
|
while (p != end)
|
||
|
{
|
||
|
p = skip_whitespace(p);
|
||
|
|
||
|
switch (*p)
|
||
|
{
|
||
|
case 'v':
|
||
|
p++;
|
||
|
|
||
|
switch (*p++)
|
||
|
{
|
||
|
case ' ':
|
||
|
case '\t':
|
||
|
p = parse_vertex(data, p);
|
||
|
break;
|
||
|
|
||
|
case 't':
|
||
|
p = parse_texcoord(data, p);
|
||
|
break;
|
||
|
|
||
|
case 'n':
|
||
|
p = parse_normal(data, p);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
p--; /* roll p++ back in case *p was a newline */
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'f':
|
||
|
p++;
|
||
|
|
||
|
switch (*p++)
|
||
|
{
|
||
|
case ' ':
|
||
|
case '\t':
|
||
|
p = parse_face(data, p);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
p--; /* roll p++ back in case *p was a newline */
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'o':
|
||
|
p++;
|
||
|
|
||
|
switch (*p++)
|
||
|
{
|
||
|
case ' ':
|
||
|
case '\t':
|
||
|
p = parse_object(data, p);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
p--; /* roll p++ back in case *p was a newline */
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'g':
|
||
|
p++;
|
||
|
|
||
|
switch (*p++)
|
||
|
{
|
||
|
case ' ':
|
||
|
case '\t':
|
||
|
p = parse_group(data, p);
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
p--; /* roll p++ back in case *p was a newline */
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'm':
|
||
|
p++;
|
||
|
if (p[0] == 't' &&
|
||
|
p[1] == 'l' &&
|
||
|
p[2] == 'l' &&
|
||
|
p[3] == 'i' &&
|
||
|
p[4] == 'b' &&
|
||
|
is_whitespace(p[5]))
|
||
|
p = parse_mtllib(data, p + 5, callbacks, user_data);
|
||
|
break;
|
||
|
|
||
|
case 'u':
|
||
|
p++;
|
||
|
if (p[0] == 's' &&
|
||
|
p[1] == 'e' &&
|
||
|
p[2] == 'm' &&
|
||
|
p[3] == 't' &&
|
||
|
p[4] == 'l' &&
|
||
|
is_whitespace(p[5]))
|
||
|
p = parse_usemtl(data, p + 5);
|
||
|
break;
|
||
|
|
||
|
case '#':
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
p = skip_line(p);
|
||
|
|
||
|
data->line++;
|
||
|
}
|
||
|
if (array_size(data->mesh->colors) > 0)
|
||
|
{
|
||
|
/* Fill the remaining slots in the colors array */
|
||
|
unsigned int ii;
|
||
|
for (ii = array_size(data->mesh->colors); ii < array_size(data->mesh->positions); ++ii)
|
||
|
{
|
||
|
array_push(data->mesh->colors, 1.0f);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
void fast_obj_destroy(fastObjMesh* m)
|
||
|
{
|
||
|
unsigned int ii;
|
||
|
|
||
|
|
||
|
for (ii = 0; ii < array_size(m->objects); ii++)
|
||
|
object_clean(&m->objects[ii]);
|
||
|
|
||
|
for (ii = 0; ii < array_size(m->groups); ii++)
|
||
|
group_clean(&m->groups[ii]);
|
||
|
|
||
|
for (ii = 0; ii < array_size(m->materials); ii++)
|
||
|
mtl_clean(&m->materials[ii]);
|
||
|
|
||
|
for (ii = 0; ii < array_size(m->textures); ii++)
|
||
|
map_clean(&m->textures[ii]);
|
||
|
|
||
|
array_clean(m->positions);
|
||
|
array_clean(m->texcoords);
|
||
|
array_clean(m->normals);
|
||
|
array_clean(m->colors);
|
||
|
array_clean(m->face_vertices);
|
||
|
array_clean(m->face_materials);
|
||
|
array_clean(m->indices);
|
||
|
array_clean(m->objects);
|
||
|
array_clean(m->groups);
|
||
|
array_clean(m->materials);
|
||
|
array_clean(m->textures);
|
||
|
|
||
|
memory_dealloc(m);
|
||
|
}
|
||
|
|
||
|
|
||
|
fastObjMesh* fast_obj_read(const char* path)
|
||
|
{
|
||
|
fastObjCallbacks callbacks;
|
||
|
callbacks.file_open = file_open;
|
||
|
callbacks.file_close = file_close;
|
||
|
callbacks.file_read = file_read;
|
||
|
callbacks.file_size = file_size;
|
||
|
|
||
|
return fast_obj_read_with_callbacks(path, &callbacks, 0);
|
||
|
}
|
||
|
|
||
|
|
||
|
fastObjMesh* fast_obj_read_with_callbacks(const char* path, const fastObjCallbacks* callbacks, void* user_data)
|
||
|
{
|
||
|
fastObjData data;
|
||
|
fastObjMesh* m;
|
||
|
void* file;
|
||
|
char* buffer;
|
||
|
char* start;
|
||
|
char* end;
|
||
|
char* last;
|
||
|
fastObjUInt read;
|
||
|
fastObjUInt bytes;
|
||
|
|
||
|
/* Check if callbacks are valid */
|
||
|
if(!callbacks)
|
||
|
return 0;
|
||
|
|
||
|
|
||
|
/* Open file */
|
||
|
file = callbacks->file_open(path, user_data);
|
||
|
if (!file)
|
||
|
return 0;
|
||
|
|
||
|
|
||
|
/* Empty mesh */
|
||
|
m = (fastObjMesh*)(memory_realloc(0, sizeof(fastObjMesh)));
|
||
|
if (!m)
|
||
|
return 0;
|
||
|
|
||
|
m->positions = 0;
|
||
|
m->texcoords = 0;
|
||
|
m->normals = 0;
|
||
|
m->colors = 0;
|
||
|
m->face_vertices = 0;
|
||
|
m->face_materials = 0;
|
||
|
m->indices = 0;
|
||
|
m->materials = 0;
|
||
|
m->textures = 0;
|
||
|
m->objects = 0;
|
||
|
m->groups = 0;
|
||
|
|
||
|
|
||
|
/* Add dummy position/texcoord/normal/texture */
|
||
|
array_push(m->positions, 0.0f);
|
||
|
array_push(m->positions, 0.0f);
|
||
|
array_push(m->positions, 0.0f);
|
||
|
|
||
|
array_push(m->texcoords, 0.0f);
|
||
|
array_push(m->texcoords, 0.0f);
|
||
|
|
||
|
array_push(m->normals, 0.0f);
|
||
|
array_push(m->normals, 0.0f);
|
||
|
array_push(m->normals, 1.0f);
|
||
|
|
||
|
array_push(m->textures, map_default());
|
||
|
|
||
|
|
||
|
/* Data needed during parsing */
|
||
|
data.mesh = m;
|
||
|
data.object = object_default();
|
||
|
data.group = group_default();
|
||
|
data.material = 0;
|
||
|
data.line = 1;
|
||
|
data.base = 0;
|
||
|
|
||
|
|
||
|
/* Find base path for materials/textures */
|
||
|
{
|
||
|
const char* sep1 = strrchr(path, FAST_OBJ_SEPARATOR);
|
||
|
const char* sep2 = strrchr(path, FAST_OBJ_OTHER_SEP);
|
||
|
|
||
|
/* Use the last separator in the path */
|
||
|
const char* sep = sep2 && (!sep1 || sep1 < sep2) ? sep2 : sep1;
|
||
|
|
||
|
if (sep)
|
||
|
data.base = string_substr(path, 0, sep - path + 1);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Create buffer for reading file */
|
||
|
buffer = (char*)(memory_realloc(0, 2 * BUFFER_SIZE * sizeof(char)));
|
||
|
if (!buffer)
|
||
|
return 0;
|
||
|
|
||
|
start = buffer;
|
||
|
for (;;)
|
||
|
{
|
||
|
/* Read another buffer's worth from file */
|
||
|
read = (fastObjUInt)(callbacks->file_read(file, start, BUFFER_SIZE, user_data));
|
||
|
if (read == 0 && start == buffer)
|
||
|
break;
|
||
|
|
||
|
|
||
|
/* Ensure buffer ends in a newline */
|
||
|
if (read < BUFFER_SIZE)
|
||
|
{
|
||
|
if (read == 0 || start[read - 1] != '\n')
|
||
|
start[read++] = '\n';
|
||
|
}
|
||
|
|
||
|
end = start + read;
|
||
|
if (end == buffer)
|
||
|
break;
|
||
|
|
||
|
|
||
|
/* Find last new line */
|
||
|
last = end;
|
||
|
while (last > buffer)
|
||
|
{
|
||
|
last--;
|
||
|
if (*last == '\n')
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Check there actually is a new line */
|
||
|
if (*last != '\n')
|
||
|
break;
|
||
|
|
||
|
last++;
|
||
|
|
||
|
|
||
|
/* Process buffer */
|
||
|
parse_buffer(&data, buffer, last, callbacks, user_data);
|
||
|
|
||
|
|
||
|
/* Copy overflow for next buffer */
|
||
|
bytes = (fastObjUInt)(end - last);
|
||
|
memmove(buffer, last, bytes);
|
||
|
start = buffer + bytes;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Flush final object/group */
|
||
|
flush_object(&data);
|
||
|
object_clean(&data.object);
|
||
|
|
||
|
flush_group(&data);
|
||
|
group_clean(&data.group);
|
||
|
|
||
|
m->position_count = array_size(m->positions) / 3;
|
||
|
m->texcoord_count = array_size(m->texcoords) / 2;
|
||
|
m->normal_count = array_size(m->normals) / 3;
|
||
|
m->color_count = array_size(m->colors) / 3;
|
||
|
m->face_count = array_size(m->face_vertices);
|
||
|
m->index_count = array_size(m->indices);
|
||
|
m->material_count = array_size(m->materials);
|
||
|
m->texture_count = array_size(m->textures);
|
||
|
m->object_count = array_size(m->objects);
|
||
|
m->group_count = array_size(m->groups);
|
||
|
|
||
|
|
||
|
/* Clean up */
|
||
|
memory_dealloc(buffer);
|
||
|
memory_dealloc(data.base);
|
||
|
|
||
|
callbacks->file_close(file, user_data);
|
||
|
|
||
|
return m;
|
||
|
}
|
||
|
|
||
|
#endif
|