/*
* This file is part of mverse
* Copyright (C) 2022 juanvalencia.xyz
* mverse is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include
#include
#include
#include "obj.h"
struct Setv3 {
float data[3];
};
struct Setv2 {
float data[2];
};
struct Seti {
int v, vn, vt;
};
static void readV3(char *line, struct Setv3 **v, int vIndex);
static void readV2(char *line, struct Setv2 **vn, int vtIndex);
static void readF(char *line, Mesh *mesh, int meshIndex, struct Setv3 *v, struct Setv2 *vt, struct Setv3 *vn);
static Material * readMtl(char *line, const char *path, int *size);
static unsigned int useMtl(char *line, Material *mtl, unsigned int size);
/* -------------------------------------------------------------------------- */
struct Seti * readIndices(char *line, int *nIndices);
static int readIndex(char *line, struct Seti *f);
static Vertex createVertex(struct Seti f, struct Setv3 *v, struct Setv2 *vt, struct Setv3 *vn);
static int vertexInVertices(Vertex vertex, Vertex *vertices, int nVertices);
static void vertexAdd(Mesh *mesh, Vertex vertex);
static int vertexGetIndex(Vertex *vertices, Vertex vertex, int nVertices);
static void indexAdd(Mesh *mesh, int index);
/* -------------------------------------------------------------------------- */
static void getDir(char *filepath);
static void appendMtl(char *line, Material **mtl, int index);
static void readColor(char *line, float *k);
Obj
objCreate(const char *filename)
{
Obj o;
Mesh *mesh;
Material *mtl;
char lineBuffer[OBJ_LINE_MAX_SIZE];
FILE *fi;
struct Setv3 *v, *vn;
struct Setv2 *vt;
int vSize, vtSize, vnSize;
vSize = vtSize = vnSize = 0;
fi = (!strcmp(filename, "-")) ? stdin : fopen(filename, "r");
mesh = (Mesh *)calloc(1, sizeof(Mesh));
// On error return NULL
if (fi == NULL || mesh == NULL) {
fprintf(stderr, "objCreateMesh() Error: %s\n", strerror(errno));
fclose(fi);
exit(1);
}
v = (struct Setv3 *)calloc(1, vSize * sizeof(struct Setv3));
vt = (struct Setv2 *)calloc(1, vtSize * sizeof(struct Setv2));
vn = (struct Setv3 *)calloc(1, vnSize * sizeof(struct Setv3));
if (v == NULL) {
fprintf(stderr, "objCreateMesh() Error: %s\n", strerror(errno));
exit(1);
}
int n;
char key[500];
int vIndex, vtIndex, vnIndex;
int mtlSize, mtlIndex, meshIndex;
vIndex = vtIndex = vnIndex = meshIndex = mtlSize = 0;
while (fgets(lineBuffer, OBJ_LINE_MAX_SIZE, fi)) {
sscanf(lineBuffer, "%s%n", key, &n);
if (!strcmp("f" , key)) readF (lineBuffer + n, mesh, meshIndex, v, vt, vn);
else if (!strcmp("vt", key)) readV2(lineBuffer + n, &vt, vtIndex++);
else if (!strcmp("vn", key)) readV3(lineBuffer + n, &vn, vnIndex++);
else if (!strcmp("v" , key)) readV3(lineBuffer + n, &v, vIndex++);
else if (!strcmp("mtllib", key)) mtl = readMtl(lineBuffer + n, filename, &mtlSize);
else if (!strcmp("usemtl", key) && mtlSize > 0) {
mtlIndex = useMtl (lineBuffer + n, mtl, mtlSize);
mesh = (Mesh *)realloc(mesh, (++meshIndex + 1) * sizeof(Mesh));
memset(mesh + meshIndex, 0, sizeof(Mesh));
mesh[meshIndex].material = mtl[mtlIndex];
mesh[meshIndex].indexSize = 0;
}
key[0] = '\0';
}
o.mesh = mesh;
o.size = meshIndex + 1;
for (int i = 1; i < o.size; i++) {
o.mesh[i].vertices = o.mesh[0].vertices;
o.mesh[i].vertexSize = o.mesh[0].vertexSize;
}
free(v);
free(vt);
free(vn);
fclose(fi);
return o;
}
void
readF(char *line, Mesh *mesh, int meshIndex, struct Setv3 *v, struct Setv2 *vt, struct Setv3 *vn)
{
Vertex vertexBuffer;
struct Seti *f;
int i, nIndices, vi;
f = readIndices(line, &nIndices);
for (i = 0; i < nIndices; i++) {
vertexBuffer = createVertex(f[i], v, vt, vn);
if (!vertexInVertices(vertexBuffer, mesh->vertices, mesh->vertexSize))
vertexAdd(mesh, vertexBuffer);
vi = vertexGetIndex(mesh->vertices, vertexBuffer, mesh->vertexSize);
indexAdd(mesh + meshIndex, vi);
}
free(f);
}
void
readV3(char *line, struct Setv3 **v, int vIndex)
{
int i, n;
char *ptr;
struct Setv3 *vptr;
if (vIndex > 0)
*v = (struct Setv3 *)realloc(*v, (vIndex + 1) * sizeof(struct Setv3));
vptr = *v;
for (i = 0, ptr = line, n = 0; i < 3; i++, ptr += n)
sscanf(ptr, " %f%n", vptr[vIndex].data + i, &n);
}
void
readV2(char *line, struct Setv2 **v, int vIndex)
{
int i, n;
char *ptr;
struct Setv2 *vptr;
if (vIndex > 0)
*v = (struct Setv2 *)realloc(*v, (vIndex + 1) * sizeof(struct Setv2));
vptr = *v;
for (i = 0, ptr = line, n = 0; i < 2; i++, ptr += n)
sscanf(line, " %f%n", vptr[vIndex].data + i, &n);
}
struct Seti *
readIndices(char *line, int *nIndices)
{
struct Seti *f;
struct Seti *buffer;
char *ptr;
int bufferSize;
int n;
buffer = (struct Seti *)calloc(3, sizeof(struct Seti));
for (ptr = line, bufferSize = 0; *ptr != '\n'; ptr += n, bufferSize++) {
if (bufferSize + 1 > 3)
buffer = (struct Seti *)realloc(buffer, (bufferSize + 1) * sizeof(struct Seti));
n = readIndex(ptr, buffer + bufferSize);
if (buffer[bufferSize].v != -1) buffer[bufferSize].v--;
if (buffer[bufferSize].vt != -1) buffer[bufferSize].vt--;
if (buffer[bufferSize].vn != -1) buffer[bufferSize].vn--;
if (n > 0) continue;
memset(buffer, 0, (bufferSize + 1) * sizeof(struct Seti));
free(buffer);
fprintf(stderr, "readIndices() Error: bad format in line '%s'", line);
exit(1);
}
*nIndices = (bufferSize - 2) * 3;
f = (struct Seti *)calloc(nIndices[0], sizeof(struct Seti));
int i, j, k;
for (i = j = 0, k = 1; i < bufferSize - 2; i++) {
f[3 * i] = (j == 0) ? buffer[0] : buffer[j];
if (j + k < bufferSize) {
f[3 * i + 1] = buffer[j + k];
} else {
f[3 * i + 1] = buffer[0];
j = 0;
k *= 2;
f[3 * i + 2] = buffer[j + k];
j += k;
continue;
}
f[3 * i + 2] = (j + 2 * k < bufferSize) ? buffer[j + 2 * k] : buffer[0];
if (j + 2 * k < bufferSize) {
j += 2 * k;
} else {
j = 0;
k *= 2; // 1 2 4
}
}
free(buffer);
return f;
}
int
readIndex(char *line, struct Seti *f)
{
int n;
f->v = f->vt = f->vn = -1;
if ((sscanf(line, " %d/%d/%d%n", &f->v, &f->vn, &f->vt, &n) >= 3)) return n;
f->v = f->vt = f->vn = -1;
if ((sscanf(line, " %d//%d%n", &f->v, &f->vn, &n) >= 2)) return n;
f->v = f->vt = f->vn = -1;
if ((sscanf(line, " %d/%d%n", &f->v, &f->vt, &n) >= 2)) return n;
f->v = f->vt = f->vn = -1;
if ((sscanf(line, " %d%n", &f->v, &n) >= 1)) return n;
return 0;
}
Vertex
createVertex(struct Seti f, struct Setv3 *v, struct Setv2 *vt, struct Setv3 *vn)
{
int i;
Vertex out = {.position = {0, 0, 0}, .normal = {0, 0, 0}, .texCoords = {0, 0}};
for (i = 0; i < 3; i++) {
if (f.v != -1) out.position[i] = v[f.v].data[i];
if (f.vn != -1) out.normal[i] = vn[f.vn].data[i];
if (f.vt != -1 && i < 2) out.texCoords[i] = vt[f.vt].data[i];
}
return out;
}
int
vertexInVertices(Vertex vertex, Vertex *vertices, int nVertices)
{
return vertexGetIndex(vertices, vertex, nVertices) + 1;
}
int
vertexGetIndex(Vertex *vertices, Vertex vertex, int nVertices) {
int i, j, n;
for (i = 0; i < nVertices; i++) {
n = 0;
for (j = 0; j < 3; j++) {
if (vertices[i].position[j] == vertex.position[j]) n++;
if (vertices[i].normal[j] == vertex.normal[j]) n++;
}
for (j = 0; j < 2; j++) {
if (vertices[i].texCoords[j] == vertex.texCoords[j]) n++;
}
if (n == 8) return i;
}
return -1;
}
void
vertexAdd(Mesh *mesh, Vertex vertex)
{
Vertex *v;
int i;
unsigned int vSize;
v = mesh->vertices;
vSize = mesh->vertexSize;
if (!vSize) v = (Vertex *)calloc(1, sizeof(Vertex));
else v = (Vertex *)realloc(v, (vSize + 1) * sizeof(Vertex));
if (v == NULL) {
fprintf(stderr, "vertexAdd() Error: %s\n", strerror(errno));
exit(1);
}
for (i = 0; i < 3; i++) {
v[vSize].position[i] = vertex.position[i];
v[vSize].normal[i] = vertex.normal[i];
if (i < 2)
v[vSize].texCoords[i] = vertex.texCoords[i];
}
mesh->vertices = v;
mesh->vertexSize++;
}
void
indexAdd(Mesh *mesh, int index)
{
unsigned int *iv, iSize;
iv = mesh->indices;
iSize = mesh->indexSize;
if (!iSize) iv = (unsigned int *)calloc(1, sizeof(int));
else iv = (unsigned int *)realloc(iv, (iSize + 1) * sizeof(int));
if (iv == NULL) {
fprintf(stderr, "vertexAdd() Error: %s\n", strerror(errno));
exit(1);
}
iv[iSize] = index;
mesh->indices = iv;
mesh->indexSize++;
}
Material *
readMtl(char *line, const char *objFile, int *size)
{
Material *out;
char buffer[OBJ_LINE_MAX_SIZE], mtlFilename[OBJ_LINE_MAX_SIZE], key[OBJ_MAX_WORD];
char path[OBJ_MAX_WORD], fileName[OBJ_MAX_WORD];
FILE *fin;
int i, n;
strncpy(path, objFile, 512);
getDir(path);
sscanf(line, " ./%s", fileName);
sprintf(mtlFilename, "%s/%s", path, fileName);
fin = fopen(mtlFilename, "r");
i = 0;
while(fgets(buffer, OBJ_LINE_MAX_SIZE, fin)) {
sscanf(buffer, "%s%n", key, &n);
if (!strcmp(key, "newmtl")) appendMtl(buffer + n, &out, i++);
if (i > 0) {
if (!strcmp(key, "Ka")) readColor(buffer + n, out[i - 1].ka);
else if (!strcmp(key, "Kd")) readColor(buffer + n, out[i - 1].kd);
else if (!strcmp(key, "Ks")) readColor(buffer + n, out[i - 1].ks);
else if (!strcmp(key, "illum")) sscanf(buffer + n, "%u", &out[i - 1].illum);
else if (!strcmp(key, "Ns")) sscanf(buffer + n, "%f", &out[i - 1].ns);
}
key[0] = '\0';
}
if (size) *size = i;
fclose(fin);
return out;
}
void
appendMtl(char *line, Material **mtl, int index)
{
char name[OBJ_LINE_MAX_SIZE];
if (index == 0)
*mtl = (Material *)calloc(1, sizeof(Material));
else
*mtl = (Material *)realloc(*mtl, (index + 1) * sizeof(Material));
sscanf(line, "%s", name);
strncpy((*mtl + index)->name, name, OBJ_LINE_MAX_SIZE);
if (mtl == NULL) {
fprintf(stderr, "appendMtl() Error: %s\n", strerror(errno));
exit(1);
}
}
void
readColor(char *line, float *k)
{
int ret;
ret = sscanf(line, "%f %f %f", k, k + 1, k + 2);
switch (ret) {
case 1:
k[1] = k[2] = k[0];
break;
case 3:
break;
default:
fprintf(stderr, "readColor() Error: line '%s' invalid format", line - 2);
exit(1);
break;
}
}
unsigned int
useMtl(char *line, Material *mtl, unsigned int size)
{
int i;
char name[OBJ_LINE_MAX_SIZE];
for (i = 0; i < size; i++) {
sscanf(line, "%s", name);
if (!strcmp(name, mtl[i].name))
return i;
}
return 0;
}
void
getDir(char *filepath)
{
int i;
for (i = strlen(filepath) - 1; i >= 0; i--) {
switch (filepath[i]) {
case '/':
case '\\':
filepath[i] = '\0';
return;
default:
break;
}
}
strcpy(filepath, ".");
}