2472 lines
60 KiB
C
2472 lines
60 KiB
C
/* MIT License - 2022 JackCarterSmith */
|
|
/* Modified and cleaned version - 19/08/2022 */
|
|
/* */
|
|
/* Copyright (c) 2005,2013,2014 Robert Kooima */
|
|
/* */
|
|
/* 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. */
|
|
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include <math.h>
|
|
|
|
#ifndef CONF_NO_GL
|
|
#include <GL/glew.h>
|
|
#endif
|
|
|
|
#define MAXSTR 1024
|
|
|
|
/*============================================================================*/
|
|
|
|
#define index_t unsigned int
|
|
#define GL_INDEX_T GL_UNSIGNED_INT
|
|
/*
|
|
#define index_t unsigned short
|
|
#define GL_INDEX_T GL_UNSIGNED_SHORT
|
|
*/
|
|
/*============================================================================*/
|
|
|
|
#include "obj.h"
|
|
|
|
struct obj_prop
|
|
{
|
|
char *str;
|
|
int opt;
|
|
unsigned int map;
|
|
|
|
float c[4];
|
|
float o[3];
|
|
float s[3];
|
|
};
|
|
|
|
struct obj_mtrl
|
|
{
|
|
char *name;
|
|
|
|
struct obj_prop kv[OBJ_PROP_COUNT];
|
|
};
|
|
|
|
struct obj_vert
|
|
{
|
|
float u[3];
|
|
float n[3];
|
|
float t[2];
|
|
float v[3];
|
|
};
|
|
|
|
struct obj_poly
|
|
{
|
|
index_t vi[3];
|
|
};
|
|
|
|
struct obj_line
|
|
{
|
|
index_t vi[2];
|
|
};
|
|
|
|
struct obj_surf
|
|
{
|
|
int mi;
|
|
|
|
int pc;
|
|
int pm;
|
|
int lc;
|
|
int lm;
|
|
|
|
unsigned int pibo;
|
|
unsigned int libo;
|
|
|
|
struct obj_poly *pv;
|
|
struct obj_line *lv;
|
|
};
|
|
|
|
struct obj
|
|
{
|
|
unsigned int vao;
|
|
unsigned int vbo;
|
|
|
|
int mc;
|
|
int mm;
|
|
int vc;
|
|
int vm;
|
|
int sc;
|
|
int sm;
|
|
|
|
int uloc;
|
|
int nloc;
|
|
int tloc;
|
|
int vloc;
|
|
|
|
int cloc[OBJ_PROP_COUNT];
|
|
int oloc[OBJ_PROP_COUNT];
|
|
int Mloc[OBJ_PROP_COUNT];
|
|
|
|
struct obj_mtrl *mv;
|
|
struct obj_vert *vv;
|
|
struct obj_surf *sv;
|
|
};
|
|
|
|
static void invalidate(obj *);
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
#define assert_surf(O, i) \
|
|
{ assert(O); assert(0 <= i && i < O->sc); }
|
|
#define assert_vert(O, i) \
|
|
{ assert(O); assert(0 <= i && i < O->vc); }
|
|
#define assert_mtrl(O, i) \
|
|
{ assert(O); assert(0 <= i && i < O->mc); }
|
|
|
|
#define assert_line(O, i, j) \
|
|
{ assert_surf(O, i); assert(0 <= j && j < O->sv[i].lc); }
|
|
#define assert_poly(O, i, j) \
|
|
{ assert_surf(O, i); assert(0 <= j && j < O->sv[i].pc); }
|
|
#define assert_prop(O, i, j) \
|
|
{ assert_mtrl(O, i); assert(0 <= j && j < OBJ_PROP_COUNT); }
|
|
|
|
/*============================================================================*/
|
|
/* Vector cache */
|
|
|
|
struct vec2
|
|
{
|
|
float v[2];
|
|
int _ii;
|
|
};
|
|
|
|
struct vec3
|
|
{
|
|
float v[3];
|
|
int _ii;
|
|
};
|
|
|
|
struct iset
|
|
{
|
|
int vi;
|
|
int gi;
|
|
|
|
int _vi;
|
|
int _ti;
|
|
int _ni;
|
|
int _ii;
|
|
};
|
|
|
|
static int _vc, _vm;
|
|
static int _tc, _tm;
|
|
static int _nc, _nm;
|
|
static int _ic, _im;
|
|
|
|
static struct vec3 *_vv;
|
|
static struct vec2 *_tv;
|
|
static struct vec3 *_nv;
|
|
static struct iset *_iv;
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
static int add__(void **_v, int *_c, int *_m, size_t _s)
|
|
{
|
|
int m = (*_m > 0) ? *_m * 2 : 2;
|
|
void *v;
|
|
|
|
/* If space remains in the current block, return it. */
|
|
|
|
if (*_m > *_c)
|
|
return (*_c)++;
|
|
|
|
/* Else, try to increase the size of the block. */
|
|
|
|
else if ((v = realloc(*_v, _s * m)))
|
|
{
|
|
*_v = v;
|
|
*_m = m;
|
|
return (*_c)++;
|
|
}
|
|
|
|
/* Else, indicate failure. */
|
|
|
|
else return -1;
|
|
}
|
|
|
|
static int add_v(void)
|
|
{
|
|
return add__((void **) &_vv, &_vc, &_vm, sizeof (struct vec3));
|
|
}
|
|
|
|
static int add_t(void)
|
|
{
|
|
return add__((void **) &_tv, &_tc, &_tm, sizeof (struct vec2));
|
|
}
|
|
|
|
static int add_n(void)
|
|
{
|
|
return add__((void **) &_nv, &_nc, &_nm, sizeof (struct vec3));
|
|
}
|
|
|
|
static int add_i(void)
|
|
{
|
|
return add__((void **) &_iv, &_ic, &_im, sizeof (struct iset));
|
|
}
|
|
|
|
/*============================================================================*/
|
|
/* Handy functions */
|
|
|
|
static void cross(float *z, const float *x, const float *y)
|
|
{
|
|
float t[3];
|
|
|
|
t[0] = x[1] * y[2] - x[2] * y[1];
|
|
t[1] = x[2] * y[0] - x[0] * y[2];
|
|
t[2] = x[0] * y[1] - x[1] * y[0];
|
|
|
|
z[0] = t[0];
|
|
z[1] = t[1];
|
|
z[2] = t[2];
|
|
}
|
|
|
|
static void normalize(float *v)
|
|
{
|
|
float k = 1.0f / (float) sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
|
|
|
v[0] *= k;
|
|
v[1] *= k;
|
|
v[2] *= k;
|
|
}
|
|
|
|
static void normal(float *n, const float *a,
|
|
const float *b,
|
|
const float *c)
|
|
{
|
|
float u[3];
|
|
float v[3];
|
|
|
|
u[0] = b[0] - a[0];
|
|
u[1] = b[1] - a[1];
|
|
u[2] = b[2] - a[2];
|
|
|
|
v[0] = c[0] - a[0];
|
|
v[1] = c[1] - a[1];
|
|
v[2] = c[2] - a[2];
|
|
|
|
cross(n, u, v);
|
|
normalize(n);
|
|
}
|
|
|
|
/*============================================================================*/
|
|
|
|
#pragma pack(push, 1)
|
|
struct tga_head
|
|
{
|
|
unsigned char id_length;
|
|
unsigned char color_map_type;
|
|
unsigned char image_type;
|
|
unsigned short color_map_offset;
|
|
unsigned short color_map_length;
|
|
unsigned char color_map_size;
|
|
unsigned short image_x_origin;
|
|
unsigned short image_y_origin;
|
|
unsigned short image_width;
|
|
unsigned short image_height;
|
|
unsigned char image_depth;
|
|
unsigned char image_descriptor;
|
|
};
|
|
#pragma pack(pop)
|
|
|
|
void *read_tga(const char *filename, int *w, int *h, int *d)
|
|
{
|
|
struct tga_head head;
|
|
FILE *stream;
|
|
|
|
if ((stream = fopen(filename, "rb")))
|
|
{
|
|
if (fread(&head, sizeof (struct tga_head), 1, stream) == 1)
|
|
{
|
|
if (head.image_type == 2)
|
|
{
|
|
*w = (int) head.image_width;
|
|
*h = (int) head.image_height;
|
|
*d = (int) head.image_depth;
|
|
|
|
if (fseek(stream, head.id_length, SEEK_CUR) == 0)
|
|
{
|
|
size_t s = (*d) / 8;
|
|
size_t n = (*w) * (*h);
|
|
void *p;
|
|
|
|
if ((p = calloc(n, s)))
|
|
{
|
|
if (fread(p, s, n, stream) == n)
|
|
{
|
|
fclose(stream);
|
|
return p;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fclose(stream);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
unsigned int obj_load_image(const char *filename)
|
|
{
|
|
unsigned int o = 0;
|
|
|
|
#ifndef CONF_NO_GL
|
|
if (filename)
|
|
{
|
|
int w;
|
|
int h;
|
|
int d;
|
|
void *p;
|
|
|
|
/* Read the image data from the named file to a new pixel buffer. */
|
|
|
|
if ((p = read_tga(filename, &w, &h, &d)))
|
|
{
|
|
/* Create an OpenGL texture object using these pixels. */
|
|
|
|
glGenTextures(1, &o);
|
|
glBindTexture(GL_TEXTURE_2D, o);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
|
|
|
if (d == 32)
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0,
|
|
GL_BGRA, GL_UNSIGNED_BYTE, p);
|
|
if (d == 24)
|
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0,
|
|
GL_BGR, GL_UNSIGNED_BYTE, p);
|
|
|
|
glGenerateMipmap(GL_TEXTURE_2D);
|
|
|
|
/* Discard the unnecessary pixel buffer. */
|
|
|
|
free(p);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return o;
|
|
}
|
|
|
|
static void dirpath(char *pathname)
|
|
{
|
|
int i;
|
|
|
|
/* Find the path by cutting a file name at the last directory delimiter. */
|
|
|
|
for (i = (int) strlen(pathname) - 1; i >= 0; --i)
|
|
if (pathname[i] == '/' || pathname[i] == '\\')
|
|
{
|
|
pathname[i] = '\0';
|
|
return;
|
|
}
|
|
|
|
/* If no delimiter was found, return the current directory. */
|
|
|
|
strcpy(pathname, ".");
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
static void read_image(obj *O, int mi, int ki, const char *line,
|
|
const char *path)
|
|
{
|
|
unsigned int clamp = 0;
|
|
|
|
float o[3] = { 0.0f, 0.0f, 0.0f };
|
|
float s[3] = { 1.0f, 1.0f, 1.0f };
|
|
|
|
char pathname[MAXSTR];
|
|
|
|
char map[MAXSTR - 1];
|
|
char val[MAXSTR];
|
|
|
|
const char *end;
|
|
|
|
memset(map, 0, MAXSTR - 1);
|
|
memset(val, 0, MAXSTR);
|
|
|
|
while (line[0] != '\0' && line[0] != '\r' && line[0] != '\n')
|
|
{
|
|
int n = 0;
|
|
|
|
while(isspace(line[0])) line++;
|
|
|
|
/* Parse property map options. */
|
|
|
|
if (sscanf(line, "-clamp %s%n", val, &n) >= 1)
|
|
{
|
|
clamp = (strcmp(val, "on") == 0) ? OBJ_OPT_CLAMP : 0;
|
|
line += n;
|
|
}
|
|
|
|
/* Parse property map scale. */
|
|
|
|
else if (sscanf(line, "-s %f %f %f%n", s + 0, s + 1, s + 2, &n) >= 3)
|
|
line += n;
|
|
else if (sscanf(line, "-s %f %f%n", s + 0, s + 1, &n) >= 2)
|
|
line += n;
|
|
else if (sscanf(line, "-s %f%n", s + 0, &n) >= 1)
|
|
line += n;
|
|
|
|
/* Parse property map offset. */
|
|
|
|
else if (sscanf(line, "-o %f %f %f%n", o + 0, o + 1, o + 2, &n) >= 3)
|
|
line += n;
|
|
else if (sscanf(line, "-o %f %f%n", o + 0, o + 1, &n) >= 2)
|
|
line += n;
|
|
else if (sscanf(line, "-o %f%n", o + 0, &n) >= 1)
|
|
line += n;
|
|
|
|
/* Check for a file name */
|
|
|
|
else if ((end = strstr(line, ".tga"))) { strncpy(map, line, end - line + 4); break; }
|
|
else if ((end = strstr(line, ".TGA"))) { strncpy(map, line, end - line + 4); break; }
|
|
|
|
/* If we see something we don't recognize, stop looking. */
|
|
|
|
else break;
|
|
}
|
|
|
|
/* Apply all parsed property attributes to the material. */
|
|
|
|
sprintf(pathname, "%s/%s", path, map);
|
|
|
|
obj_set_mtrl_opt(O, mi, ki, clamp);
|
|
obj_set_mtrl_map(O, mi, ki, pathname);
|
|
obj_set_mtrl_o (O, mi, ki, o);
|
|
obj_set_mtrl_s (O, mi, ki, s);
|
|
}
|
|
|
|
static void read_color(obj *O, int mi, int ki, const char *line)
|
|
{
|
|
float c[4];
|
|
|
|
/* Merge incoming color components with existing defaults. */
|
|
|
|
obj_get_mtrl_c(O, mi, ki, c);
|
|
sscanf(line, "%f %f %f", c, c + 1, c + 2);
|
|
obj_set_mtrl_c(O, mi, ki, c);
|
|
}
|
|
|
|
static void read_alpha(obj *O, int mi, int ki, const char *line)
|
|
{
|
|
float c[4];
|
|
float t;
|
|
|
|
/* Merge incoming color components with existing defaults. */
|
|
|
|
obj_get_mtrl_c(O, mi, ki, c);
|
|
sscanf(line, "%f", &t);
|
|
c[3] = 1.0 - t;
|
|
obj_set_mtrl_c(O, mi, ki, c);
|
|
}
|
|
|
|
static void read_mtl(const char *path,
|
|
const char *file,
|
|
const char *name, obj *O, int mi)
|
|
{
|
|
char pathname[MAXSTR];
|
|
|
|
char buf[MAXSTR];
|
|
char key[MAXSTR];
|
|
char arg[MAXSTR];
|
|
|
|
FILE *fin;
|
|
|
|
int scanning = 1;
|
|
int n = 0;
|
|
|
|
sprintf(pathname, "%s/%s", path, file);
|
|
|
|
if ((fin = fopen(pathname, "r")))
|
|
{
|
|
/* Process each line of the MTL file. */
|
|
|
|
while (fgets (buf, MAXSTR, fin))
|
|
if (sscanf(buf, "%s%n", key, &n) >= 1)
|
|
{
|
|
const char *c = buf + n;
|
|
|
|
if (scanning)
|
|
{
|
|
/* Determine if we've found the MTL we're looking for. */
|
|
|
|
if (!strcmp(key, "newmtl"))
|
|
{
|
|
sscanf(c, "%s", arg);
|
|
|
|
if ((scanning = strcmp(arg, name)) == 0)
|
|
obj_set_mtrl_name(O, mi, name);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Stop scanning when the next MTL begins. */
|
|
|
|
if (!strcmp(key, "newmtl"))
|
|
break;
|
|
|
|
/* Parse this material's properties. */
|
|
|
|
else if (!strcmp(key, "map_Kd"))
|
|
read_image(O, mi, OBJ_KD, c, path);
|
|
else if (!strcmp(key, "map_Ka"))
|
|
read_image(O, mi, OBJ_KA, c, path);
|
|
else if (!strcmp(key, "map_Ke"))
|
|
read_image(O, mi, OBJ_KE, c, path);
|
|
else if (!strcmp(key, "map_Ks"))
|
|
read_image(O, mi, OBJ_KS, c, path);
|
|
else if (!strcmp(key, "map_Ns"))
|
|
read_image(O, mi, OBJ_NS, c, path);
|
|
else if (!strcmp(key, "map_Kn"))
|
|
read_image(O, mi, OBJ_KN, c, path);
|
|
|
|
else if (!strcmp(key, "Kd"))
|
|
read_color(O, mi, OBJ_KD, c);
|
|
else if (!strcmp(key, "Ka"))
|
|
read_color(O, mi, OBJ_KA, c);
|
|
else if (!strcmp(key, "Ke"))
|
|
read_color(O, mi, OBJ_KE, c);
|
|
else if (!strcmp(key, "Ks"))
|
|
read_color(O, mi, OBJ_KS, c);
|
|
else if (!strcmp(key, "Ns"))
|
|
read_color(O, mi, OBJ_NS, c);
|
|
|
|
else if (!strcmp(key, "d"))
|
|
read_alpha(O, mi, OBJ_KD, c);
|
|
else if (!strcmp(key, "Tr"))
|
|
read_alpha(O, mi, OBJ_KD, c);
|
|
}
|
|
}
|
|
fclose(fin);
|
|
}
|
|
}
|
|
|
|
static void read_mtllib(char *file, const char *line)
|
|
{
|
|
/* Parse the first file name from the given line. */
|
|
|
|
sscanf(line, "%s", file);
|
|
}
|
|
|
|
static int read_usemtl(const char *path,
|
|
const char *file,
|
|
const char *line, obj *O)
|
|
{
|
|
char name[MAXSTR];
|
|
|
|
int si;
|
|
int mi;
|
|
|
|
sscanf(line, "%s", name);
|
|
|
|
/* Create a new material for the incoming definition. */
|
|
|
|
if ((mi = obj_add_mtrl(O)) >= 0)
|
|
{
|
|
/* Create a new surface to contain geometry with the new material. */
|
|
|
|
if ((si = obj_add_surf(O)) >= 0)
|
|
{
|
|
/* Read the material definition and apply it to the new surface. */
|
|
|
|
read_mtl(path, file, name, O, mi);
|
|
obj_set_surf(O, si, mi);
|
|
|
|
/* Return the surface so that new geometry may be added to it. */
|
|
|
|
return si;
|
|
}
|
|
}
|
|
|
|
/* On failure, return the default surface. */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
static int read_poly_indices(const char *line, int *_vi, int *_ti, int *_ni)
|
|
{
|
|
int n;
|
|
|
|
*_vi = 0;
|
|
*_ti = 0;
|
|
*_ni = 0;
|
|
|
|
/* Parse a face vertex specification from the given line. */
|
|
|
|
if (sscanf(line, "%d/%d/%d%n", _vi, _ti, _ni, &n) >= 3) return n;
|
|
if (sscanf(line, "%d/%d%n", _vi, _ti, &n) >= 2) return n;
|
|
if (sscanf(line, "%d//%d%n", _vi, _ni, &n) >= 2) return n;
|
|
if (sscanf(line, "%d%n", _vi, &n) >= 1) return n;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int read_poly_vertices(const char *line, obj *O, int gi)
|
|
{
|
|
const char *c = line;
|
|
|
|
int _vi;
|
|
int _ti;
|
|
int _ni;
|
|
int _ii;
|
|
int _ij;
|
|
|
|
int dc;
|
|
int vi;
|
|
int ic = 0;
|
|
|
|
/* Scan the face string, converting index sets to vertices. */
|
|
|
|
while ((dc = read_poly_indices(c, &_vi, &_ti, &_ni)))
|
|
{
|
|
/* Convert face indices to vector cache indices. */
|
|
|
|
_vi += (_vi < 0) ? _vc : -1;
|
|
_ti += (_ti < 0) ? _tc : -1;
|
|
_ni += (_ni < 0) ? _nc : -1;
|
|
|
|
/* Initialize a new index set. */
|
|
|
|
if ((_ii = add_i()) >= 0)
|
|
{
|
|
_iv[_ii]._vi = _vi;
|
|
_iv[_ii]._ni = _ni;
|
|
_iv[_ii]._ti = _ti;
|
|
|
|
/* Search the vector reference list for a repeated index set. */
|
|
|
|
for (_ij = _vv[_vi]._ii; _ij >= 0; _ij = _iv[_ij]._ii)
|
|
if (_iv[_ij]._vi == _vi &&
|
|
_iv[_ij]._ti == _ti &&
|
|
_iv[_ij]._ni == _ni &&
|
|
_iv[_ij]. gi == gi)
|
|
{
|
|
/* A repeat has been found. Link new to old. */
|
|
|
|
_vv[_vi]._ii = _ii;
|
|
_iv[_ii]._ii = _ij;
|
|
_iv[_ii]. vi = _iv[_ij].vi;
|
|
_iv[_ii]. gi = _iv[_ij].gi;
|
|
|
|
break;
|
|
}
|
|
|
|
/* If no repeat was found, add a new vertex. */
|
|
|
|
if ((_ij < 0) && (vi = obj_add_vert(O)) >= 0)
|
|
{
|
|
_vv[_vi]._ii = _ii;
|
|
_iv[_ii]._ii = -1;
|
|
_iv[_ii]. vi = vi;
|
|
_iv[_ii]. gi = gi;
|
|
|
|
/* Initialize the new vertex using valid cache references. */
|
|
|
|
if (0 <= _vi && _vi < _vc) obj_set_vert_v(O, vi, _vv[_vi].v);
|
|
if (0 <= _ni && _ni < _nc) obj_set_vert_n(O, vi, _nv[_ni].v);
|
|
if (0 <= _ti && _ti < _tc) obj_set_vert_t(O, vi, _tv[_ti].v);
|
|
}
|
|
ic++;
|
|
}
|
|
c += dc;
|
|
}
|
|
return ic;
|
|
}
|
|
|
|
static void read_f(const char *line, obj *O, int si, int gi)
|
|
{
|
|
float n[3];
|
|
float t[3];
|
|
int i, pi;
|
|
|
|
/* Create new vertex references for this face. */
|
|
|
|
int i0 = _ic;
|
|
int ic = read_poly_vertices(line, O, gi);
|
|
|
|
/* If smoothing, apply this face's normal to vertices that need it. */
|
|
|
|
if (gi)
|
|
{
|
|
normal(n, _vv[_iv[i0 + 0]._vi].v,
|
|
_vv[_iv[i0 + 1]._vi].v,
|
|
_vv[_iv[i0 + 2]._vi].v);
|
|
|
|
for (i = 0; i < ic; ++i)
|
|
if (_iv[i0 + 0]._ni < 0)
|
|
{
|
|
obj_get_vert_n(O, _iv[i0 + i]._vi, t);
|
|
t[0] += n[0];
|
|
t[1] += n[1];
|
|
t[2] += n[2];
|
|
obj_set_vert_n(O, _iv[i0 + i]._vi, t);
|
|
}
|
|
}
|
|
|
|
/* Convert our N new vertex references into N-2 new triangles. */
|
|
|
|
for (i = 0; i < ic - 2; ++i)
|
|
|
|
if ((pi = obj_add_poly(O, si)) >= 0)
|
|
{
|
|
int vi[3];
|
|
|
|
vi[0] = _iv[i0 ].vi;
|
|
vi[1] = _iv[i0 + i + 1].vi;
|
|
vi[2] = _iv[i0 + i + 2].vi;
|
|
|
|
obj_set_poly(O, si, pi, vi);
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
static int read_line_indices(const char *line, int *_vi, int *_ti)
|
|
{
|
|
int n;
|
|
|
|
*_vi = 0;
|
|
*_ti = 0;
|
|
|
|
/* Parse a line vertex specification from the given line. */
|
|
|
|
if (sscanf(line, "%d/%d%n", _vi, _ti, &n) >= 2) return n;
|
|
if (sscanf(line, "%d%n", _vi, &n) >= 1) return n;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int read_line_vertices(const char *line, obj *O)
|
|
{
|
|
const char *c = line;
|
|
|
|
int _vi;
|
|
int _ti;
|
|
int _ii;
|
|
int _ij;
|
|
|
|
int dc;
|
|
int vi;
|
|
int ic = 0;
|
|
|
|
/* Scan the line string, converting index sets to vertices. */
|
|
|
|
while ((dc = read_line_indices(c, &_vi, &_ti)))
|
|
{
|
|
/* Convert line indices to vector cache indices. */
|
|
|
|
_vi += (_vi < 0) ? _vc : -1;
|
|
_ti += (_ti < 0) ? _tc : -1;
|
|
|
|
/* Initialize a new index set. */
|
|
|
|
if ((_ii = add_i()) >= 0)
|
|
{
|
|
_iv[_ii]._vi = _vi;
|
|
_iv[_ii]._ti = _ti;
|
|
|
|
/* Search the vector reference list for a repeated index set. */
|
|
|
|
for (_ij = _vv[_vi]._ii; _ij >= 0; _ij = _iv[_ij]._ii)
|
|
if (_iv[_ij]._vi == _vi &&
|
|
_iv[_ij]._ti == _ti)
|
|
{
|
|
/* A repeat has been found. Link new to old. */
|
|
|
|
_vv[_vi]._ii = _ii;
|
|
_iv[_ii]._ii = _ij;
|
|
_iv[_ii]. vi = _iv[_ij].vi;
|
|
|
|
break;
|
|
}
|
|
|
|
/* If no repeat was found, add a new vertex. */
|
|
|
|
if ((_ij < 0) && (vi = obj_add_vert(O)) >= 0)
|
|
{
|
|
_vv[_vi]._ii = _ii;
|
|
_iv[_ii]._ii = -1;
|
|
_iv[_ii]. vi = vi;
|
|
|
|
/* Initialize the new vertex using valid cache references. */
|
|
|
|
if (0 <= _vi && _vi < _vc) obj_set_vert_v(O, vi, _vv[_vi].v);
|
|
if (0 <= _ti && _ti < _tc) obj_set_vert_t(O, vi, _tv[_ti].v);
|
|
}
|
|
ic++;
|
|
}
|
|
c += dc;
|
|
}
|
|
return ic;
|
|
}
|
|
|
|
static void read_l(const char *line, obj *O, int si)
|
|
{
|
|
int i, li;
|
|
|
|
/* Create new vertices for this line. */
|
|
|
|
int i0 = _ic;
|
|
int ic = read_line_vertices(line, O);
|
|
|
|
/* Convert our N new vertices into N-1 new lines. */
|
|
|
|
for (i = 0; i < ic - 1; ++i)
|
|
|
|
if ((li = obj_add_line(O, si)) >= 0)
|
|
{
|
|
int vi[2];
|
|
|
|
vi[0] = _iv[i0 + i ].vi;
|
|
vi[1] = _iv[i0 + i + 1].vi;
|
|
|
|
obj_set_line(O, si, li, vi);
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
static void read_v(const char *line)
|
|
{
|
|
int _vi;
|
|
|
|
/* Parse a vertex position. */
|
|
|
|
if ((_vi = add_v()) >= 0)
|
|
{
|
|
sscanf(line, "%f %f %f", _vv[_vi].v + 0,
|
|
_vv[_vi].v + 1,
|
|
_vv[_vi].v + 2);
|
|
_vv[_vi]._ii = -1;
|
|
}
|
|
}
|
|
|
|
static void read_vt(const char *line)
|
|
{
|
|
int _ti;
|
|
|
|
/* Parse a texture coordinate. */
|
|
|
|
if ((_ti = add_t()) >= 0)
|
|
{
|
|
sscanf(line, "%f %f", _tv[_ti].v + 0,
|
|
_tv[_ti].v + 1);
|
|
_tv[_ti]._ii = -1;
|
|
}
|
|
}
|
|
|
|
static void read_vn(const char *line)
|
|
{
|
|
int _ni;
|
|
|
|
/* Parse a normal. */
|
|
|
|
if ((_ni = add_n()) >= 0)
|
|
{
|
|
sscanf(line, "%f %f %f", _nv[_ni].v + 0,
|
|
_nv[_ni].v + 1,
|
|
_nv[_ni].v + 2);
|
|
_nv[_ni]._ii = -1;
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
static void read_obj(obj *O, const char *filename)
|
|
{
|
|
char buf[MAXSTR];
|
|
char key[MAXSTR];
|
|
|
|
char L[MAXSTR];
|
|
char D[MAXSTR];
|
|
|
|
FILE *fin;
|
|
|
|
/* Flush the vector caches. */
|
|
|
|
_vc = 0;
|
|
_tc = 0;
|
|
_nc = 0;
|
|
_ic = 0;
|
|
|
|
/* Add the named file to the given object. */
|
|
|
|
if ((fin = fopen(filename, "r")))
|
|
{
|
|
/* Ensure there exists a default surface 0 and default material 0. */
|
|
|
|
int si = obj_add_surf(O);
|
|
int mi = obj_add_mtrl(O);
|
|
int gi = 0;
|
|
int n;
|
|
|
|
obj_set_surf(O, si, mi);
|
|
|
|
/* Extract the directory from the filename for use in MTL loading. */
|
|
|
|
strncpy(D, filename, MAXSTR);
|
|
dirpath(D);
|
|
|
|
/* Process each line of the OBJ file, invoking the handler for each. */
|
|
|
|
while (fgets (buf, MAXSTR, fin))
|
|
if (sscanf(buf, "%s%n", key, &n) >= 1)
|
|
{
|
|
const char *c = buf + n;
|
|
|
|
if (!strcmp(key, "f" )) read_f (c, O, si, gi);
|
|
else if (!strcmp(key, "l" )) read_l (c, O, si);
|
|
else if (!strcmp(key, "vt")) read_vt(c);
|
|
else if (!strcmp(key, "vn")) read_vn(c);
|
|
else if (!strcmp(key, "v" )) read_v (c);
|
|
|
|
else if (!strcmp(key, "mtllib")) read_mtllib( L, c );
|
|
else if (!strcmp(key, "usemtl")) si = read_usemtl(D, L, c, O);
|
|
else if (!strcmp(key, "s" )) gi = atoi(c);
|
|
}
|
|
|
|
fclose(fin);
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
static void obj_rel_mtrl(struct obj_mtrl *mp)
|
|
{
|
|
/* Release any resources held by this material. */
|
|
|
|
int ki;
|
|
|
|
for (ki = 0; ki < OBJ_PROP_COUNT; ki++)
|
|
{
|
|
if (mp->kv[ki].str) free(mp->kv[ki].str);
|
|
#ifndef CONF_NO_GL
|
|
if (mp->kv[ki].map) glDeleteTextures(1, &mp->kv[ki].map);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void obj_rel_surf(struct obj_surf *sp)
|
|
{
|
|
#ifndef CONF_NO_GL
|
|
if (sp->pibo) glDeleteBuffers(1, &sp->pibo);
|
|
if (sp->libo) glDeleteBuffers(1, &sp->libo);
|
|
#endif
|
|
|
|
sp->pibo = 0;
|
|
sp->libo = 0;
|
|
|
|
/* Release this surface's polygon and line vectors. */
|
|
|
|
if (sp->pv) free(sp->pv);
|
|
if (sp->lv) free(sp->lv);
|
|
}
|
|
|
|
static void obj_rel(obj *O)
|
|
{
|
|
int si;
|
|
int mi;
|
|
|
|
/* Release resources held by this file and it's materials and surfaces. */
|
|
|
|
#ifndef CONF_NO_GL
|
|
if (O->vbo) glDeleteBuffers (1, &O->vbo);
|
|
if (O->vao) glDeleteVertexArrays(1, &O->vao);
|
|
#endif
|
|
|
|
O->vbo = 0;
|
|
|
|
for (mi = 0; mi < O->mc; ++mi) obj_rel_mtrl(O->mv + mi);
|
|
for (si = 0; si < O->sc; ++si) obj_rel_surf(O->sv + si);
|
|
}
|
|
|
|
/*============================================================================*/
|
|
|
|
obj *obj_create(const char *filename)
|
|
{
|
|
obj *O;
|
|
int i;
|
|
|
|
/* Allocate and initialize a new file. */
|
|
|
|
if ((O = (obj *) calloc(1, sizeof (obj))))
|
|
{
|
|
if (filename)
|
|
{
|
|
/* Read the named file. */
|
|
|
|
read_obj(O, filename);
|
|
|
|
/* Post-process the loaded object. */
|
|
|
|
obj_mini(O);
|
|
obj_proc(O);
|
|
}
|
|
|
|
/* Set default shader locations. */
|
|
|
|
for (i = 0; i < OBJ_PROP_COUNT; i++)
|
|
{
|
|
O->cloc[i] = -1;
|
|
O->oloc[i] = -1;
|
|
O->Mloc[i] = -1;
|
|
}
|
|
O->uloc = -1;
|
|
O->nloc = -1;
|
|
O->tloc = -1;
|
|
O->vloc = -1;
|
|
}
|
|
return O;
|
|
}
|
|
|
|
void obj_delete(obj *O)
|
|
{
|
|
assert(O);
|
|
|
|
obj_rel(O);
|
|
|
|
free(O);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
int obj_add_mtrl(obj *O)
|
|
{
|
|
unsigned int opt = 0;
|
|
|
|
const float Kd[4] = { 0.8f, 0.8f, 0.8f, 1.0f };
|
|
const float Ka[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
|
|
const float Ke[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
|
|
const float Ks[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
|
|
const float Ns[4] = { 8.0f, 0.0f, 0.0f, 0.0f };
|
|
const float s[3] = { 1.0f, 1.0f, 1.0f };
|
|
|
|
int mi;
|
|
|
|
assert(O);
|
|
|
|
/* Allocate and initialize a new material. */
|
|
|
|
if ((mi = add__((void **) &O->mv,
|
|
&O->mc,
|
|
&O->mm, sizeof (struct obj_mtrl))) >= 0)
|
|
{
|
|
memset(O->mv + mi, 0, sizeof (struct obj_mtrl));
|
|
|
|
obj_set_mtrl_opt(O, mi, OBJ_KD, opt);
|
|
obj_set_mtrl_opt(O, mi, OBJ_KA, opt);
|
|
obj_set_mtrl_opt(O, mi, OBJ_KE, opt);
|
|
obj_set_mtrl_opt(O, mi, OBJ_KS, opt);
|
|
obj_set_mtrl_opt(O, mi, OBJ_NS, opt);
|
|
obj_set_mtrl_opt(O, mi, OBJ_KN, opt);
|
|
|
|
obj_set_mtrl_c (O, mi, OBJ_KD, Kd);
|
|
obj_set_mtrl_c (O, mi, OBJ_KA, Ka);
|
|
obj_set_mtrl_c (O, mi, OBJ_KE, Ke);
|
|
obj_set_mtrl_c (O, mi, OBJ_KS, Ks);
|
|
obj_set_mtrl_c (O, mi, OBJ_NS, Ns);
|
|
|
|
obj_set_mtrl_s (O, mi, OBJ_KD, s);
|
|
obj_set_mtrl_s (O, mi, OBJ_KA, s);
|
|
obj_set_mtrl_s (O, mi, OBJ_KE, s);
|
|
obj_set_mtrl_s (O, mi, OBJ_KS, s);
|
|
obj_set_mtrl_s (O, mi, OBJ_NS, s);
|
|
obj_set_mtrl_s (O, mi, OBJ_KN, s);
|
|
}
|
|
return mi;
|
|
}
|
|
|
|
int obj_add_vert(obj *O)
|
|
{
|
|
int vi;
|
|
|
|
assert(O);
|
|
|
|
/* Allocate and initialize a new vertex. */
|
|
|
|
if ((vi = add__((void **) &O->vv,
|
|
&O->vc,
|
|
&O->vm, sizeof (struct obj_vert))) >= 0)
|
|
|
|
memset(O->vv + vi, 0, sizeof (struct obj_vert));
|
|
|
|
return vi;
|
|
}
|
|
|
|
int obj_add_poly(obj *O, int si)
|
|
{
|
|
int pi;
|
|
|
|
assert_surf(O, si);
|
|
|
|
/* Allocate and initialize a new polygon. */
|
|
|
|
if ((pi = add__((void **) &O->sv[si].pv,
|
|
&O->sv[si].pc,
|
|
&O->sv[si].pm, sizeof (struct obj_poly)))>=0)
|
|
|
|
memset(O->sv[si].pv + pi, 0, sizeof (struct obj_poly));
|
|
|
|
return pi;
|
|
}
|
|
|
|
int obj_add_line(obj *O, int si)
|
|
{
|
|
int li;
|
|
|
|
assert_surf(O, si);
|
|
|
|
/* Allocate and initialize a new line. */
|
|
|
|
if ((li = add__((void **) &O->sv[si].lv,
|
|
&O->sv[si].lc,
|
|
&O->sv[si].lm, sizeof (struct obj_line)))>=0)
|
|
|
|
memset(O->sv[si].lv + li, 0, sizeof (struct obj_line));
|
|
|
|
return li;
|
|
}
|
|
|
|
int obj_add_surf(obj *O)
|
|
{
|
|
int si;
|
|
|
|
assert(O);
|
|
|
|
/* Allocate and initialize a new surface. */
|
|
|
|
if ((si = add__((void **) &O->sv,
|
|
&O->sc,
|
|
&O->sm, sizeof (struct obj_surf))) >= 0)
|
|
|
|
memset(O->sv + si, 0, sizeof (struct obj_surf));
|
|
|
|
return si;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
int obj_num_mtrl(const obj *O)
|
|
{
|
|
assert(O);
|
|
return O->mc;
|
|
}
|
|
|
|
int obj_num_vert(const obj *O)
|
|
{
|
|
assert(O);
|
|
return O->vc;
|
|
}
|
|
|
|
int obj_num_poly(const obj *O, int si)
|
|
{
|
|
assert_surf(O, si);
|
|
return O->sv[si].pc;
|
|
}
|
|
|
|
int obj_num_line(const obj *O, int si)
|
|
{
|
|
assert_surf(O, si);
|
|
return O->sv[si].lc;
|
|
}
|
|
|
|
int obj_num_surf(const obj *O)
|
|
{
|
|
assert(O);
|
|
return O->sc;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void obj_del_mtrl(obj *O, int mi)
|
|
{
|
|
int si;
|
|
|
|
assert_mtrl(O, mi);
|
|
|
|
/* Remove this material from the material vector. */
|
|
|
|
obj_rel_mtrl(O->mv + mi);
|
|
|
|
memmove(O->mv + mi,
|
|
O->mv + mi + 1,
|
|
(O->mc - mi - 1) * sizeof (struct obj_mtrl));
|
|
|
|
O->mc--;
|
|
|
|
/* Remove all references to this material. */
|
|
|
|
for (si = O->sc - 1; si >= 0; --si)
|
|
{
|
|
struct obj_surf *sp = O->sv + si;
|
|
|
|
if (sp->mi == mi)
|
|
obj_del_surf(O, si);
|
|
else
|
|
if (sp->mi > mi)
|
|
sp->mi--;
|
|
}
|
|
}
|
|
|
|
void obj_del_vert(obj *O, int vi)
|
|
{
|
|
int si;
|
|
int pi;
|
|
int li;
|
|
|
|
assert_vert(O, vi);
|
|
|
|
/* Remove this vertex from the file's vertex vector. */
|
|
|
|
memmove(O->vv + vi,
|
|
O->vv + vi + 1,
|
|
(O->vc - vi - 1) * sizeof (struct obj_vert));
|
|
|
|
O->vc--;
|
|
|
|
/* Remove all references to this vertex from all surfaces. */
|
|
|
|
for (si = 0; si < O->sc; ++si)
|
|
{
|
|
/* Delete all referencing polygons. Decrement later references. */
|
|
|
|
for (pi = O->sv[si].pc - 1; pi >= 0; --pi)
|
|
{
|
|
struct obj_poly *pp = O->sv[si].pv + pi;
|
|
|
|
if (pp->vi[0] == vi || pp->vi[1] == vi || pp->vi[2] == vi)
|
|
obj_del_poly(O, si, pi);
|
|
else
|
|
{
|
|
if (pp->vi[0] > vi) pp->vi[0]--;
|
|
if (pp->vi[1] > vi) pp->vi[1]--;
|
|
if (pp->vi[2] > vi) pp->vi[2]--;
|
|
}
|
|
}
|
|
|
|
/* Delete all referencing lines. Decrement later references. */
|
|
|
|
for (li = O->sv[si].lc - 1; li >= 0; --li)
|
|
{
|
|
struct obj_line *lp = O->sv[si].lv + li;
|
|
|
|
if (lp->vi[0] == vi || lp->vi[1] == vi)
|
|
obj_del_line(O, si, li);
|
|
else
|
|
{
|
|
if (lp->vi[0] > vi) lp->vi[0]--;
|
|
if (lp->vi[1] > vi) lp->vi[1]--;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Schedule the VBO for refresh. */
|
|
|
|
invalidate(O);
|
|
}
|
|
|
|
void obj_del_poly(obj *O, int si, int pi)
|
|
{
|
|
assert_poly(O, si, pi);
|
|
|
|
/* Remove this polygon from the surface's polygon vector. */
|
|
|
|
memmove(O->sv[si].pv + pi,
|
|
O->sv[si].pv + pi + 1,
|
|
(O->sv[si].pc - pi - 1) * sizeof (struct obj_poly));
|
|
|
|
O->sv[si].pc--;
|
|
}
|
|
|
|
void obj_del_line(obj *O, int si, int li)
|
|
{
|
|
assert_line(O, si, li);
|
|
|
|
/* Remove this line from the surface's line vector. */
|
|
|
|
memmove(O->sv[si].lv + li,
|
|
O->sv[si].lv + li + 1,
|
|
(O->sv[si].lc - li - 1) * sizeof (struct obj_line));
|
|
|
|
O->sv[si].lc--;
|
|
}
|
|
|
|
void obj_del_surf(obj *O, int si)
|
|
{
|
|
assert_surf(O, si);
|
|
|
|
/* Remove this surface from the file's surface vector. */
|
|
|
|
obj_rel_surf(O->sv + si);
|
|
|
|
memmove(O->sv + si,
|
|
O->sv + si + 1,
|
|
(O->sc - si - 1) * sizeof (struct obj_surf));
|
|
|
|
O->sc--;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
static char *set_name(char *old, const char *src)
|
|
{
|
|
char *dst = NULL;
|
|
|
|
if (old)
|
|
free(old);
|
|
|
|
if (src && (dst = (char *) malloc(strlen(src) + 1)))
|
|
strcpy(dst, src);
|
|
|
|
return dst;
|
|
}
|
|
|
|
void obj_set_mtrl_name(obj *O, int mi, const char *name)
|
|
{
|
|
assert_mtrl(O, mi);
|
|
O->mv[mi].name = set_name(O->mv[mi].name, name);
|
|
}
|
|
|
|
void obj_set_mtrl_map(obj *O, int mi, int ki, const char *str)
|
|
{
|
|
assert_prop(O, mi, ki);
|
|
|
|
#ifndef CONF_NO_GL
|
|
if (O->mv[mi].kv[ki].map)
|
|
glDeleteTextures(1, &O->mv[mi].kv[ki].map);
|
|
#endif
|
|
|
|
O->mv[mi].kv[ki].map = obj_load_image(str);
|
|
O->mv[mi].kv[ki].str = set_name(O->mv[mi].kv[ki].str, str);
|
|
}
|
|
|
|
void obj_set_mtrl_opt(obj *O, int mi, int ki, unsigned int opt)
|
|
{
|
|
assert_prop(O, mi, ki);
|
|
|
|
O->mv[mi].kv[ki].opt = opt;
|
|
}
|
|
|
|
void obj_set_mtrl_c(obj *O, int mi, int ki, const float c[4])
|
|
{
|
|
assert_prop(O, mi, ki);
|
|
|
|
O->mv[mi].kv[ki].c[0] = c[0];
|
|
O->mv[mi].kv[ki].c[1] = c[1];
|
|
O->mv[mi].kv[ki].c[2] = c[2];
|
|
O->mv[mi].kv[ki].c[3] = c[3];
|
|
}
|
|
|
|
void obj_set_mtrl_s(obj *O, int mi, int ki, const float s[3])
|
|
{
|
|
assert_prop(O, mi, ki);
|
|
|
|
O->mv[mi].kv[ki].s[0] = s[0];
|
|
O->mv[mi].kv[ki].s[1] = s[1];
|
|
O->mv[mi].kv[ki].s[2] = s[2];
|
|
}
|
|
|
|
void obj_set_mtrl_o(obj *O, int mi, int ki, const float o[3])
|
|
{
|
|
assert_prop(O, mi, ki);
|
|
|
|
O->mv[mi].kv[ki].o[0] = o[0];
|
|
O->mv[mi].kv[ki].o[1] = o[1];
|
|
O->mv[mi].kv[ki].o[2] = o[2];
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
static void invalidate(obj *O)
|
|
{
|
|
#ifndef CONF_NO_GL
|
|
if (O->vbo) glDeleteBuffers (1, &O->vbo);
|
|
if (O->vao) glDeleteVertexArrays(1, &O->vao);
|
|
#endif
|
|
O->vbo = 0;
|
|
O->vao = 0;
|
|
}
|
|
|
|
void obj_set_vert_v(obj *O, int vi, const float v[3])
|
|
{
|
|
assert_vert(O, vi);
|
|
|
|
O->vv[vi].v[0] = v[0];
|
|
O->vv[vi].v[1] = v[1];
|
|
O->vv[vi].v[2] = v[2];
|
|
|
|
invalidate(O);
|
|
}
|
|
|
|
void obj_set_vert_t(obj *O, int vi, const float t[2])
|
|
{
|
|
assert_vert(O, vi);
|
|
|
|
O->vv[vi].t[0] = t[0];
|
|
O->vv[vi].t[1] = t[1];
|
|
|
|
invalidate(O);
|
|
}
|
|
|
|
void obj_set_vert_n(obj *O, int vi, const float n[3])
|
|
{
|
|
assert_vert(O, vi);
|
|
|
|
O->vv[vi].n[0] = n[0];
|
|
O->vv[vi].n[1] = n[1];
|
|
O->vv[vi].n[2] = n[2];
|
|
|
|
invalidate(O);
|
|
}
|
|
|
|
void obj_set_vert_u(obj *O, int vi, const float u[3])
|
|
{
|
|
assert_vert(O, vi);
|
|
|
|
O->vv[vi].u[0] = u[0];
|
|
O->vv[vi].u[1] = u[1];
|
|
O->vv[vi].u[2] = u[2];
|
|
|
|
invalidate(O);
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void obj_set_poly(obj *O, int si, int pi, const int vi[3])
|
|
{
|
|
assert_poly(O, si, pi);
|
|
|
|
O->sv[si].pv[pi].vi[0] = (index_t) vi[0];
|
|
O->sv[si].pv[pi].vi[1] = (index_t) vi[1];
|
|
O->sv[si].pv[pi].vi[2] = (index_t) vi[2];
|
|
}
|
|
|
|
void obj_set_line(obj *O, int si, int li, const int vi[2])
|
|
{
|
|
assert_line(O, si, li);
|
|
|
|
O->sv[si].lv[li].vi[0] = (index_t) vi[0];
|
|
O->sv[si].lv[li].vi[1] = (index_t) vi[1];
|
|
}
|
|
|
|
void obj_set_surf(obj *O, int si, int mi)
|
|
{
|
|
assert_surf(O, si);
|
|
|
|
O->sv[si].mi = mi;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void obj_set_vert_loc(obj *O, int u, int n, int t, int v)
|
|
{
|
|
assert(O);
|
|
|
|
O->uloc = u;
|
|
O->nloc = n;
|
|
O->tloc = t;
|
|
O->vloc = v;
|
|
|
|
invalidate(O);
|
|
}
|
|
|
|
void obj_set_prop_loc(obj *O, int ki, int c, int o, int M)
|
|
{
|
|
assert(O);
|
|
assert(0 <= ki && ki < OBJ_PROP_COUNT);
|
|
|
|
O->cloc[ki] = c;
|
|
O->oloc[ki] = o;
|
|
O->Mloc[ki] = M;
|
|
}
|
|
|
|
/*============================================================================*/
|
|
|
|
const char *obj_get_mtrl_name(const obj *O, int mi)
|
|
{
|
|
assert_mtrl(O, mi);
|
|
return O->mv[mi].name;
|
|
}
|
|
|
|
unsigned int obj_get_mtrl_map(const obj *O, int mi, int ki)
|
|
{
|
|
assert_prop(O, mi, ki);
|
|
return O->mv[mi].kv[ki].map;
|
|
}
|
|
|
|
unsigned int obj_get_mtrl_opt(const obj *O, int mi, int ki)
|
|
{
|
|
assert_prop(O, mi, ki);
|
|
return O->mv[mi].kv[ki].opt;
|
|
}
|
|
|
|
void obj_get_mtrl_c(const obj *O, int mi, int ki, float *c)
|
|
{
|
|
assert_prop(O, mi, ki);
|
|
|
|
c[0] = O->mv[mi].kv[ki].c[0];
|
|
c[1] = O->mv[mi].kv[ki].c[1];
|
|
c[2] = O->mv[mi].kv[ki].c[2];
|
|
c[3] = O->mv[mi].kv[ki].c[3];
|
|
}
|
|
|
|
void obj_get_mtrl_s(const obj *O, int mi, int ki, float *s)
|
|
{
|
|
assert_prop(O, mi, ki);
|
|
|
|
s[0] = O->mv[mi].kv[ki].s[0];
|
|
s[1] = O->mv[mi].kv[ki].s[1];
|
|
s[2] = O->mv[mi].kv[ki].s[2];
|
|
}
|
|
|
|
void obj_get_mtrl_o(const obj *O, int mi, int ki, float *o)
|
|
{
|
|
assert_prop(O, mi, ki);
|
|
|
|
o[0] = O->mv[mi].kv[ki].o[0];
|
|
o[1] = O->mv[mi].kv[ki].o[1];
|
|
o[2] = O->mv[mi].kv[ki].o[2];
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void obj_get_vert_v(const obj *O, int vi, float *v)
|
|
{
|
|
assert_vert(O, vi);
|
|
|
|
v[0] = O->vv[vi].v[0];
|
|
v[1] = O->vv[vi].v[1];
|
|
v[2] = O->vv[vi].v[2];
|
|
}
|
|
|
|
void obj_get_vert_t(const obj *O, int vi, float *t)
|
|
{
|
|
assert_vert(O, vi);
|
|
|
|
t[0] = O->vv[vi].t[0];
|
|
t[1] = O->vv[vi].t[1];
|
|
}
|
|
|
|
void obj_get_vert_n(const obj *O, int vi, float *n)
|
|
{
|
|
assert_vert(O, vi);
|
|
|
|
n[0] = O->vv[vi].n[0];
|
|
n[1] = O->vv[vi].n[1];
|
|
n[2] = O->vv[vi].n[2];
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void obj_get_poly(const obj *O, int si, int pi, int *vi)
|
|
{
|
|
assert_poly(O, si, pi);
|
|
|
|
vi[0] = (int) O->sv[si].pv[pi].vi[0];
|
|
vi[1] = (int) O->sv[si].pv[pi].vi[1];
|
|
vi[2] = (int) O->sv[si].pv[pi].vi[2];
|
|
}
|
|
|
|
void obj_get_line(const obj *O, int si, int li, int *vi)
|
|
{
|
|
assert_line(O, si, li);
|
|
|
|
vi[0] = (int) O->sv[si].lv[li].vi[0];
|
|
vi[1] = (int) O->sv[si].lv[li].vi[1];
|
|
}
|
|
|
|
int obj_get_surf(const obj *O, int si)
|
|
{
|
|
assert_surf(O, si);
|
|
return O->sv[si].mi;
|
|
}
|
|
|
|
/*============================================================================*/
|
|
|
|
void obj_mini(obj *O)
|
|
{
|
|
int si;
|
|
int mi;
|
|
|
|
/* Remove empty surfaces. */
|
|
|
|
for (si = O->sc - 1; si >= 0; --si)
|
|
if (O->sv[si].pc == 0 &&
|
|
O->sv[si].lc == 0)
|
|
obj_del_surf(O, si);
|
|
|
|
/* Remove unreferenced materials. */
|
|
|
|
for (mi = O->mc - 1; mi >= 0; --mi)
|
|
{
|
|
int cc = 0;
|
|
|
|
for (si = 0; si < O->sc; ++si)
|
|
if (O->sv[si].mi == mi)
|
|
cc++;
|
|
|
|
if (cc == 0)
|
|
obj_del_mtrl(O, mi);
|
|
}
|
|
}
|
|
|
|
void obj_norm(obj *O)
|
|
{
|
|
int vi;
|
|
int si;
|
|
int pi;
|
|
|
|
assert(O);
|
|
|
|
/* Zero the normals for all vertices. */
|
|
|
|
for (vi = 0; vi < O->vc; ++vi)
|
|
{
|
|
O->vv[vi].n[0] = 0;
|
|
O->vv[vi].n[1] = 0;
|
|
O->vv[vi].n[2] = 0;
|
|
}
|
|
|
|
/* Compute normals for all faces. */
|
|
|
|
for (si = 0; si < O->sc; ++si)
|
|
for (pi = 0; pi < O->sv[si].pc; ++pi)
|
|
{
|
|
struct obj_vert *v0 = O->vv + O->sv[si].pv[pi].vi[0];
|
|
struct obj_vert *v1 = O->vv + O->sv[si].pv[pi].vi[1];
|
|
struct obj_vert *v2 = O->vv + O->sv[si].pv[pi].vi[2];
|
|
|
|
float n[3];
|
|
|
|
/* Compute the normal formed by these 3 vertices. */
|
|
|
|
normal(n, v0->v, v1->v, v2->v);
|
|
|
|
/* Sum this normal to all vertices. */
|
|
|
|
v0->n[0] += n[0];
|
|
v0->n[1] += n[1];
|
|
v0->n[2] += n[2];
|
|
|
|
v1->n[0] += n[0];
|
|
v1->n[1] += n[1];
|
|
v1->n[2] += n[2];
|
|
|
|
v2->n[0] += n[0];
|
|
v2->n[1] += n[1];
|
|
v2->n[2] += n[2];
|
|
}
|
|
}
|
|
|
|
void obj_proc(obj *O)
|
|
{
|
|
int si;
|
|
int sj;
|
|
int pi;
|
|
int vi;
|
|
|
|
assert(O);
|
|
|
|
/* Normalize all normals. Zero all tangent vectors. */
|
|
|
|
for (vi = 0; vi < O->vc; ++vi)
|
|
{
|
|
normalize(O->vv[vi].n);
|
|
|
|
O->vv[vi].u[0] = 0.0f;
|
|
O->vv[vi].u[1] = 0.0f;
|
|
O->vv[vi].u[2] = 0.0f;
|
|
}
|
|
|
|
/* Compute tangent vectors for all vertices. */
|
|
|
|
for (si = 0; si < O->sc; ++si)
|
|
for (pi = 0; pi < O->sv[si].pc; ++pi)
|
|
{
|
|
struct obj_vert *v0 = O->vv + O->sv[si].pv[pi].vi[0];
|
|
struct obj_vert *v1 = O->vv + O->sv[si].pv[pi].vi[1];
|
|
struct obj_vert *v2 = O->vv + O->sv[si].pv[pi].vi[2];
|
|
|
|
float dt1, dv1[3];
|
|
float dt2, dv2[3];
|
|
|
|
float u[3];
|
|
|
|
/* Compute the tangent vector for this polygon. */
|
|
|
|
dv1[0] = v1->v[0] - v0->v[0];
|
|
dv1[1] = v1->v[1] - v0->v[1];
|
|
dv1[2] = v1->v[2] - v0->v[2];
|
|
|
|
dv2[0] = v2->v[0] - v0->v[0];
|
|
dv2[1] = v2->v[1] - v0->v[1];
|
|
dv2[2] = v2->v[2] - v0->v[2];
|
|
|
|
dt1 = v1->t[1] - v0->t[1];
|
|
dt2 = v2->t[1] - v0->t[1];
|
|
|
|
u[0] = dt2 * dv1[0] - dt1 * dv2[0];
|
|
u[1] = dt2 * dv1[1] - dt1 * dv2[1];
|
|
u[2] = dt2 * dv1[2] - dt1 * dv2[2];
|
|
|
|
normalize(u);
|
|
|
|
/* Accumulate the tangent vectors for this polygon's vertices. */
|
|
|
|
v0->u[0] += u[0]; v0->u[1] += u[1]; v0->u[2] += u[2];
|
|
v1->u[0] += u[0]; v1->u[1] += u[1]; v1->u[2] += u[2];
|
|
v2->u[0] += u[0]; v2->u[1] += u[1]; v2->u[2] += u[2];
|
|
}
|
|
|
|
/* Orthonormalize each tangent basis. */
|
|
|
|
for (vi = 0; vi < O->vc; ++vi)
|
|
{
|
|
float *n = O->vv[vi].n;
|
|
float *u = O->vv[vi].u;
|
|
|
|
float v[3];
|
|
|
|
cross(v, n, u);
|
|
cross(u, v, n);
|
|
normalize(u);
|
|
}
|
|
|
|
/* Sort surfaces such that transparent ones appear later. */
|
|
|
|
for (si = 0; si < O->sc; ++si)
|
|
for (sj = si + 1; sj < O->sc; ++sj)
|
|
if (O->mv[O->sv[si].mi].kv[OBJ_KD].c[3] <
|
|
O->mv[O->sv[sj].mi].kv[OBJ_KD].c[3])
|
|
{
|
|
struct obj_surf temp;
|
|
|
|
temp = O->sv[si];
|
|
O->sv[si] = O->sv[sj];
|
|
O->sv[sj] = temp;
|
|
}
|
|
}
|
|
|
|
void obj_init(obj *O)
|
|
{
|
|
#ifndef CONF_NO_GL
|
|
if (O->vao == 0)
|
|
{
|
|
const size_t vs = sizeof (struct obj_vert);
|
|
const size_t ps = sizeof (struct obj_poly);
|
|
const size_t ls = sizeof (struct obj_line);
|
|
|
|
int si;
|
|
|
|
/* Store the following bindings in a vertex array object. */
|
|
|
|
glGenVertexArrays(1, &O->vao);
|
|
glBindVertexArray( O->vao);
|
|
|
|
/* Store all vertex data in a vertex buffer object. */
|
|
|
|
glGenBuffers(1, &O->vbo);
|
|
glBindBuffer(GL_ARRAY_BUFFER, O->vbo);
|
|
glBufferData(GL_ARRAY_BUFFER, O->vc * vs, O->vv, GL_STATIC_DRAW);
|
|
|
|
/* Store all index data in index buffer objects. */
|
|
|
|
for (si = 0; si < O->sc; ++si)
|
|
{
|
|
if (O->sv[si].pc > 0)
|
|
{
|
|
glGenBuffers(1, &O->sv[si].pibo);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, O->sv[si].pibo);
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, O->sv[si].pc * ps,
|
|
O->sv[si].pv, GL_STATIC_DRAW);
|
|
}
|
|
|
|
if (O->sv[si].lc > 0)
|
|
{
|
|
glGenBuffers(1, &O->sv[si].libo);
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, O->sv[si].libo);
|
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, O->sv[si].lc * ls,
|
|
O->sv[si].lv, GL_STATIC_DRAW);
|
|
}
|
|
}
|
|
|
|
/* Enable and bind the attributes. */
|
|
|
|
if (O->uloc >= 0)
|
|
{
|
|
glEnableVertexAttribArray(O->uloc);
|
|
glVertexAttribPointer(O->uloc, 3, GL_FLOAT, GL_FALSE, vs, (const GLvoid *) 0);
|
|
}
|
|
if (O->nloc >= 0)
|
|
{
|
|
glEnableVertexAttribArray(O->nloc);
|
|
glVertexAttribPointer(O->nloc, 3, GL_FLOAT, GL_FALSE, vs, (const GLvoid *) 12);
|
|
}
|
|
if (O->tloc >= 0)
|
|
{
|
|
glEnableVertexAttribArray(O->tloc);
|
|
glVertexAttribPointer(O->tloc, 2, GL_FLOAT, GL_FALSE, vs, (const GLvoid *) 24);
|
|
}
|
|
if (O->vloc >= 0)
|
|
{
|
|
glEnableVertexAttribArray(O->vloc);
|
|
glVertexAttribPointer(O->vloc, 3, GL_FLOAT, GL_FALSE, vs, (const GLvoid *) 32);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
int obj_cmp_vert(obj *O, int vi, int vj, float eps, float dot)
|
|
{
|
|
if (fabs(O->vv[vi].v[0] - O->vv[vj].v[0]) >= eps) return 0;
|
|
if (fabs(O->vv[vi].v[1] - O->vv[vj].v[1]) >= eps) return 0;
|
|
if (fabs(O->vv[vi].v[2] - O->vv[vj].v[2]) >= eps) return 0;
|
|
|
|
if (fabs(O->vv[vi].t[0] - O->vv[vj].t[0]) >= eps) return 0;
|
|
if (fabs(O->vv[vi].t[1] - O->vv[vj].t[1]) >= eps) return 0;
|
|
|
|
if (O->vv[vi].n[0] * O->vv[vj].n[0] +
|
|
O->vv[vi].n[1] * O->vv[vj].n[1] +
|
|
O->vv[vi].n[2] * O->vv[vj].n[2] < dot) return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
void obj_swp_vert(obj *O, int vi, int vj)
|
|
{
|
|
int si;
|
|
int pi;
|
|
int li;
|
|
|
|
/* Replace all occurrences of vi with vj. */
|
|
|
|
for (si = 0; si < O->sc; ++si)
|
|
{
|
|
for (pi = 0; pi < O->sv[si].pc; ++pi)
|
|
{
|
|
if (O->sv[si].pv[pi].vi[0] == vi)
|
|
O->sv[si].pv[pi].vi[0] = vj;
|
|
if (O->sv[si].pv[pi].vi[1] == vi)
|
|
O->sv[si].pv[pi].vi[1] = vj;
|
|
if (O->sv[si].pv[pi].vi[2] == vi)
|
|
O->sv[si].pv[pi].vi[2] = vj;
|
|
}
|
|
for (li = 0; li < O->sv[si].lc; ++li)
|
|
{
|
|
if (O->sv[si].lv[li].vi[0] == vi)
|
|
O->sv[si].lv[li].vi[0] = vj;
|
|
if (O->sv[si].lv[li].vi[1] == vi)
|
|
O->sv[si].lv[li].vi[1] = vj;
|
|
}
|
|
}
|
|
}
|
|
|
|
void obj_uniq(obj *O, float eps, float dot, int verbose)
|
|
{
|
|
int vc = O->vc;
|
|
int vi;
|
|
int vj;
|
|
int di;
|
|
|
|
/* Merge all vertices within epsilon of one another. */
|
|
|
|
for (vi = 0; vi < O->vc; vi += di)
|
|
{
|
|
di = 1;
|
|
|
|
for (vj = 0; vj < vi; ++vj)
|
|
{
|
|
if (obj_cmp_vert(O, vi, vj, eps, dot))
|
|
{
|
|
if (verbose) printf("%d %d\n", vi, vc--);
|
|
|
|
obj_swp_vert(O, vi, vj);
|
|
obj_del_vert(O, vi);
|
|
|
|
di = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void obj_sort(obj *O, int qc)
|
|
{
|
|
const int vc = O->vc;
|
|
|
|
struct vert
|
|
{
|
|
int qs; /* Cache insertion serial number */
|
|
int *iv; /* Polygon reference list buffer */
|
|
int ic; /* Polygon reference list length */
|
|
};
|
|
|
|
/* Vertex optimization data; vertex FIFO cache */
|
|
|
|
struct vert *vv = (struct vert *) malloc(vc * sizeof (struct vert));
|
|
int *qv = (int *) malloc(qc * sizeof (int ));
|
|
|
|
int qs = 1; /* Current cache insertion serial number */
|
|
int qi = 0; /* Current cache insertion point [0, qc) */
|
|
|
|
int si;
|
|
int pi;
|
|
int vi;
|
|
int ii;
|
|
int qj;
|
|
|
|
/* Initialize the vertex cache to empty. */
|
|
|
|
for (qj = 0; qj < qc; ++qj)
|
|
qv[qj] = -1;
|
|
|
|
/* Process each surface of this file in turn. */
|
|
|
|
for (si = 0; si < O->sc; ++si)
|
|
{
|
|
const int pc = O->sv[si].pc;
|
|
|
|
/* Allocate the polygon reference list buffers. */
|
|
|
|
int *ip, *iv = (int *) malloc(3 * pc * sizeof (int));
|
|
|
|
/* Count the number of polygon references per vertex. */
|
|
|
|
memset(vv, 0, vc * sizeof (struct vert));
|
|
|
|
for (pi = 0; pi < pc; ++pi)
|
|
{
|
|
const index_t *i = O->sv[si].pv[pi].vi;
|
|
|
|
vv[i[0]].ic++;
|
|
vv[i[1]].ic++;
|
|
vv[i[2]].ic++;
|
|
}
|
|
|
|
/* Initialize all vertex optimization data. */
|
|
|
|
for (vi = 0, ip = iv; vi < vc; ++vi)
|
|
{
|
|
vv[vi].qs = -qc;
|
|
vv[vi].iv = ip;
|
|
ip += vv[vi].ic;
|
|
vv[vi].ic = 0;
|
|
}
|
|
|
|
/* Fill the polygon reference list buffers. */
|
|
|
|
for (pi = 0; pi < pc; ++pi)
|
|
{
|
|
const index_t *i = O->sv[si].pv[pi].vi;
|
|
|
|
vv[i[0]].iv[vv[i[0]].ic++] = pi;
|
|
vv[i[1]].iv[vv[i[1]].ic++] = pi;
|
|
vv[i[2]].iv[vv[i[2]].ic++] = pi;
|
|
}
|
|
|
|
/* Iterate over the polygon array of this surface. */
|
|
|
|
for (pi = 0; pi < pc; ++pi)
|
|
{
|
|
const index_t *i = O->sv[si].pv[pi].vi;
|
|
|
|
int qd = qs - qc;
|
|
|
|
int dk = -1; /* The best polygon score */
|
|
int pk = pi; /* The best polygon index */
|
|
|
|
/* Find the best polygon among those referred-to by the cache. */
|
|
|
|
for (qj = 0; qj < qc; ++qj)
|
|
if (qv[qj] >= 0)
|
|
|
|
for (ii = 0; ii < vv[qv[qj]].ic; ++ii)
|
|
{
|
|
int pj = vv[qv[qj]].iv[ii];
|
|
int dj = 0;
|
|
|
|
const index_t *j = O->sv[si].pv[pj].vi;
|
|
|
|
/* Recently-used vertex bonus. */
|
|
|
|
if (vv[j[0]].qs > qd) dj += vv[j[0]].qs - qd;
|
|
if (vv[j[1]].qs > qd) dj += vv[j[1]].qs - qd;
|
|
if (vv[j[2]].qs > qd) dj += vv[j[2]].qs - qd;
|
|
|
|
/* Low-valence vertex bonus. */
|
|
|
|
dj -= vv[j[0]].ic;
|
|
dj -= vv[j[1]].ic;
|
|
dj -= vv[j[2]].ic;
|
|
|
|
if (dk < dj)
|
|
{
|
|
dk = dj;
|
|
pk = pj;
|
|
}
|
|
}
|
|
|
|
if (pk != pi)
|
|
{
|
|
struct obj_poly temp;
|
|
|
|
/* Update the polygon reference list. */
|
|
|
|
for (vi = 0; vi < 3; ++vi)
|
|
for (ii = 0; ii < vv[i[vi]].ic; ++ii)
|
|
if (vv[i[vi]].iv[ii] == pi)
|
|
{
|
|
vv[i[vi]].iv[ii] = pk;
|
|
break;
|
|
}
|
|
|
|
/* Swap the best polygon into the current position. */
|
|
|
|
temp = O->sv[si].pv[pi];
|
|
O->sv[si].pv[pi] = O->sv[si].pv[pk];
|
|
O->sv[si].pv[pk] = temp;
|
|
}
|
|
|
|
/* Iterate over the current polygon's vertices. */
|
|
|
|
for (vi = 0; vi < 3; ++vi)
|
|
{
|
|
struct vert *vp = vv + i[vi];
|
|
|
|
/* If this vertex was a cache miss then queue it. */
|
|
|
|
if (qs - vp->qs >= qc)
|
|
{
|
|
vp->qs = qs++;
|
|
qv[qi] = i[vi];
|
|
qi = (qi + 1) % qc;
|
|
}
|
|
|
|
/* Remove the current polygon from the reference list. */
|
|
|
|
vp->ic--;
|
|
|
|
for (ii = 0; ii < vp->ic; ++ii)
|
|
if (vp->iv[ii] == pk)
|
|
{
|
|
vp->iv[ii] = vp->iv[vp->ic];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
free(iv);
|
|
}
|
|
free(qv);
|
|
free(vv);
|
|
}
|
|
|
|
float obj_acmr(obj *O, int qc)
|
|
{
|
|
int *vs = (int *) malloc(O->vc * sizeof (int));
|
|
int qs = 1;
|
|
|
|
int si;
|
|
int vi;
|
|
int pi;
|
|
|
|
int nn = 0;
|
|
int dd = 0;
|
|
|
|
for (si = 0; si < O->sc; ++si)
|
|
{
|
|
for (vi = 0; vi < O->vc; ++vi)
|
|
vs[vi] = -qc;
|
|
|
|
for (pi = 0; pi < O->sv[si].pc; ++pi)
|
|
{
|
|
const index_t *i = O->sv[si].pv[pi].vi;
|
|
|
|
if (qs - vs[i[0]] >= qc) { vs[i[0]] = qs++; nn++; }
|
|
if (qs - vs[i[1]] >= qc) { vs[i[1]] = qs++; nn++; }
|
|
if (qs - vs[i[2]] >= qc) { vs[i[2]] = qs++; nn++; }
|
|
|
|
dd++;
|
|
}
|
|
}
|
|
|
|
return (float) nn / (float) dd;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
#ifndef CONF_NO_GL
|
|
|
|
static void obj_render_prop(const obj *O, int mi, int ki)
|
|
{
|
|
const struct obj_prop *kp = O->mv[mi].kv + ki;
|
|
|
|
if (kp->map)
|
|
{
|
|
GLenum wrap = GL_REPEAT;
|
|
|
|
/* Bind the property map. */
|
|
|
|
glBindTexture(GL_TEXTURE_2D, kp->map);
|
|
|
|
/* Apply the property options. */
|
|
|
|
if (kp->opt & OBJ_OPT_CLAMP)
|
|
wrap = GL_CLAMP_TO_EDGE;
|
|
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrap);
|
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrap);
|
|
|
|
/* Apply the texture coordinate offset and scale. */
|
|
|
|
if (O->Mloc[ki] >= 0)
|
|
{
|
|
GLfloat T[16];
|
|
|
|
memset(T, 0, sizeof (T));
|
|
|
|
T[ 0] = kp->s[0];
|
|
T[ 5] = kp->s[1];
|
|
T[10] = kp->s[2];
|
|
T[12] = kp->o[0];
|
|
T[13] = kp->o[1];
|
|
T[14] = kp->o[2];
|
|
T[15] = 1.0f;
|
|
|
|
glUniformMatrix4fv(O->Mloc[ki], 1, GL_FALSE, T);
|
|
}
|
|
}
|
|
else glBindTexture(GL_TEXTURE_2D, 0);
|
|
}
|
|
|
|
void obj_render_mtrl(const obj *O, int mi)
|
|
{
|
|
int ki;
|
|
|
|
/* Bind all material properties and texture maps. */
|
|
|
|
for (ki = 0; ki < OBJ_PROP_COUNT; ki++)
|
|
{
|
|
if (O->oloc[ki] >= 0)
|
|
{
|
|
glActiveTexture(GL_TEXTURE0 + ki);
|
|
obj_render_prop(O, mi, ki);
|
|
glUniform1i(O->oloc[ki], ki);
|
|
}
|
|
if (O->cloc[ki] >= 0)
|
|
glUniform4fv(O->cloc[ki], 1, O->mv[mi].kv[ki].c);
|
|
}
|
|
glActiveTexture(GL_TEXTURE0);
|
|
}
|
|
|
|
void obj_render_surf(const obj *O, int si)
|
|
{
|
|
const struct obj_surf *sp = O->sv + si;
|
|
|
|
if (0 < sp->pc || sp->lc > 0)
|
|
{
|
|
/* Apply this surface's material. */
|
|
|
|
if (0 <= sp->mi && sp->mi < O->mc)
|
|
obj_render_mtrl(O, sp->mi);
|
|
|
|
/* Render all polygons. */
|
|
|
|
if (sp->pibo)
|
|
{
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, sp->pibo);
|
|
glDrawElements(GL_TRIANGLES, 3 * sp->pc, GL_INDEX_T, (const GLvoid *) 0);
|
|
}
|
|
|
|
/* Render all lines. */
|
|
|
|
if (sp->libo)
|
|
{
|
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, sp->libo);
|
|
glDrawElements(GL_LINES, 2 * sp->lc, GL_INDEX_T, (const GLvoid *) 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
void obj_render(obj *O)
|
|
{
|
|
int si;
|
|
|
|
assert(O);
|
|
|
|
/* Initialize the vertex arrays. */
|
|
|
|
obj_init(O);
|
|
|
|
/* Render each surface. */
|
|
|
|
glBindVertexArray(O->vao);
|
|
|
|
for (si = 0; si < O->sc; ++si)
|
|
obj_render_surf(O, si);
|
|
}
|
|
|
|
#else
|
|
|
|
void obj_render(obj *O)
|
|
{
|
|
}
|
|
|
|
#endif
|
|
|
|
/*============================================================================*/
|
|
|
|
void obj_bound(const obj *O, float *b)
|
|
{
|
|
int vi;
|
|
|
|
assert(O);
|
|
|
|
/* Compute the bounding box of this object. */
|
|
|
|
if (O->vc > 0)
|
|
{
|
|
const float *v = O->vv[0].v;
|
|
|
|
b[0] = b[3] = v[0];
|
|
b[1] = b[4] = v[1];
|
|
b[2] = b[5] = v[2];
|
|
}
|
|
|
|
for (vi = 0; vi < O->vc; ++vi)
|
|
{
|
|
const float *v = O->vv[vi].v;
|
|
|
|
if (b[0] > v[0]) b[0] = v[0];
|
|
if (b[1] > v[1]) b[1] = v[1];
|
|
if (b[2] > v[2]) b[2] = v[2];
|
|
|
|
if (b[3] < v[0]) b[3] = v[0];
|
|
if (b[4] < v[1]) b[4] = v[1];
|
|
if (b[5] < v[2]) b[5] = v[2];
|
|
}
|
|
}
|
|
|
|
/*============================================================================*/
|
|
|
|
static void obj_write_map(FILE *fout, const obj *O, int mi, int ki, const char *s)
|
|
{
|
|
struct obj_prop *kp = O->mv[mi].kv + ki;
|
|
|
|
/* If this property has a map... */
|
|
|
|
if (kp->str)
|
|
{
|
|
fprintf(fout, "map_%s ", s);
|
|
|
|
/* Store all map options. */
|
|
|
|
if ((kp->opt & OBJ_OPT_CLAMP) != 0) fprintf(fout, "-clamp on ");
|
|
|
|
/* Store the map offset, if any. */
|
|
|
|
if (fabs(kp->o[0]) > 0 ||
|
|
fabs(kp->o[1]) > 0 ||
|
|
fabs(kp->o[2]) > 0) fprintf(fout, "-o %f %f %f ",
|
|
kp->o[0], kp->o[1], kp->o[2]);
|
|
|
|
/* Store the map scale, if any. */
|
|
|
|
if (fabs(kp->s[0] - 1) > 0 ||
|
|
fabs(kp->s[1] - 1) > 0 ||
|
|
fabs(kp->s[2] - 1) > 0) fprintf(fout, "-s %f %f %f ",
|
|
kp->s[0], kp->s[1], kp->s[2]);
|
|
|
|
/* Store the map image file name. */
|
|
|
|
fprintf(fout, "%s\n", kp->str);
|
|
}
|
|
}
|
|
|
|
static void obj_write_mtl(const obj *O, const char *mtl)
|
|
{
|
|
FILE *fout;
|
|
int mi;
|
|
|
|
if ((fout = fopen(mtl, "w")))
|
|
{
|
|
for (mi = 0; mi < O->mc; ++mi)
|
|
{
|
|
struct obj_mtrl *mp = O->mv + mi;
|
|
|
|
/* Start a new material. */
|
|
|
|
if (mp->name)
|
|
fprintf(fout, "newmtl %s\n", mp->name);
|
|
else
|
|
fprintf(fout, "newmtl default\n");
|
|
|
|
/* Store all material property colors. */
|
|
|
|
fprintf(fout, "Kd %12.8f %12.8f %12.8f\n", mp->kv[OBJ_KD].c[0],
|
|
mp->kv[OBJ_KD].c[1],
|
|
mp->kv[OBJ_KD].c[2]);
|
|
fprintf(fout, "Ka %12.8f %12.8f %12.8f\n", mp->kv[OBJ_KA].c[0],
|
|
mp->kv[OBJ_KA].c[1],
|
|
mp->kv[OBJ_KA].c[2]);
|
|
fprintf(fout, "Ke %12.8f %12.8f %12.8f\n", mp->kv[OBJ_KE].c[0],
|
|
mp->kv[OBJ_KE].c[1],
|
|
mp->kv[OBJ_KE].c[2]);
|
|
fprintf(fout, "Ks %12.8f %12.8f %12.8f\n", mp->kv[OBJ_KS].c[0],
|
|
mp->kv[OBJ_KS].c[1],
|
|
mp->kv[OBJ_KS].c[2]);
|
|
|
|
fprintf(fout, "Ns %12.8f\n", mp->kv[OBJ_NS].c[0]);
|
|
fprintf(fout, "d %12.8f\n", mp->kv[OBJ_KD].c[3]);
|
|
|
|
/* Store all material property maps. */
|
|
|
|
obj_write_map(fout, O, mi, OBJ_KD, "Kd");
|
|
obj_write_map(fout, O, mi, OBJ_KA, "Ka");
|
|
obj_write_map(fout, O, mi, OBJ_KA, "Ke");
|
|
obj_write_map(fout, O, mi, OBJ_KS, "Ks");
|
|
obj_write_map(fout, O, mi, OBJ_NS, "Ns");
|
|
obj_write_map(fout, O, mi, OBJ_KN, "Kn");
|
|
}
|
|
}
|
|
fclose(fout);
|
|
}
|
|
|
|
static void obj_write_obj(const obj *O, const char *obj,
|
|
const char *mtl, int prec)
|
|
{
|
|
FILE *fout;
|
|
|
|
if ((fout = fopen(obj, "w")))
|
|
{
|
|
char formv[256];
|
|
char formt[256];
|
|
char formn[256];
|
|
|
|
int si;
|
|
int vi;
|
|
int pi;
|
|
int li;
|
|
|
|
if (mtl) fprintf(fout, "mtllib %s\n", mtl);
|
|
|
|
/* Store all vertex data. */
|
|
|
|
sprintf(formv, "v %%.%df %%.%df %%.%df\n", prec, prec, prec);
|
|
sprintf(formt, "vt %%.%df %%.%df\n", prec, prec);
|
|
sprintf(formn, "vn %%.%df %%.%df %%.%df\n", prec, prec, prec);
|
|
|
|
for (vi = 0; vi < O->vc; ++vi)
|
|
fprintf(fout, formv, O->vv[vi].v[0],
|
|
O->vv[vi].v[1],
|
|
O->vv[vi].v[2]);
|
|
for (vi = 0; vi < O->vc; ++vi)
|
|
fprintf(fout, formt, O->vv[vi].t[0],
|
|
O->vv[vi].t[1]);
|
|
for (vi = 0; vi < O->vc; ++vi)
|
|
fprintf(fout, formn, O->vv[vi].n[0],
|
|
O->vv[vi].n[1],
|
|
O->vv[vi].n[2]);
|
|
|
|
for (si = 0; si < O->sc; ++si)
|
|
{
|
|
int mi = O->sv[si].mi;
|
|
|
|
/* Store the surface's material reference */
|
|
|
|
if (0 <= mi && mi < O->mc && O->mv[mi].name)
|
|
fprintf(fout, "usemtl %s\n", O->mv[O->sv[si].mi].name);
|
|
else
|
|
fprintf(fout, "usemtl default\n");
|
|
|
|
/* Store all polygon definitions. */
|
|
|
|
for (pi = 0; pi < O->sv[si].pc; pi++)
|
|
{
|
|
int vi0 = O->sv[si].pv[pi].vi[0] + 1;
|
|
int vi1 = O->sv[si].pv[pi].vi[1] + 1;
|
|
int vi2 = O->sv[si].pv[pi].vi[2] + 1;
|
|
|
|
fprintf(fout, "f %d/%d/%d %d/%d/%d %d/%d/%d\n", vi0, vi0, vi0,
|
|
vi1, vi1, vi1,
|
|
vi2, vi2, vi2);
|
|
}
|
|
|
|
/* Store all line definitions. */
|
|
|
|
for (li = 0; li < O->sv[si].lc; li++)
|
|
{
|
|
int vi0 = O->sv[si].lv[li].vi[0] + 1;
|
|
int vi1 = O->sv[si].lv[li].vi[1] + 1;
|
|
|
|
fprintf(fout, "l %d/%d/%d %d/%d/%d\n", vi0, vi0, vi0,
|
|
vi1, vi1, vi1);
|
|
}
|
|
}
|
|
|
|
fclose(fout);
|
|
}
|
|
}
|
|
|
|
void obj_write(const obj *O, const char *obj, const char *mtl, const char *outPath, int prec)
|
|
{
|
|
char objPath[MAXSTR];
|
|
char mtlPath[MAXSTR];
|
|
|
|
#ifdef _WIN32
|
|
#define EXPORT_FORMAT "%s\\%s"
|
|
#else
|
|
#define EXPORT_FORMAT "%s/%s"
|
|
#endif
|
|
|
|
assert(O);
|
|
|
|
if (obj) {
|
|
if (outPath != NULL)
|
|
sprintf_s(objPath, MAXSTR, EXPORT_FORMAT, outPath, obj);
|
|
else
|
|
sprintf_s(objPath, MAXSTR, "%s", obj);
|
|
|
|
obj_write_obj(O, objPath, mtl, prec);
|
|
}
|
|
|
|
if (mtl) {
|
|
if (outPath != NULL)
|
|
sprintf_s(mtlPath, MAXSTR, EXPORT_FORMAT, outPath, mtl);
|
|
else
|
|
sprintf_s(mtlPath, MAXSTR, "%s", mtl);
|
|
|
|
obj_write_mtl(O, mtlPath);
|
|
}
|
|
}
|
|
|
|
/*============================================================================*/
|