b23636c9 |
// Little OBJ loader
// Ingemar's little OBJ loader
// Formerly named LoadOBJ or loadobj
// by Ingemar Ragnemalm 2005, 2008, 2019, 2021
// Original version 2005 was extremely simple and intended for use with old OpenGL immediate mode.
// Additions and reorganization by Mikael Kalms 2008
// 120913: Revised LoadModelPlus/DrawModel by Jens.
// Partially corrected formatting. (It is a mess!)
// 130227: Error reporting in DrawModel
// 130422: Added ScaleModel
// 150909: Frees up temporary "Mesh" memory i LoadModel. Thanks to Simon Keisala for finding this!
// Added DisposeModel. Limited the number of error printouts, thanks to Rasmus Hytter for this suggestion!
// 160302: Uses fopen_s on Windows, as suggested by Jesper Post. Should reduce warnings a bit.
// 160510: Uses calloc instead of malloc (for safety) in many places where it could possibly cause problems.
// 170406: Added "const" to string arguments to make C++ happier.
// 190416: free mesh->coordStarts properly.
// 191110: Finally! Multi-part OBJ files work! Renamed LoadModelPlus to LoadModel.
// and added LoadModelSet! The old LoadModel was removed. Converted to use vec2/vec3
// instead of arrays of float. It is now dependent on VectorUtils3. If you want to
// make it independent of VectorUtils3, only a few structs and functions have to be replaced.
// MTL support working! Parser totally rewritten. Officially renamed to Ingemars Little OBJ Loader,
// LOL for short! And boy did I find a nice acronym!
// 210125: Corrected types in LoadDataToModel. Corrected material disposal in the still badly tested DisposeModel.
// 210218: Allows TAB between parts of string.
// 210304: Corrected bad allocation size on row 538, found by Jens Lindgren, who also found glDeleteVertexArrays in DisposeModel.
// 210422: Change by Felicia Castenbrandt, around row 415, copy path or source. This change is likely to be needed in some other places.
// 210502: Dispose the material name list, omission found by Jens Lindgren.
// Usage:
// Load simple models with LoadModel. Multi-part models are loaded with LoadModelSet.
// DrawModel will draw the model, or, for a multi-part model, one part.
// If you need to modify a model on the CPU, you can do that and re-upload
// with LoadDataToModel.
// There is no longer any call that only loads the data, but you can
// make your own by taking out the GPU upload from LoadModel.
// This is free software. I appreciate credits in derivate products or
// if it is used in public products.
#include "LittleOBJLoader.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include "VectorUtils3.h"
#if defined(_WIN32)
#define sscanf sscanf_s
#endif
static char atLineEnd = 0;
static char atFileEnd = 0;
static char ParseString(char *line, int *pos, char *s)
{
int i = 0;
char c;
c = line[(*pos)++];
while (c == 32 || c == 9) // Important change 210218
c = line[(*pos)++]; // Skip leading spaces
while (c != 0 && c != 32 && c != 9 && c != EOF && c != '/' && i < 254)
{
s[i++] = c;
c = line[(*pos)++];
}
atLineEnd = (c == 0);
s[i] = 0;
return c;
}
static float ParseFloat(char *line, int *pos)
{
float floatValue = 0.0;
char s[255];
ParseString(line, pos, s);
sscanf(s, "%f", &floatValue);
if (isnan(floatValue) || isinf(floatValue))
floatValue = 0.0;
return floatValue;
}
static int ParseInt(char *line, int *pos, char *endc)
{
int intValue = 0;
char s[255];
char c = ParseString(line, pos, s);
if (endc != NULL) *endc = c;
if (strlen(s) == 0)
intValue = -1; // Nothing found!
else
sscanf(s, "%d", &intValue);
return intValue;
}
static vec3 ParseVec3(char *line, int *pos)
{
vec3 v;
v.x = ParseFloat(line, pos);
v.y = ParseFloat(line, pos);
v.z = ParseFloat(line, pos);
return v;
}
void ParseLine(FILE *fp, char *line)
{
int i, j;
char c = '_';
for (i = 0, j= 0; i < 2048; i++)
{
c = getc(fp);
if (c != 10 && c != 13)
line[j++] = c;
if (c == EOF || ((c == 10 || c == 13) && j > 0))
{
if (c == EOF)
atFileEnd = 1;
line[j] = 0;
return;
}
}
line[j] = 0;
return;
}
// Dispose the strings in the materials list
static void DisposeMtlList(Mtl **materials)
{
if (materials != NULL)
for (int i = 0; materials[i] != NULL; i++)
free(materials[i]);
free(materials);
}
static Mtl **ParseMTL(char *filename)
{
Mtl *m;
char s[255];
char line[2048];
int pos;
Mtl **materials = NULL;
FILE *fp;
// It seems Windows/VS doesn't like fopen any more, but fopen_s is not on the others.
#if defined(_WIN32)
fopen_s(&fp, filename, "r");
#else
fp = fopen(filename, "rb"); // rw works everywhere except Windows?
#endif
if (fp == NULL)
return NULL;
atLineEnd = 0;
atFileEnd = 0;
int matcount = 0;
while (!atFileEnd)
{
ParseLine(fp, line);
pos = 0;
ParseString(line, &pos, s);
if (strcmp(s, "newmtl") == 0)
matcount++;
}
rewind(fp);
materials = (Mtl **)calloc(sizeof(MtlPtr)*(matcount+1), 1);
matcount = 0;
atLineEnd = 0;
atFileEnd = 0;
while (!atFileEnd)
{
ParseLine(fp, line);
pos = 0;
ParseString(line, &pos, s);
// NEW MATERIAL!
if (strcmp(s, "newmtl") == 0)
{
matcount++;
materials[matcount-1] = (Mtl *)calloc(sizeof(Mtl),1);
m = materials[matcount-1];
materials[matcount] = NULL;
ParseString(line, &pos, m->newmtl);
}
if (m != NULL)
{
if (strcmp(s, "Ka") == 0)
m->Ka = ParseVec3(line, &pos);
if (strcmp(s, "Kd") == 0)
m->Kd = ParseVec3(line, &pos);
if (strcmp(s, "Ks") == 0)
m->Ks = ParseVec3(line, &pos);
if (strcmp(s, "Ke") == 0)
m->Ke = ParseVec3(line, &pos);
if (strcmp(s, "Tr") == 0)
{
m->Tr = ParseFloat(line, &pos);
m->d = 1 - m->Tr;
}
if (strcmp(s, "d") == 0)
{
m->d = ParseFloat(line, &pos);
m->Tr = 1 - m->d;
}
if (strcmp(s, "illum") == 0)
m->illum = ParseInt(line, &pos, NULL);
if (strcmp(s, "map_Ka") == 0)
ParseString(line, &pos, m->map_Ka);
if (strcmp(s, "map_Kd") == 0)
ParseString(line, &pos, m->map_Kd);
if (strcmp(s, "map_Ks") == 0)
ParseString(line, &pos, m->map_Ks);
if (strcmp(s, "map_Ke") == 0)
ParseString(line, &pos, m->map_Ke);
if (strcmp(s, "map_d") == 0)
ParseString(line, &pos, m->map_d);
if (strcmp(s, "map_bump") == 0)
ParseString(line, &pos, m->map_bump);
if (strcmp(s, "bump") == 0)
ParseString(line, &pos, m->map_bump);
}
}
fclose(fp);
return materials;
}
typedef struct Mesh
{
vec3 *vertices;
int vertexCount;
vec3 *vertexNormals;
int normalsCount;
vec2 *textureCoords;
int texCount;
int *coordIndex;
int *normalsIndex;
int *textureIndex;
int coordCount; // Number of indices in each index struct
// Borders between groups
int *coordStarts;
int groupCount;
char *materialName;
} Mesh, *MeshPtr;
// Globals set by LoadObj
static int vertCount, texCount, normalsCount, coordCount;
static int zeroFix;
static char hasPositionIndices;
static char hasNormalIndices;
static char hasTexCoordIndices;
// List of materials
Mtl **gMaterials;
char **gMtlNameList;
char *gMtlLibName = NULL;
static char ParseOBJ(const char *filename, MeshPtr theMesh)
{
int lastCoordCount = -1;
FILE *fp;
// It seems Windows/VS doesn't like fopen any more, but fopen_s is not on the others.
#if defined(_WIN32)
fopen_s(&fp, filename, "r");
#else
fp = fopen(filename, "rb"); // rw works everywhere except Windows?
#endif
if (fp == NULL)
return -1;
// These must be taken care of by the caller!
vertCount=0;
texCount=0;
normalsCount=0;
coordCount=0;
atLineEnd = 0;
atFileEnd = 0;
while (!atFileEnd)
{
char line[2048];
ParseLine(fp, line);
int pos = 0;
char s[256];
ParseString(line, &pos, s);
if (strcmp(s, "v") == 0)
{
vec3 v = ParseVec3(line, &pos);
if (theMesh->vertices != NULL)
theMesh->vertices[vertCount++] = v;
else
vertCount += 1;
}
if (strcmp(s, "vn") == 0)
{
vec3 v = ParseVec3(line, &pos);
if (theMesh->vertexNormals != NULL)
theMesh->vertexNormals[normalsCount++] = v;
else
normalsCount += 1;
}
if (strcmp(s, "vt") == 0)
{
vec3 v = ParseVec3(line, &pos);
if (theMesh->textureCoords != NULL)
{
theMesh->textureCoords[texCount].x = v.x;
theMesh->textureCoords[texCount++].y = v.y;
}
else
texCount += 1;
}
if (strcmp(s, "f") == 0)
{
// Unknown number of singeltons or triplets!
int p = -1;
int i = -1;
int p1;
char *ns;
char done = 0;
for (i = pos ; !done; pos++)
{
if (line[pos] == 0)
done = 1;
if (p >= 0) // in triplet)
{
char c = line[pos];
if (c == '/' || c == ' ' || c == 0)
{
ns = &line[p1]; // Start of substring
line[pos] = 0; // End of substring
if (p1 != pos)
{
i = atoi(ns);
if (i == 0)
zeroFix = 1;
if (i < 0)
{
switch (p)
{
case 0: i = vertCount + i + 1; break;
case 1: i = texCount + i + 1; break;
case 2: i = normalsCount + i + 1; break;
}
}
i = i + zeroFix;
}
else
i = 0; // -1
if (i > 0)
switch (p)
{
case 0:
if (theMesh->coordIndex != NULL)
theMesh->coordIndex[coordCount-1] = i-1;
break;
case 1:
if (theMesh->textureIndex != NULL)
if (i >= 0)
theMesh->textureIndex[coordCount-1] = i-1;
hasTexCoordIndices = 1;
break;
case 2:
if (theMesh->normalsIndex != NULL)
if (i >= 0)
theMesh->normalsIndex[coordCount-1] = i-1;
hasNormalIndices = 1;
break;
}
p1 = pos + 1;
p++;
}
if (c == ' ')
{
p = -1; // Next triplet
}
}
else // Wait for non-space
if (line[pos] != ' ')
{
if (!done)
coordCount++;
p = 0; // Triplet starts!
p1 = pos;
}
}
if (theMesh->coordIndex != NULL)
theMesh->coordIndex[coordCount] = -1;
if (theMesh->textureIndex != NULL)
theMesh->textureIndex[coordCount] = -1;
if (theMesh->normalsIndex != NULL)
theMesh->normalsIndex[coordCount] = -1;
coordCount++;
}
if (strcmp(s, "mtllib") == 0)
{
// Save file name for later
char libname[256];
// Added by Felicia Castenbrandt: Get path of main file, if any
char temp[255];
int index = 0;
//find index of last /
for(int i = 0; i < strlen(filename); i++)
if(filename[i] == '/')
index = i;
//copy everything before the last / to temp
if (index != 0)
{
for(int i = 0; i < index+1; i++)
{
char ch = filename[i];
strncat(temp, &ch, 1);
}
}
// End of addition
/*char c =*/ ParseString(line, &pos, libname);
gMtlLibName = (char *)malloc(strlen(libname)+1);
#if defined(_WIN32)
strcpy_s(gMtlLibName, strlen(libname) + 1, libname);
#else
strcpy(gMtlLibName, libname);
#endif
}
if (strcmp(s, "usemtl") == 0)
{
// Groups are only identified by usemtl!
if (coordCount > 0) // If no data has been seen, this must be the first group!
{
if (lastCoordCount != coordCount) // must not be the same; empty group (happens if a model has both "g" and "o")
{
theMesh->groupCount += 1;
if (theMesh->coordStarts != NULL) // NULL if we are just counting
theMesh->coordStarts[theMesh->groupCount] = coordCount;
lastCoordCount = coordCount;
}
else
printf("Ignored part!\n");
}
if (gMtlNameList != NULL)
{
// Save matname for later
char matname[255];
ParseString(line, &pos, matname);
gMtlNameList[theMesh->groupCount] = (char *)malloc(strlen(matname)+1);
#if defined(_WIN32)
strcpy_s(gMtlNameList[theMesh->groupCount], strlen(matname) + 1, matname);
#else
strcpy(gMtlNameList[theMesh->groupCount], matname);
#endif
}
}
} // While
// Add last groups *if* there was more data since last time!
if (coordCount > lastCoordCount)
{
theMesh->groupCount += 1; // End of last group
if (theMesh->coordStarts != NULL)
theMesh->coordStarts[theMesh->groupCount] = coordCount;
}
fclose(fp);
if (gMtlLibName != NULL)
gMaterials = ParseMTL(gMtlLibName);
// else try based on filename: filename.obj -> filename.mtl or filename_obj.mtl
if (gMaterials == NULL)
if (strlen(filename) > 4)
{
char tryname[255];
strcpy(tryname, filename);
tryname[strlen(tryname) - 4] = '_';
strcat(tryname, ".mtl");
gMaterials = ParseMTL(tryname);
}
if (gMaterials == NULL)
if (strlen(filename) > 4)
{
char tmpname[255];
strcpy(tmpname, filename);
tmpname[strlen(tmpname) - 4] = 0;
strcat(tmpname, ".mtl");
gMaterials = ParseMTL(tmpname);
}
return 0;
}
// Load raw, unprocessed OBJ data!
static struct Mesh * LoadOBJ(const char *filename)
{
// Reads once to find sizes, again to fill buffers
Mesh *theMesh;
// Allocate Mesh but not the buffers
theMesh = (Mesh *)calloc(sizeof(Mesh), 1);
hasPositionIndices = 1;
hasTexCoordIndices = 0;
hasNormalIndices = 0;
theMesh->coordStarts = NULL;
theMesh->groupCount = 0;
gMtlNameList = NULL;
// Dispose if they exist!
gMtlLibName = NULL;
gMaterials = NULL;
vertCount=0;
texCount=0;
normalsCount=0;
coordCount=0;
zeroFix = 0;
// Parse for size
ParseOBJ(filename, theMesh);
// Allocate arrays!
if (vertCount > 0)
theMesh->vertices = (vec3 *)malloc(sizeof(vec3) * vertCount);
if (texCount > 0)
theMesh->textureCoords = (vec2 *)malloc(sizeof(vec2) * texCount);
if (normalsCount > 0)
theMesh->vertexNormals = (vec3 *)malloc(sizeof(vec3) * normalsCount);
if (hasPositionIndices)
theMesh->coordIndex = (int *)calloc(coordCount, sizeof(int));
if (hasNormalIndices)
theMesh->normalsIndex = (int *)calloc(coordCount, sizeof(int));
if (hasTexCoordIndices)
theMesh->textureIndex = (int *)calloc(coordCount, sizeof(int));
gMtlNameList = (char **)calloc(sizeof(char *) * (theMesh->groupCount+1), 1);
theMesh->coordStarts = (int *)calloc(sizeof(int) * (theMesh->groupCount+2), 1); // Length found on last pass
theMesh->groupCount = 0;
// Zero again
vertCount=0;
texCount=0;
normalsCount=0;
coordCount=0;
// Parse again for filling buffers
ParseOBJ(filename, theMesh);
theMesh->vertexCount = vertCount;
theMesh->coordCount = coordCount;
theMesh->texCount = texCount;
theMesh->normalsCount = normalsCount;
return theMesh;
}
void DecomposeToTriangles(struct Mesh *theMesh)
{
int i, vertexCount, triangleCount;
int *newCoords, *newNormalsIndex, *newTextureIndex;
int newIndex = 0; // Index in newCoords
int first = 0;
// 1.1 Calculate how big the list will become
vertexCount = 0; // Number of vertices in current polygon
triangleCount = 0; // Resulting number of triangles
for (i = 0; i < theMesh->coordCount; i++)
{
if (theMesh->coordIndex[i] == -1)
{
if (vertexCount > 2) triangleCount += vertexCount - 2;
vertexCount = 0;
}
else
{
vertexCount = vertexCount + 1;
}
}
newCoords = (int *)calloc(triangleCount * 3, sizeof(int));
if (theMesh->normalsIndex != NULL)
newNormalsIndex = (int *)calloc(triangleCount * 3, sizeof(int));
if (theMesh->textureIndex != NULL)
newTextureIndex = (int *)calloc(triangleCount * 3, sizeof(int));
// 1.2 Loop through old list and write the new one
// Almost same loop but now it has space to write the result
vertexCount = 0;
for (i = 0; i < theMesh->coordCount; i++)
{
if (theMesh->coordIndex[i] == -1)
{
first = i + 1;
vertexCount = 0;
}
else
{
vertexCount = vertexCount + 1;
if (vertexCount > 2)
{
newCoords[newIndex++] = theMesh->coordIndex[first];
newCoords[newIndex++] = theMesh->coordIndex[i-1];
newCoords[newIndex++] = theMesh->coordIndex[i];
if (theMesh->normalsIndex != NULL)
{
newNormalsIndex[newIndex-3] = theMesh->normalsIndex[first];
newNormalsIndex[newIndex-2] = theMesh->normalsIndex[i-1];
newNormalsIndex[newIndex-1] = theMesh->normalsIndex[i];
}
if (theMesh->textureIndex != NULL)
{
newTextureIndex[newIndex-3] = theMesh->textureIndex[first];
newTextureIndex[newIndex-2] = theMesh->textureIndex[i-1];
newTextureIndex[newIndex-1] = theMesh->textureIndex[i];
}
}
}
}
free(theMesh->coordIndex);
theMesh->coordIndex = newCoords;
theMesh->coordCount = triangleCount * 3;
if (theMesh->normalsIndex != NULL)
{
free(theMesh->normalsIndex);
theMesh->normalsIndex = newNormalsIndex;
}
if (theMesh->textureIndex != NULL)
{
free(theMesh->textureIndex);
theMesh->textureIndex = newTextureIndex;
}
} // DecomposeToTriangles
static void GenerateNormals(Mesh* mesh)
{
// If model has vertices but no vertexnormals, generate normals
if (mesh->vertices && !mesh->vertexNormals)
{
int face;
int normalIndex;
mesh->vertexNormals = (vec3 *)calloc(sizeof(vec3) * mesh->vertexCount, 1);
mesh->normalsCount = mesh->vertexCount;
mesh->normalsIndex = (int *)calloc(mesh->coordCount, sizeof(GLuint));
memcpy(mesh->normalsIndex, mesh->coordIndex, sizeof(GLuint) * mesh->coordCount);
for (face = 0; face * 3 < mesh->coordCount; face++)
{
int i0 = mesh->coordIndex[face * 3 + 0];
int i1 = mesh->coordIndex[face * 3 + 1];
int i2 = mesh->coordIndex[face * 3 + 2];
vec3 v0 = VectorSub(mesh->vertices[i1], mesh->vertices[i0]);
vec3 v1 = VectorSub(mesh->vertices[i2], mesh->vertices[i0]);
vec3 v2 = VectorSub(mesh->vertices[i2], mesh->vertices[i1]);
float sqrLen0 = dot(v0, v0);
float sqrLen1 = dot(v1, v1);
float sqrLen2 = dot(v2, v2);
float len0 = (sqrLen0 >= 1e-6f) ? sqrt(sqrLen0) : 1e-3f;
float len1 = (sqrLen1 >= 1e-6f) ? sqrt(sqrLen1) : 1e-3f;
float len2 = (sqrLen2 >= 1e-6f) ? sqrt(sqrLen2) : 1e-3f;
float influence0 = dot(v0, v1) / (len0 * len1);
float influence1 = -dot(v0, v2) / (len0 * len2);
float influence2 = dot(v1, v2) / (len1 * len2);
float angle0 = (influence0 >= 1.f) ? 0 :
(influence0 <= -1.f) ? M_PI : acos(influence0);
float angle1 = (influence1 >= 1.f) ? 0 :
(influence1 <= -1.f) ? M_PI : acos(influence1);
float angle2 = (influence2 >= 1.f) ? 0 :
(influence2 <= -1.f) ? M_PI : acos(influence2);
vec3 normal = cross(v0, v1);
mesh->vertexNormals[i0] = VectorAdd(ScalarMult(normal, angle0), mesh->vertexNormals[i0]);
mesh->vertexNormals[i1] = VectorAdd(ScalarMult(normal, angle1), mesh->vertexNormals[i1]);
mesh->vertexNormals[i2] = VectorAdd(ScalarMult(normal, angle2), mesh->vertexNormals[i2]);
}
for (normalIndex = 0; normalIndex < mesh->normalsCount; normalIndex++)
{
float length = Norm(mesh->vertexNormals[normalIndex]);
if (length > 0.01f)
mesh->vertexNormals[normalIndex] = ScalarMult(mesh->vertexNormals[normalIndex], 1/length);
}
}
}
static Model* GenerateModel(Mesh* mesh)
{
// Convert from Mesh format (multiple index lists) to Model format
// (one index list) by generating a new set of vertices/indices
// and where new vertices have been created whenever necessary
typedef struct
{
int positionIndex;
int normalIndex;
int texCoordIndex;
int newIndex;
} IndexTriplet;
int hashGap = 6;
int indexHashMapSize = (mesh->vertexCount * hashGap + mesh->coordCount);
IndexTriplet* indexHashMap = (IndexTriplet *)malloc(sizeof(IndexTriplet)
* indexHashMapSize);
int numNewVertices = 0;
int index;
int maxValue = 0;
Model* model = (Model *)malloc(sizeof(Model));
memset(model, 0, sizeof(Model));
model->indexArray = (GLuint *)malloc(sizeof(GLuint) * mesh->coordCount);
model->numIndices = mesh->coordCount;
memset(indexHashMap, 0xff, sizeof(IndexTriplet) * indexHashMapSize);
for (index = 0; index < mesh->coordCount; index++)
{
IndexTriplet currentVertex = { -1, -1, -1, -1 };
int insertPos = 0;
if (mesh->coordIndex)
currentVertex.positionIndex = mesh->coordIndex[index];
if (mesh->normalsIndex)
currentVertex.normalIndex = mesh->normalsIndex[index];
if (mesh->textureIndex)
currentVertex.texCoordIndex = mesh->textureIndex[index];
if (maxValue < currentVertex.texCoordIndex)
maxValue = currentVertex.texCoordIndex;
if (currentVertex.positionIndex >= 0)
insertPos = currentVertex.positionIndex * hashGap;
while (1)
{
if (indexHashMap[insertPos].newIndex == -1)
{
currentVertex.newIndex = numNewVertices++;
indexHashMap[insertPos] = currentVertex;
break;
}
else if (indexHashMap[insertPos].positionIndex
== currentVertex.positionIndex
&& indexHashMap[insertPos].normalIndex
== currentVertex.normalIndex
&& indexHashMap[insertPos].texCoordIndex
== currentVertex.texCoordIndex)
{
currentVertex.newIndex = indexHashMap[insertPos].newIndex;
break;
}
else
insertPos++;
}
model->indexArray[index] = currentVertex.newIndex;
}
if (mesh->vertices)
model->vertexArray = (vec3 *)malloc(sizeof(vec3) * numNewVertices);
if (mesh->vertexNormals)
model->normalArray = (vec3 *)malloc(sizeof(vec3) * numNewVertices);
if (mesh->textureCoords)
model->texCoordArray = (vec2 *)malloc(sizeof(vec2) * numNewVertices);
model->numVertices = numNewVertices;
for (index = 0; index < indexHashMapSize; index++)
{
if (indexHashMap[index].newIndex != -1)
{
if (mesh->vertices)
model->vertexArray[indexHashMap[index].newIndex] = mesh->vertices[indexHashMap[index].positionIndex];
if (mesh->vertexNormals)
{
model->normalArray[indexHashMap[index].newIndex] = mesh->vertexNormals[indexHashMap[index].normalIndex];
}
if (mesh->textureCoords)
model->texCoordArray[indexHashMap[index].newIndex] = mesh->textureCoords[indexHashMap[index].texCoordIndex];
}
}
free(indexHashMap);
// If there is a material set, match materials to parts
if (gMaterials != NULL)
if (mesh->materialName != NULL)
for (int ii = 0; gMaterials[ii] != NULL; ii++)
{
Mtl *mtl = gMaterials[ii];
if (strcmp(mesh->materialName, mtl->newmtl) == 0)
{
// Copy mtl to model!
model->material = (Mtl *)malloc(sizeof(Mtl));
memcpy(model->material, mtl, sizeof(Mtl));
strcpy(model->material->map_Ka, mtl->map_Ka);
strcpy(model->material->map_Kd, mtl->map_Kd);
strcpy(model->material->map_Ks, mtl->map_Ks);
strcpy(model->material->map_Ke, mtl->map_Ke);
strcpy(model->material->map_Ns, mtl->map_Ns);
strcpy(model->material->map_d, mtl->map_d);
strcpy(model->material->map_bump, mtl->map_bump);
}
}
return model;
}
Mesh **SplitToMeshes(Mesh *m)
{
int * mapc = (int *)malloc(m->vertexCount * sizeof(int));
int * mapt = (int *)malloc(m->texCount * sizeof(int));
int * mapn = (int *)malloc(m->normalsCount * sizeof(int));
if (m == NULL || m ->vertices == NULL || m->vertexCount == 0)
{
printf("Illegal mesh!\n");
return NULL;
}
Mesh **mm = (Mesh **)calloc(sizeof(Mesh *), m->groupCount+2);
for (int mi = 0; mi < m->groupCount; mi++) // For all sections
{
int from = m->coordStarts[mi];
int to = m->coordStarts[mi+1];
mm[mi] = (Mesh *)calloc(sizeof(Mesh), 1); // allocate struct
// Fill mapc, mapt, mapn with -1 (illegal index)
for (int ii = 0; ii < m->vertexCount; ii++)
mapc[ii] = -1;
for (int ii = 0; ii < m->texCount; ii++)
mapt[ii] = -1;
for (int ii = 0; ii < m->normalsCount; ii++)
mapn[ii] = -1;
// Count number of entries needed
int intVertexCount = 0;
int intTexCount = 0;
int intNormalsCount = 0;
for (int j = from; j < to; j++) // For all index triplets
{
int ix = m->coordIndex[j];
if (ix > -1)
if (mapc[ix] == -1)
{
mapc[ix] = ix;
intVertexCount++;
}
if (m->textureIndex != NULL)
{
ix = m->textureIndex[j];
if (ix > -1)
if (mapt[ix] == -1)
{
mapt[ix] = ix;
intTexCount++;
}
}
if (m->normalsIndex != NULL)
{
ix = m->normalsIndex[j];
if (ix > -1)
if (mapn[ix] == -1)
{
mapn[ix] = ix;
intNormalsCount++;
}
}
}
// Allocate buffers
mm[mi]->coordIndex = (int *)malloc((to - from) * sizeof(int));
mm[mi]->textureIndex = (int *)malloc((to - from) * sizeof(int));
mm[mi]->normalsIndex = (int *)malloc((to - from) * sizeof(int));
if (intVertexCount > 0)
mm[mi]->vertices = (vec3 *)malloc(intVertexCount * sizeof(vec3));
if (intTexCount > 0)
mm[mi]->textureCoords = (vec2 *)malloc(intTexCount * sizeof(vec2));
if (intNormalsCount > 0)
mm[mi]->vertexNormals = (vec3 *)malloc(intNormalsCount * sizeof(vec3));
mm[mi]->vertexCount = intVertexCount;
mm[mi]->texCount = intTexCount;
mm[mi]->normalsCount = intNormalsCount;
// Fill mapc, mapt, mapn with -1 (illegal index)
for (int ii = 0; ii < m->vertexCount; ii++)
mapc[ii] = -1;
for (int ii = 0; ii < m->texCount; ii++)
mapt[ii] = -1;
for (int ii = 0; ii < m->normalsCount; ii++)
mapn[ii] = -1;
int mapcCount = 0;
int maptCount = 0;
int mapnCount = 0;
for (int j = from; j < to; j++) // For all index triplets
{
int ix = m->coordIndex[j];
if (ix > -1)
{
if (mapc[ix] == -1) // Unmapped
{
mapc[ix] = mapcCount++;
mm[mi]->vertices[mapc[ix]] = m->vertices[ix]; // Copy vertex to mm[i]
}
mm[mi]->coordIndex[j-from] = mapc[ix];
}
else // Separator
{
mm[mi]->coordIndex[j-from] = -1;
}
if (m->textureIndex != NULL)
if (mm[mi]->textureIndex != NULL)
{
ix = m->textureIndex[j];
if (ix > -1)
{
if (mapt[ix] == -1) // Unmapped
{
mapt[ix] = maptCount++;
mm[mi]->textureCoords[mapt[ix]] = m->textureCoords[ix]; // Copy vertex to mm[i]
}
mm[mi]->textureIndex[j-from] = mapt[ix];
}
else // Separator
{
mm[mi]->textureIndex[j-from] = -1;
}
}
if (m->normalsIndex != NULL)
if (mm[mi]->normalsIndex != NULL)
{
ix = m->normalsIndex[j];
if (ix > -1)
{
if (mapn[ix] == -1) // Unmapped
{
mapn[ix] = mapnCount++;
mm[mi]->vertexNormals[mapn[ix]] = m->vertexNormals[ix]; // Copy vertex to mm[i]
}
mm[mi]->normalsIndex[j-from] = mapn[ix];
}
else // Separator
{
mm[mi]->normalsIndex[j-from] = -1;
}
}
} // for all index triplets
mm[mi]->coordCount = to - from;
if (gMtlNameList != NULL)
{
mm[mi]->materialName = gMtlNameList[mi];
gMtlNameList[mi] = NULL; // No longer "owned" by the gMtlNameList
}
} // for all parts
return mm;
}
static void DisposeMesh(Mesh *mesh)
{
// Free the mesh!
if (mesh != NULL)
{
if (mesh->vertices != NULL)
free(mesh->vertices);
if (mesh->vertexNormals != NULL)
free(mesh->vertexNormals);
if (mesh->textureCoords != NULL)
free(mesh->textureCoords);
if (mesh->coordIndex != NULL)
free(mesh->coordIndex);
if (mesh->normalsIndex != NULL)
free(mesh->normalsIndex);
if (mesh->textureIndex != NULL)
free(mesh->textureIndex);
#if !defined(_WIN32)
// This is very disturbing, causes heap corruption on Windows. Reason not found.
if (mesh->coordStarts != NULL)
free(mesh->coordStarts);
#endif
if (mesh->materialName != NULL)
free(mesh->materialName);
// Dispose the material name list too
if (gMtlNameList != NULL)
{
for (int i = 0; i < mesh->groupCount; i++)
if (gMtlNameList[i] != NULL)
free(gMtlNameList[i]);
free(gMtlNameList);
gMtlNameList = NULL;
}
free(mesh);
}
}
void CenterModel(Model *m)
{
int i;
float maxx = -1e10, maxy = -1e10, maxz = -1e10, minx = 1e10, miny = 1e10, minz = 1e10;
for (i = 0; i < m->numVertices; i++)
{
if (m->vertexArray[i].x < minx) minx = m->vertexArray[i].x;
if (m->vertexArray[i].x > maxx) maxx = m->vertexArray[i].x;
if (m->vertexArray[i].y < miny) miny = m->vertexArray[i].y;
if (m->vertexArray[i].y > maxy) maxy = m->vertexArray[i].y;
if (m->vertexArray[i].z < minz) minz = m->vertexArray[i].z;
if (m->vertexArray[i].z > maxz) maxz = m->vertexArray[i].z;
}
for (i = 0; i < m->numVertices; i++)
{
m->vertexArray[i].x -= (maxx + minx)/2.0f;
m->vertexArray[i].y -= (maxy + miny)/2.0f;
m->vertexArray[i].z -= (maxz + minz)/2.0f;
}
}
void ScaleModel(Model *m, float sx, float sy, float sz)
{
long i;
for (i = 0; i < m->numVertices; i++)
{
m->vertexArray[i].x *= sx;
m->vertexArray[i].y *= sy;
m->vertexArray[i].z *= sz;
}
}
static void ReportRerror(const char *caller, const char *name)
{
static unsigned int draw_error_counter = 0;
if(draw_error_counter < NUM_DRAWMODEL_ERROR)
{
fprintf(stderr, "%s warning: '%s' not found in shader!\n", caller, name);
draw_error_counter++;
}
else if(draw_error_counter == NUM_DRAWMODEL_ERROR)
{
fprintf(stderr, "%s: Number of error bigger than %i. No more vill be printed.\n", caller, NUM_DRAWMODEL_ERROR);
draw_error_counter++;
}
}
// This code makes a lot of calls for rebinding variables just in case,
// and to get attribute locations. This is clearly not optimal, but the
// goal is stability.
void DrawModel(Model *m, GLuint program, const char* vertexVariableName, const char* normalVariableName, const char* texCoordVariableName)
{
if (m != NULL)
{
GLint loc;
glBindVertexArray(m->vao); // Select VAO
glBindBuffer(GL_ARRAY_BUFFER, m->vb);
loc = glGetAttribLocation(program, vertexVariableName);
if (loc >= 0)
{
glVertexAttribPointer(loc, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(loc);
}
else
ReportRerror("DrawModel", vertexVariableName);
if (normalVariableName!=NULL)
{
loc = glGetAttribLocation(program, normalVariableName);
if (loc >= 0)
{
glBindBuffer(GL_ARRAY_BUFFER, m->nb);
glVertexAttribPointer(loc, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(loc);
}
else
ReportRerror("DrawModel", normalVariableName);
}
if ((m->texCoordArray != NULL)&&(texCoordVariableName != NULL))
{
loc = glGetAttribLocation(program, texCoordVariableName);
if (loc >= 0)
{
glBindBuffer(GL_ARRAY_BUFFER, m->tb);
glVertexAttribPointer(loc, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(loc);
}
else
ReportRerror("DrawModel", texCoordVariableName);
}
glDrawElements(GL_TRIANGLES, m->numIndices, GL_UNSIGNED_INT, 0L);
}
}
void DrawWireframeModel(Model *m, GLuint program, const char* vertexVariableName, const char* normalVariableName, const char* texCoordVariableName)
{
if (m != NULL)
{
GLint loc;
glBindVertexArray(m->vao); // Select VAO
glBindBuffer(GL_ARRAY_BUFFER, m->vb);
loc = glGetAttribLocation(program, vertexVariableName);
if (loc >= 0)
{
glVertexAttribPointer(loc, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(loc);
}
else
ReportRerror("DrawWireframeModel", vertexVariableName);
if (normalVariableName!=NULL)
{
loc = glGetAttribLocation(program, normalVariableName);
if (loc >= 0)
{
glBindBuffer(GL_ARRAY_BUFFER, m->nb);
glVertexAttribPointer(loc, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(loc);
}
else
ReportRerror("DrawWireframeModel", normalVariableName);
}
if ((m->texCoordArray != NULL)&&(texCoordVariableName != NULL))
{
loc = glGetAttribLocation(program, texCoordVariableName);
if (loc >= 0)
{
glBindBuffer(GL_ARRAY_BUFFER, m->tb);
glVertexAttribPointer(loc, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(loc);
}
else
ReportRerror("DrawWireframeModel", texCoordVariableName);
}
glDrawElements(GL_LINE_STRIP, m->numIndices, GL_UNSIGNED_INT, 0L);
}
}
// Called from LoadModel, LoadModelSet and LoadDataToModel
// VAO and VBOs must already exist!
// Useful by its own when the model changes on CPU
void ReloadModelData(Model *m)
{
glBindVertexArray(m->vao);
// VBO for vertex data
glBindBuffer(GL_ARRAY_BUFFER, m->vb);
glBufferData(GL_ARRAY_BUFFER, m->numVertices*3*sizeof(GLfloat), m->vertexArray, GL_STATIC_DRAW);
// VBO for normal data
glBindBuffer(GL_ARRAY_BUFFER, m->nb);
glBufferData(GL_ARRAY_BUFFER, m->numVertices*3*sizeof(GLfloat), m->normalArray, GL_STATIC_DRAW);
// VBO for texture coordinate data NEW for 5b
if (m->texCoordArray != NULL)
{
glBindBuffer(GL_ARRAY_BUFFER, m->tb);
glBufferData(GL_ARRAY_BUFFER, m->numVertices*2*sizeof(GLfloat), m->texCoordArray, GL_STATIC_DRAW);
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m->ib);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, m->numIndices*sizeof(GLuint), m->indexArray, GL_STATIC_DRAW);
}
static void GenModelBuffers(Model *m)
{
glGenVertexArrays(1, &m->vao);
glGenBuffers(1, &m->vb);
glGenBuffers(1, &m->ib);
glGenBuffers(1, &m->nb);
if (m->texCoordArray != NULL)
glGenBuffers(1, &m->tb);
ReloadModelData(m);
}
// For simple models
Model* LoadModel(const char* name)
{
Model* model = NULL;
Mesh* mesh = LoadOBJ(name);
DecomposeToTriangles(mesh);
GenerateNormals(mesh);
model = GenerateModel(mesh);
DisposeMesh(mesh);
GenModelBuffers(model);
return model;
}
// For multiple part models
Model** LoadModelSet(const char* name)
{
Mesh* mesh = LoadOBJ(name);
Mesh **mm = SplitToMeshes(mesh);
int i, ii;
for (i = 0; mm[i] != NULL; i++) {}
Model **md = (Model **)calloc(sizeof(Model *), i+1);
for (ii = 0; mm[ii] != NULL; ii++)
{
DecomposeToTriangles(mm[ii]);
GenerateNormals(mm[ii]);
md[ii] = GenerateModel(mm[ii]);
DisposeMesh(mm[ii]);
}
free(mm);
DisposeMtlList(gMaterials);
DisposeMesh(mesh);
gMtlNameList = NULL;
gMaterials = NULL;
for (int i = 0; md[i] != NULL; i++)
GenModelBuffers(md[i]);
return md;
}
// Loader for inline data to Model (almost same as LoadModelPlus)
Model* LoadDataToModel(
vec3 *vertices,
vec3 *normals,
vec2 *texCoords,
vec3 *colors,
GLuint *indices,
int numVert,
int numInd)
{
Model* m = (Model *)malloc(sizeof(Model));
memset(m, 0, sizeof(Model));
m->vertexArray = vertices;
m->texCoordArray = texCoords;
m->normalArray = normals;
m->indexArray = indices;
m->numVertices = numVert;
m->numIndices = numInd;
GenModelBuffers(m);
return m;
}
// Cleanup function, not tested!
void DisposeModel(Model *m)
{
if (m != NULL)
{
if (m->vertexArray != NULL)
free(m->vertexArray);
if (m->normalArray != NULL)
free(m->normalArray);
if (m->texCoordArray != NULL)
free(m->texCoordArray);
if (m->colorArray != NULL) // obsolete?
free(m->colorArray);
if (m->indexArray != NULL)
free(m->indexArray);
// Lazy error checking here since "glDeleteBuffers silently ignores 0's and names that do not correspond to existing buffer objects."
glDeleteBuffers(1, &m->vb);
glDeleteBuffers(1, &m->ib);
glDeleteBuffers(1, &m->nb);
glDeleteBuffers(1, &m->tb);
glDeleteVertexArrays(1, &m->vao);
if (m->material != NULL)
free(m->material);
}
free(m);
}
|