/* |
Copyright (C) 2014 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
|
Abstract: |
|
Utility for loading OBJ model files |
|
*/ |
|
#import <simd/simd.h> |
|
#import <QuartzCore/QuartzCore.h> |
#import <string.h> |
#include <map> |
|
#import "AAPLOBJModel.h" |
|
#define REALLOC_ELEMENT_INCREASE 1000 |
|
static const uint32_t kSzFloat = sizeof(float); |
|
using namespace std; |
|
typedef enum |
{ |
PARSE_MODE_OBJ = 0, |
PARSE_MODE_MTL, |
} ParseMode; |
|
static void removeLeadingWhitespace(char *string) |
{ |
if (string == NULL) |
return; |
|
char *curr = string; |
|
while (*curr != '\0' && isspace(*curr)) |
++curr; |
|
memmove(string, curr, strlen(curr)+1); |
} |
|
static void removeTrailingWhitespace(char *string) |
{ |
if (string == NULL) |
return; |
|
size_t length = strlen(string); |
|
if (length > 0) |
{ |
char *curr = string+length-1; |
|
while (isspace(*curr)) |
--curr; |
|
*(curr+1) = '\0'; |
} |
} |
|
struct FaceVertex |
{ |
uint32_t v; |
uint32_t vt; |
uint32_t vn; |
}; |
|
static bool operator== (const FaceVertex &v1, const FaceVertex &v2) |
{ |
return ((v1.v == v2.v) && (v1.vt == v2.vt) && (v1.vn == v2.vn)); |
} |
|
static bool operator< (const FaceVertex &v1, const FaceVertex &v2) |
{ |
bool result; |
if (v1.v < v2.v) |
result = true; |
else if (v1.v > v2.v) |
result = false; |
else if (v1.vt < v2.vt) |
result = true; |
else if (v1.vt > v2.vt) |
result = false; |
else if (v1.vn < v2.vn) |
result = true; |
else if (v1.vn > v2.vn) |
result = false; |
else |
{ |
assert(v1 == v2); |
result = false; |
} |
|
return result; |
} |
|
typedef std::map<FaceVertex, uint32_t> FaceVertexMap; |
|
#pragma mark - |
#pragma mark AAPLOBJModel |
#pragma mark - |
|
@interface AAPLOBJModel () |
- (BOOL)parseString:(const char *)string mode:(ParseMode)mode; |
- (BOOL)parseObjModeDefinitionArguments:(char *)readBuffer definitionIndex:(int)definitionIndex; |
- (BOOL)parseMtlModeDefinitionArguments:(char *)readBuffer definitionIndex:(int)definitionIndex; |
- (BOOL)constructOpenGLData; |
@end |
|
@implementation AAPLOBJModel |
{ |
NSString *filePath; |
BOOL shouldComputeTangentSpace; |
BOOL shouldNormalizeNormals; |
|
NSMutableArray *comments; |
NSMutableDictionary *objects; |
|
NSMutableDictionary *currentObject; |
AAPLOBJModelGroup *currentGroup; |
|
NSMutableArray *materials; |
AAPLObjMaterial *currentMaterial; |
|
float *rawVertexData; |
float *rawVertexTextureData; |
float *rawVertexNormalData; |
|
size_t rawVertexDataAllocElementSize; |
size_t rawVertexTextureDataAllocElementSize; |
size_t rawVertexNormalDataAllocElementSize; |
|
int currentRawVertexIndex; |
int currentRawVertexTextureIndex; |
int currentRawVertexNormalIndex; |
|
int actualRawVertexCount; |
int actualRawVertexTextureCount; |
int actualRawVertexNormalCount; |
|
BOOL faceDefinitionFound; |
|
BOOL faceDefinedVertex, faceDefinedVertexTexture, faceDefinedVertexNormal; |
|
// packed vertex data |
NSData *vertexData; |
float *vertexDataInternal; |
|
size_t vertexDataAllocElementSize; |
int currentVertexDataIndex; |
|
NSMutableArray *vertexDataAttributes; |
} |
|
@synthesize vertexDataAllocElementSize; |
@synthesize filePath; |
@synthesize comments; |
@synthesize materials; |
@synthesize vertexData; |
@synthesize vertexDataAttributes; |
@synthesize objects; |
|
- (id)initWithContentsOfFile:(NSString *)inputFilePath computeTangentSpace:(BOOL)computeTangentSpace normalizeNormals:(BOOL)normalizeNormals |
{ |
self = [super init]; |
if (self) |
{ |
CFTimeInterval startTime = CACurrentMediaTime(); |
|
filePath = [inputFilePath copy]; |
|
shouldComputeTangentSpace = computeTangentSpace; |
shouldNormalizeNormals = normalizeNormals; |
|
NSError *error; |
NSString *fileString = [NSString stringWithContentsOfFile:inputFilePath encoding:NSUTF8StringEncoding error:&error]; |
if (!fileString) |
{ |
NSLog(@"Failed to open obj file: %@, error: %@", inputFilePath, error); |
} |
else |
{ |
comments = [[NSMutableArray alloc] initWithCapacity:10]; |
objects = [[NSMutableDictionary alloc] initWithCapacity:10]; |
|
materials = [[NSMutableArray alloc] initWithCapacity:10]; |
|
currentObject = [[NSMutableDictionary alloc] initWithCapacity:10]; |
[objects setObject:currentObject forKey:AAPLOBJModelObjectDefaultKey]; |
currentGroup = [[AAPLOBJModelGroup alloc] init]; |
[currentObject setValue:currentGroup forKey:AAPLOBJModelGroupDefaultKey]; |
|
rawVertexDataAllocElementSize = REALLOC_ELEMENT_INCREASE; |
rawVertexTextureDataAllocElementSize = REALLOC_ELEMENT_INCREASE; |
rawVertexNormalDataAllocElementSize = REALLOC_ELEMENT_INCREASE; |
|
rawVertexData = (float *)malloc(4 * rawVertexDataAllocElementSize * kSzFloat); |
rawVertexTextureData = (float *)malloc(3 * rawVertexTextureDataAllocElementSize * kSzFloat); |
rawVertexNormalData = (float *)malloc(3 * rawVertexNormalDataAllocElementSize * kSzFloat); |
|
if ([self parseString:[fileString UTF8String] mode:PARSE_MODE_OBJ]) |
{ |
NSLog(@"Parse successful"); |
|
if ([self constructOpenGLData]) |
NSLog(@"Construction of OpenGL data successful"); |
else |
NSLog(@"Construction of OpenGL data failed"); |
} |
else |
NSLog(@"Parse failed"); |
|
for (NSDictionary *object in [objects allValues]) |
{ |
for (AAPLOBJModelGroup *group in [object allValues]) |
{ |
if (group->rawFaceData) |
free(group->rawFaceData); |
} |
} |
|
free(rawVertexData); |
free(rawVertexTextureData); |
free(rawVertexNormalData); |
|
NSLog(@"Time to parse and load file %@ was %f", inputFilePath, CACurrentMediaTime() - startTime); |
} |
} |
|
return self; |
} |
|
- (void)dealloc |
{ |
for (NSDictionary *object in [objects allValues]) |
{ |
for (AAPLOBJModelGroup *group in [object allValues]) |
{ |
group->indexData = nil; |
if (group->indexDataInternal) |
free(group->indexDataInternal); |
} |
} |
|
vertexData = nil; |
|
if (vertexDataInternal) |
{ |
delete [] (vertexDataInternal); |
} |
|
vertexDataAttributes = nil; |
|
materials = nil; |
|
objects = nil; |
comments = nil; |
|
filePath = nil; |
} |
|
typedef struct _DefType |
{ |
const char string[10]; |
size_t length; |
} DefType; |
|
enum |
{ |
OBJ_D_VERTEX_TEXTURE = 0, |
OBJ_D_VERTEX_NORMAL, |
OBJ_D_VERTEX, |
OBJ_D_FACE, |
OBJ_D_GROUP, |
OBJ_D_OBJECT, |
OBJ_D_COMMENT, |
OBJ_D_SMOOTH, |
OBJ_D_USE_MTL, |
OBJ_D_MTL_LIB, |
OBJ_D_COUNT, |
}; |
|
DefType objDefTypes[OBJ_D_COUNT] = |
{ |
{ "vt", 2 }, |
{ "vn", 2 }, |
{ "v", 1 }, |
{ "f", 1 }, |
{ "g", 1 }, |
{ "o", 1 }, |
{ "#", 1 }, |
{ "s", 1 }, |
{ "usemtl", 6 }, |
{ "mtllib", 6 }, |
}; |
|
enum |
{ |
MTL_D_NEW_MTL = 0, |
MTL_D_COMMENT, |
MTL_D_AMBIENT_COLOR, |
MTL_D_DIFFUSE_COLOR, |
MTL_D_SPECULAR_COLOR, |
MTL_D_SPECULAR_EXPONENT, |
MTL_D_INDEX_OF_REFRACTION, |
MTL_D_ILLUMINATION_MODEL, |
MTL_D_DISSOLVE, |
MTL_D_TRANSPARENCY, |
MTL_D_TRANSMISSION_FILTER, |
MTL_D_AMBIENT_MAP, |
MTL_D_DIFFUSE_MAP, |
MTL_D_SPECULAR_MAP, |
MTL_D_BUMP_MAP, |
MTL_D_COUNT, |
}; |
|
DefType mtlDefTypes[MTL_D_COUNT] = |
{ |
{ "newmtl", 6 }, |
{ "#", 1 }, |
{ "Ka", 2 }, |
{ "Kd", 2 }, |
{ "Ks", 2 }, |
{ "Ns", 2 }, |
{ "Ni", 2 }, |
{ "illum", 5 }, |
{ "d", 1 }, |
{ "Tr", 2 }, |
{ "Tf", 2 }, |
{ "map_Ka", 6 }, |
{ "map_Kd", 6 }, |
{ "map_Ks", 6 }, |
{ "map_bump", 8 }, |
}; |
|
- (BOOL)parseString:(const char *)string mode:(ParseMode)mode |
{ |
BOOL success = YES; |
|
DefType *defTypes = objDefTypes; |
int defTypeCount = OBJ_D_COUNT; |
if (mode == PARSE_MODE_MTL) |
{ |
defTypes = mtlDefTypes; |
defTypeCount = MTL_D_COUNT; |
} |
|
BOOL foundDef; |
int foundDefIndex = 0; |
int defTypeCharsMatchCount; |
int i,j; |
|
const char *currStringPtr = string; |
size_t maxReadBufferSize = 1000; |
char readBuffer[maxReadBufferSize]; |
int readCount; |
|
while (*currStringPtr != '\0') |
{ |
foundDef = NO; |
|
for (i=0; i < defTypeCount && foundDef == NO; i++) |
{ |
const char *currMatchStringPtr = currStringPtr; |
|
while (*(currMatchStringPtr) == '\t' || *(currMatchStringPtr) == ' ') |
++currMatchStringPtr; |
|
defTypeCharsMatchCount = 0; |
for (j=0; j < defTypes[i].length && *(currMatchStringPtr+j) != '\0'; j++) |
{ |
if (*(currMatchStringPtr+j) == defTypes[i].string[j]) |
++defTypeCharsMatchCount; |
} |
if (defTypeCharsMatchCount == defTypes[i].length) |
{ |
foundDef = YES; |
foundDefIndex = i; |
} |
} |
|
if (foundDef) |
{ |
// increment by the definition string |
while (*(currStringPtr) == '\t' || *(currStringPtr) == ' ') |
++currStringPtr; |
|
int lengthIncrCount = 0; |
while (*currStringPtr != '\0' && lengthIncrCount < defTypes[foundDefIndex].length) |
{ |
++currStringPtr; |
++lengthIncrCount; |
} |
|
// consume data after definitions |
readCount = 0; |
while (*currStringPtr != '\0' && *currStringPtr != '\n') |
{ |
if (readCount < maxReadBufferSize-1) |
readBuffer[readCount] = *currStringPtr; |
|
++currStringPtr; |
|
++readCount; |
} |
readBuffer[readCount] = '\0'; |
|
removeLeadingWhitespace(readBuffer); |
removeTrailingWhitespace(readBuffer); |
|
// parse the consumed data |
if (readCount > 0) |
{ |
if (mode == PARSE_MODE_OBJ) |
success = [self parseObjModeDefinitionArguments:readBuffer definitionIndex:foundDefIndex]; |
else |
success = [self parseMtlModeDefinitionArguments:readBuffer definitionIndex:foundDefIndex]; |
} |
} |
else |
{ |
// ignore any lines where a definition type wasn't found |
while (*currStringPtr != '\0' && *currStringPtr != '\n') |
++currStringPtr; |
} |
|
// consume a character for the newline left over from consumption |
++currStringPtr; |
} |
|
return success; |
} |
|
- (BOOL)parseObjModeDefinitionArguments:(char *)readBuffer definitionIndex:(int)definitionIndex |
{ |
if (definitionIndex == OBJ_D_COMMENT) |
{ |
NSString *comment = [NSString stringWithCString:readBuffer encoding:NSUTF8StringEncoding]; |
if (comment) |
[comments addObject:comment]; |
else |
NSLog(@"Unable to add comment with read buffer: %s", readBuffer); |
} |
else if (definitionIndex == OBJ_D_OBJECT) |
{ |
currentObject = [[NSMutableDictionary alloc] initWithCapacity:10]; |
NSString *string = [NSString stringWithCString:readBuffer encoding:NSUTF8StringEncoding]; |
if (string) |
{ |
[objects setObject:currentObject forKey:string]; |
currentGroup = [[AAPLOBJModelGroup alloc] init]; |
[currentObject setValue:currentGroup forKey:AAPLOBJModelGroupDefaultKey]; |
} |
else |
NSLog(@"Unable to add object with read buffer: %s", readBuffer); |
} |
else if (definitionIndex == OBJ_D_GROUP) |
{ |
NSString *string = [NSString stringWithCString:readBuffer encoding:NSUTF8StringEncoding]; |
if (string) |
{ |
currentGroup = [[AAPLOBJModelGroup alloc] init]; |
|
currentGroup->name = [string copy]; |
|
[currentObject setValue:currentGroup forKey:string]; |
} |
else |
NSLog(@"Unable to add group with read buffer: %s", readBuffer); |
} |
else if (definitionIndex == OBJ_D_USE_MTL) |
{ |
AAPLObjMaterialUsage *materialUsage = [[AAPLObjMaterialUsage alloc] init]; |
NSString *string = [NSString stringWithCString:readBuffer encoding:NSUTF8StringEncoding]; |
if (string) |
{ |
materialUsage->name = [string copy]; |
|
materialUsage->indexRange = NSMakeRange(currentGroup->currentRawFaceIndex, 0); |
|
[currentGroup->materialUsages addObject:materialUsage]; |
|
NSLog(@"Added material usage: %@, to group: %@", materialUsage->name, currentGroup->name); |
} |
else |
NSLog(@"Unable to add material usage to current group with read buffer: %s", readBuffer); |
} |
else if (definitionIndex == OBJ_D_MTL_LIB) |
{ |
NSString *string = [NSString stringWithCString:readBuffer encoding:NSUTF8StringEncoding]; |
if (string) |
{ |
NSLog(@"Found material lib: %@", string); |
|
NSString *mtlFilePath = [filePath stringByDeletingLastPathComponent]; |
mtlFilePath = [mtlFilePath stringByAppendingPathComponent:string]; |
|
NSError *error; |
NSString *fileString = [NSString stringWithContentsOfFile:mtlFilePath encoding:NSUTF8StringEncoding error:&error]; |
if (!fileString) |
{ |
NSLog(@"Failed to open mtl file: %@, error: %@", mtlFilePath, error); |
} |
else |
{ |
[self parseString:[fileString UTF8String] mode:PARSE_MODE_MTL]; |
} |
} |
else |
NSLog(@"Found material lib, but unable to convert read buffer: %s", readBuffer); |
} |
else if (definitionIndex == OBJ_D_VERTEX || definitionIndex == OBJ_D_VERTEX_TEXTURE || definitionIndex == OBJ_D_VERTEX_NORMAL) |
{ |
if (faceDefinitionFound) |
{ |
NSLog(@"Found a vertex definition after a face definition, is this allowed?"); |
return NO; |
} |
|
const char *sep = " \t"; |
char *token; |
int count; |
float value; |
|
int defTypeVertexCount = 3; |
if (definitionIndex == OBJ_D_VERTEX) |
defTypeVertexCount = 4; |
|
int *ptrCurrentIndex = nullptr; |
size_t *ptrAllocElementSize = nullptr; |
float **ptrRawData = nullptr; |
int *ptrActualRawVertexCount = nullptr; |
|
if (definitionIndex == OBJ_D_VERTEX) |
{ |
ptrCurrentIndex = ¤tRawVertexIndex; |
ptrAllocElementSize = &rawVertexDataAllocElementSize; |
ptrRawData = &rawVertexData; |
ptrActualRawVertexCount = &actualRawVertexCount; |
} |
else if (definitionIndex == OBJ_D_VERTEX_TEXTURE) |
{ |
ptrCurrentIndex = ¤tRawVertexTextureIndex; |
ptrAllocElementSize = &rawVertexTextureDataAllocElementSize; |
ptrRawData = &rawVertexTextureData; |
ptrActualRawVertexCount = &actualRawVertexTextureCount; |
} |
else if (definitionIndex == OBJ_D_VERTEX_NORMAL) |
{ |
ptrCurrentIndex = ¤tRawVertexNormalIndex; |
ptrAllocElementSize = &rawVertexNormalDataAllocElementSize; |
ptrRawData = &rawVertexNormalData; |
ptrActualRawVertexCount = &actualRawVertexNormalCount; |
} |
|
for (count = 0, token = strtok(readBuffer, sep); token && count < defTypeVertexCount; token = strtok(NULL, sep), ++count) |
{ |
value = strtof(token, NULL); |
|
(*ptrRawData)[(*ptrCurrentIndex) * defTypeVertexCount + count] = value; |
} |
|
++(*ptrCurrentIndex); |
|
if (*ptrCurrentIndex >= *ptrAllocElementSize) |
{ |
NSLog(@"increasing alloc element size from: %d, by: %d", *ptrCurrentIndex, REALLOC_ELEMENT_INCREASE); |
|
*ptrAllocElementSize += REALLOC_ELEMENT_INCREASE; |
float *temp = (float *)realloc(*ptrRawData, defTypeVertexCount * (*ptrAllocElementSize) * kSzFloat); |
if (!temp) |
{ |
NSLog(@"Unable to realloc vertex type data"); |
return NO; |
} |
else |
*ptrRawData = temp; |
} |
|
*ptrActualRawVertexCount = count; |
} |
else if (definitionIndex == OBJ_D_FACE) |
{ |
faceDefinitionFound = YES; |
|
const char *sep = " \t"; |
char *token; |
int vertexCount = 0; |
int maxFaceBufferSize = 100; |
int currentFaceBufferIndex; |
char faceBuffer[maxFaceBufferSize]; |
int vertexTypeCount; |
int v, vt, vn; |
|
if (!currentGroup->rawFaceData) |
{ |
currentGroup->rawFaceDataAllocElementSize = REALLOC_ELEMENT_INCREASE; |
currentGroup->rawFaceData = (int *)malloc(3 * 3 * currentGroup->rawFaceDataAllocElementSize * sizeof(int)); |
} |
|
for (token = strtok(readBuffer, sep); token; token = strtok(NULL, sep)) |
{ |
if (vertexCount > 2) |
{ |
NSLog(@"Found a face definition with more than three vertices"); |
return NO; |
} |
|
currentFaceBufferIndex = 0; |
vertexTypeCount = 0; |
|
v = vt = vn = 0; |
|
while (1) |
{ |
if (*token == '/' || *token == '\0') |
{ |
faceBuffer[currentFaceBufferIndex] = '\0'; |
currentFaceBufferIndex = 0; |
|
if (vertexTypeCount == 0) |
v = (int)strtol(faceBuffer, (char **)NULL, 10); |
else if (vertexTypeCount == 1) |
vt = (int)strtol(faceBuffer, (char **)NULL, 10); |
else if (vertexTypeCount == 2) |
vn = (int)strtol(faceBuffer, (char **)NULL, 10); |
|
if (*token == '\0') |
break; |
|
++vertexTypeCount; |
} |
else |
{ |
faceBuffer[currentFaceBufferIndex++] = *token; |
} |
|
++token; |
} |
|
currentGroup->rawFaceData[currentGroup->currentRawFaceIndex * 9 + (vertexCount * 3) + 0] = v; |
currentGroup->rawFaceData[currentGroup->currentRawFaceIndex * 9 + (vertexCount * 3) + 1] = vt; |
currentGroup->rawFaceData[currentGroup->currentRawFaceIndex * 9 + (vertexCount * 3) + 2] = vn; |
|
if (v != 0) |
faceDefinedVertex = YES; |
if (vt != 0) |
faceDefinedVertexTexture = YES; |
if (vn != 0) |
faceDefinedVertexNormal = YES; |
|
++vertexCount; |
} |
|
++(currentGroup->currentRawFaceIndex); |
|
if (currentGroup->currentRawFaceIndex >= currentGroup->rawFaceDataAllocElementSize) |
{ |
currentGroup->rawFaceDataAllocElementSize += REALLOC_ELEMENT_INCREASE; |
int *temp = (int *)realloc(currentGroup->rawFaceData, |
3 * 3 * currentGroup->rawFaceDataAllocElementSize * sizeof(int)); |
if (!temp) |
{ |
NSLog(@"Unable to realloc face type data"); |
return NO; |
} |
else |
currentGroup->rawFaceData = temp; |
} |
|
if (!currentGroup->actualRawFaceVertexCount) |
currentGroup->actualRawFaceVertexCount = vertexCount; |
|
if (currentGroup->actualRawFaceVertexCount != 3) |
{ |
NSLog(@"Unsupported vertex count in face definition: %d", currentGroup->actualRawFaceVertexCount); |
return NO; |
} |
} |
|
return YES; |
} |
|
- (BOOL)parseMtlModeDefinitionArguments:(char *)readBuffer definitionIndex:(int)definitionIndex |
{ |
NSString *string = [NSString stringWithCString:readBuffer encoding:NSUTF8StringEncoding]; |
if (!string) |
{ |
NSLog(@"Failed to create material string from readBuffer: %s", readBuffer); |
return NO; |
} |
|
if (definitionIndex == MTL_D_NEW_MTL) |
{ |
currentMaterial = [[AAPLObjMaterial alloc] init]; |
[materials addObject:currentMaterial]; |
|
currentMaterial->name = [string copy]; |
} |
else if (definitionIndex == MTL_D_AMBIENT_COLOR || |
definitionIndex == MTL_D_DIFFUSE_COLOR || |
definitionIndex == MTL_D_SPECULAR_COLOR || |
definitionIndex == MTL_D_TRANSMISSION_FILTER) |
{ |
const char *sep = " \t"; |
char *token; |
float *values = nullptr; |
int count; |
int countLimit = 3; |
|
if (definitionIndex == MTL_D_AMBIENT_COLOR) |
{ |
values = currentMaterial->ambientColorArray; |
currentMaterial->ambientColor = [NSValue valueWithPointer:values]; |
} |
else if (definitionIndex == MTL_D_DIFFUSE_COLOR) |
{ |
values = currentMaterial->diffuseColorArray; |
currentMaterial->diffuseColor = [NSValue valueWithPointer:values]; |
} |
else if (definitionIndex == MTL_D_SPECULAR_COLOR) |
{ |
values = currentMaterial->specularColorArray; |
currentMaterial->specularColor = [NSValue valueWithPointer:values]; |
} |
else if (definitionIndex == MTL_D_TRANSMISSION_FILTER) |
{ |
values = currentMaterial->transmissionFilterArray; |
currentMaterial->transmissionFilter = [NSValue valueWithPointer:values]; |
} |
|
for (count=0, token = strtok(readBuffer, sep); token && count < countLimit; token = strtok(NULL, sep), count++) |
{ |
values[count] = strtof(token, NULL); |
} |
} |
else if (definitionIndex == MTL_D_SPECULAR_EXPONENT) |
{ |
currentMaterial->specularExponent = [NSNumber numberWithFloat:strtof(readBuffer, NULL)]; |
} |
else if (definitionIndex == MTL_D_INDEX_OF_REFRACTION) |
{ |
currentMaterial->indexOfRefraction = [NSNumber numberWithFloat:strtof(readBuffer, NULL)]; |
} |
else if (definitionIndex == MTL_D_ILLUMINATION_MODEL) |
{ |
currentMaterial->illuminationModel = [NSNumber numberWithInt:(int)strtof(readBuffer, NULL)]; |
} |
else if (definitionIndex == MTL_D_DISSOLVE) |
{ |
currentMaterial->dissolve = [NSNumber numberWithFloat:strtof(readBuffer, NULL)]; |
} |
else if (definitionIndex == MTL_D_TRANSPARENCY) |
{ |
currentMaterial->transparency = [NSNumber numberWithFloat:strtof(readBuffer, NULL)]; |
} |
else if (definitionIndex == MTL_D_AMBIENT_MAP) |
{ |
currentMaterial->ambientMapName = [string copy]; |
} |
else if (definitionIndex == MTL_D_DIFFUSE_MAP) |
{ |
currentMaterial->diffuseMapName = [string copy]; |
} |
else if (definitionIndex == MTL_D_SPECULAR_MAP) |
{ |
currentMaterial->specularMapName = [string copy]; |
} |
else if (definitionIndex == MTL_D_BUMP_MAP) |
{ |
currentMaterial->bumpMapName = [string copy]; |
} |
|
return YES; |
} |
|
- (BOOL)canComputeTangentSpace |
{ |
return (faceDefinedVertex && rawVertexData && actualRawVertexCount == 3 && |
faceDefinedVertexNormal && rawVertexNormalData && actualRawVertexNormalCount == 3 && |
faceDefinedVertexTexture && rawVertexTextureData && (actualRawVertexTextureCount == 2 || actualRawVertexTextureCount == 3) && |
shouldComputeTangentSpace); |
} |
|
- (BOOL) constructOpenGLData |
{ |
NSMutableArray *faceGroups = [[NSMutableArray alloc] initWithCapacity:10]; |
|
if(!faceGroups) |
{ |
NSLog(@">> ERROR: failed creating a backing-store for face groups!"); |
|
return NO; |
} // if |
|
FaceVertexMap uniqueVertices; |
|
FaceVertexMap::const_iterator it; |
|
// find all groups with face data |
for (NSDictionary *object in [objects allValues]) |
{ |
for (AAPLOBJModelGroup *group in [object allValues]) |
{ |
if (group->rawFaceData) |
{ |
[faceGroups addObject:group]; |
} |
} |
} // for |
|
int i; |
int j; |
|
int *faceData = NULL; |
|
FaceVertex fv; |
uint32_t index; |
|
// unique face vertices |
for (AAPLOBJModelGroup *group in faceGroups) |
{ |
group->bytesPerIndex = 2; |
group->indexCount = group->currentRawFaceIndex * group->actualRawFaceVertexCount; |
|
for (i=0; i < group->currentRawFaceIndex; ++i) |
{ |
faceData = &group->rawFaceData[i * (group->actualRawFaceVertexCount * 3)]; |
|
for (j=0; j < group->actualRawFaceVertexCount; ++j) |
{ |
fv.v = faceData[(j * 3) + 0]; |
fv.vt = faceData[(j * 3) + 1]; |
fv.vn = faceData[(j * 3) + 2]; |
|
it = uniqueVertices.find(fv); |
|
if (it == uniqueVertices.end()) |
{ |
// map a new vertex |
index = (uint32_t)uniqueVertices.size(); |
uniqueVertices[fv] = index; |
} |
else |
{ |
index = it->second; |
} |
|
if (index >= 65536) |
{ |
group->bytesPerIndex = 4; |
} |
} // for |
} // for |
} // for |
|
// allocate index data for groups |
for (AAPLOBJModelGroup *group in faceGroups) |
{ |
group->indexDataInternal = malloc(group->currentRawFaceIndex * |
group->actualRawFaceVertexCount * |
group->bytesPerIndex); |
|
if(!group->indexDataInternal) |
{ |
NSLog(@">> ERROR: failed creating a backing-store for internal index data group!"); |
|
return NO; |
} // if |
|
group->indexData = [[NSData alloc] initWithBytesNoCopy:group->indexDataInternal |
length:group->currentRawFaceIndex * |
group->actualRawFaceVertexCount * |
group->bytesPerIndex |
freeWhenDone:NO]; |
|
if(!group->indexData) |
{ |
NSLog(@">> ERROR: failed creating a backing-store for index data group!"); |
|
return NO; |
} // if |
} // for |
|
// normalize vertex normals if requested |
if (shouldNormalizeNormals && |
(actualRawVertexNormalCount == 2 || actualRawVertexNormalCount == 3)) |
{ |
NSLog(@"Normalizing vertex normals"); |
|
int i; |
int j = 0; |
|
simd::float2 u = 0; |
simd::float3 v = 0; |
|
for (i=0; i < currentRawVertexNormalIndex; i++) |
{ |
j = i * actualRawVertexNormalCount; |
|
if (actualRawVertexNormalCount == 2) |
{ |
u.x = rawVertexNormalData[j + 0]; |
u.y = rawVertexNormalData[j + 1]; |
|
u = simd::normalize(u); |
|
rawVertexNormalData[j + 0] = u.x; |
rawVertexNormalData[j + 1] = u.y; |
} |
else if (actualRawVertexNormalCount == 3) |
{ |
v.x = rawVertexNormalData[j + 0]; |
v.y = rawVertexNormalData[j + 1]; |
v.z = rawVertexNormalData[j + 2]; |
|
v = simd::normalize(v); |
|
rawVertexNormalData[j + 0] = v.x; |
rawVertexNormalData[j + 1] = v.y; |
rawVertexNormalData[j + 2] = v.z; |
} |
} // for |
} // if |
|
// define vertex attributes |
vertexDataAttributes = [[NSMutableArray alloc] initWithCapacity:10]; |
|
if(!vertexDataAttributes) |
{ |
NSLog(@">> ERROR: failed creating a backing-store for vertex data attributes!"); |
|
return NO; |
} // if |
|
size_t stride = 0; |
int elements = 0; |
|
if (faceDefinedVertex && rawVertexData) |
{ |
AAPLObjVertexAttribute *attrib = [[AAPLObjVertexAttribute alloc] init]; |
|
attrib->indexType = AAPLObjVertexAttributeTypePosition; |
attrib->size = actualRawVertexCount; |
attrib->offset = stride; |
|
stride += actualRawVertexCount * kSzFloat; |
elements += actualRawVertexCount; |
|
[vertexDataAttributes addObject:attrib]; |
} // if |
|
if (faceDefinedVertexNormal && rawVertexNormalData) |
{ |
AAPLObjVertexAttribute *attrib = [[AAPLObjVertexAttribute alloc] init]; |
|
attrib->indexType = AAPLObjVertexAttributeTypeNormal; |
attrib->size = actualRawVertexNormalCount; |
attrib->offset = stride; |
|
stride += actualRawVertexNormalCount * kSzFloat; |
elements += actualRawVertexNormalCount; |
|
[vertexDataAttributes addObject:attrib]; |
} // if |
|
if (faceDefinedVertexTexture && rawVertexTextureData) |
{ |
AAPLObjVertexAttribute *attrib = [[AAPLObjVertexAttribute alloc] init]; |
|
attrib->indexType = AAPLObjVertexAttributeTypeTexcoord0; |
attrib->size = actualRawVertexTextureCount; |
attrib->offset = stride; |
|
stride += actualRawVertexTextureCount * kSzFloat; |
elements += actualRawVertexTextureCount; |
|
[vertexDataAttributes addObject:attrib]; |
} // if |
|
if ([self canComputeTangentSpace]) |
{ |
AAPLObjVertexAttribute *attrib = [[AAPLObjVertexAttribute alloc] init]; |
|
int tangentSpaceElementCount = 3; |
attrib->indexType = AAPLObjVertexAttributeTypeTangent; |
attrib->size = tangentSpaceElementCount; |
attrib->offset = stride; |
|
stride += tangentSpaceElementCount * kSzFloat; |
elements += tangentSpaceElementCount; |
|
[vertexDataAttributes addObject:attrib]; |
|
attrib = [[AAPLObjVertexAttribute alloc] init]; |
attrib->indexType = AAPLObjVertexAttributeTypeBitangent; |
attrib->size = tangentSpaceElementCount; |
attrib->offset = stride; |
|
stride += tangentSpaceElementCount * kSzFloat; |
elements += tangentSpaceElementCount; |
|
[vertexDataAttributes addObject:attrib]; |
} // if |
|
for (AAPLObjVertexAttribute *attrib in vertexDataAttributes) |
{ |
attrib->stride = stride; |
} |
|
// allocate vertex data internal |
currentVertexDataIndex = int(uniqueVertices.size()); |
vertexDataAllocElementSize = currentVertexDataIndex; |
|
try |
{ |
vertexDataInternal = new float[(stride / kSzFloat) * vertexDataAllocElementSize]; |
} // try |
catch(std::bad_alloc& ba) |
{ |
NSLog(@">> ERROR: failed creating a backing-store for internal vertex data!"); |
|
return NO; |
} // catch |
|
int k; |
|
float *currentVertex = NULL; |
|
// fill out vertex data |
for (it = uniqueVertices.begin(); it != uniqueVertices.end(); it++) |
{ |
fv = it->first; |
|
currentVertex = &vertexDataInternal[it->second * elements]; |
|
if (fv.v) |
{ |
for (k=0; k < actualRawVertexCount; ++k) |
{ |
currentVertex[k] = rawVertexData[(fv.v-1) * 4 + k]; |
} |
} |
|
if (fv.vn) |
{ |
for (k=0; k < actualRawVertexNormalCount; ++k) |
{ |
currentVertex[actualRawVertexCount + k] = rawVertexNormalData[(fv.vn-1) * 3 + k]; |
} |
} |
|
if (fv.vt) |
{ |
for (k=0; k < actualRawVertexTextureCount; ++k) |
{ |
currentVertex[actualRawVertexCount + actualRawVertexNormalCount + k] = rawVertexTextureData[(fv.vt-1) * 3 + k]; |
} |
} // if |
} // for |
|
int *pFaceData = NULL; |
|
|
uint32_t va = 0; |
uint32_t vb = 0; |
uint32_t vta = 0; |
uint32_t vtb = 0; |
|
int ja = 0; |
int jb = 0; |
|
uint8_t *groupIndexDataInternal = NULL; |
|
// iterate faces |
for (AAPLOBJModelGroup *group in faceGroups) |
{ |
groupIndexDataInternal = (uint8_t *)group->indexDataInternal; |
|
for (i=0; i < group->currentRawFaceIndex; ++i) |
{ |
pFaceData = &group->rawFaceData[i * (group->actualRawFaceVertexCount * 3)]; |
|
for (j=0; j < group->actualRawFaceVertexCount; ++j) |
{ |
fv.v = pFaceData[(j * 3) + 0]; |
fv.vt = pFaceData[(j * 3) + 1]; |
fv.vn = pFaceData[(j * 3) + 2]; |
|
ja = (j+1)%3; |
va = pFaceData[(ja * 3) + 0]; |
vta = pFaceData[(ja * 3) + 2]; |
|
jb = (j+2)%3; |
vb = pFaceData[(jb * 3) + 0]; |
vtb = pFaceData[(jb * 3) + 2]; |
|
index = uniqueVertices[fv]; |
|
if (group->bytesPerIndex == 2) |
{ |
*((uint16_t *)groupIndexDataInternal) = uint16_t(index); |
} |
else |
{ |
*((uint32_t *)groupIndexDataInternal) = index; |
} |
|
groupIndexDataInternal += group->bytesPerIndex; |
} // for |
} // for |
} // for |
|
// compute tangent space tangents |
if ([self canComputeTangentSpace]) |
{ |
size_t i; |
|
AAPLObjVertexAttribute *vertexAttrib = nil, *normalAttrib = nil, *texCoordAttrib = nil, *tangentAttrib = nil; |
|
for (AAPLObjVertexAttribute *attrib in vertexDataAttributes) |
{ |
if (attrib->indexType == AAPLObjVertexAttributeTypePosition) |
{ |
vertexAttrib = attrib; |
} |
else if (attrib->indexType == AAPLObjVertexAttributeTypeNormal) |
{ |
normalAttrib = attrib; |
} |
else if (attrib->indexType == AAPLObjVertexAttributeTypeTexcoord0) |
{ |
texCoordAttrib = attrib; |
} |
else if (attrib->indexType == AAPLObjVertexAttributeTypeTangent) |
{ |
tangentAttrib = attrib; |
} |
} // for |
|
simd::float4* tangents = NULL; |
|
try |
{ |
tangents = new simd::float4[currentVertexDataIndex]; |
} // try |
catch(std::bad_alloc& ba) |
{ |
NSLog(@">> ERROR: failed creating a backing-store for tangents array!"); |
|
return NO; |
} // catch |
|
simd::float3* biTangents = NULL; |
|
try |
{ |
biTangents = new simd::float3[currentVertexDataIndex]; |
} // try |
catch(std::bad_alloc& ba) |
{ |
NSLog(@">> ERROR: failed creating a backing-store for bi-tangents array!"); |
|
return NO; |
} // catch |
|
std::memset(tangents, 0x0, currentVertexDataIndex * sizeof(simd::float4)); |
std::memset(biTangents, 0x0, currentVertexDataIndex * sizeof(simd::float3)); |
|
simd::float3 vertex[3]; |
simd::float2 texCoord[3]; |
|
simd::float3 vertexEdge[2]; |
simd::float2 texCoordEdge[2]; |
|
simd::float3 tangent_3f = 0.0f; |
simd::float3 biTangent_3f = 0.0f; |
|
simd::float4 tangent_4f = 0.0f; |
|
size_t dataIndex = 0; |
|
uint32_t vertexStride = uint32_t(vertexAttrib->stride/kSzFloat); |
uint32_t vertexOffset = uint32_t(vertexAttrib->offset/kSzFloat); |
uint32_t texCoordStride = uint32_t(texCoordAttrib->stride/kSzFloat); |
uint32_t texCoordOffset = uint32_t(texCoordAttrib->offset/kSzFloat); |
|
float *pVertexDataInternal = NULL; |
float *pTexCoordDataInternal = NULL; |
|
float scale = 0.0f; |
float cp = 0.0f; |
|
uint32_t index[3] = {0, 0, 0}; |
|
// Again, index data is always in triangles |
for (AAPLOBJModelGroup *group in faceGroups) |
{ |
index[0] = 0; |
index[1] = 0; |
index[2] = 0; |
|
for (i=0; i < group->indexCount; i += 3) |
{ |
if (group->bytesPerIndex == 2) |
{ |
index[0] = (uint32_t)(((uint16_t *)(group->indexDataInternal))[i+0]); |
index[1] = (uint32_t)(((uint16_t *)(group->indexDataInternal))[i+1]); |
index[2] = (uint32_t)(((uint16_t *)(group->indexDataInternal))[i+2]); |
} |
else if (group->bytesPerIndex == 4) |
{ |
index[0] = ((uint32_t *)(group->indexDataInternal))[i+0]; |
index[1] = ((uint32_t *)(group->indexDataInternal))[i+1]; |
index[2] = ((uint32_t *)(group->indexDataInternal))[i+2]; |
} |
|
// Vertices |
|
dataIndex = index[0] * vertexStride + vertexOffset; |
|
pVertexDataInternal = &vertexDataInternal[dataIndex]; |
|
vertex[0].x = pVertexDataInternal[0]; |
vertex[0].y = pVertexDataInternal[1]; |
vertex[0].z = pVertexDataInternal[2]; |
|
dataIndex = index[1] * vertexStride + vertexOffset; |
|
pVertexDataInternal = &vertexDataInternal[dataIndex]; |
|
vertex[1].x = pVertexDataInternal[0]; |
vertex[1].y = pVertexDataInternal[1]; |
vertex[1].z = pVertexDataInternal[2]; |
|
dataIndex = index[2] * vertexStride + vertexOffset; |
|
pVertexDataInternal = &vertexDataInternal[dataIndex]; |
|
vertex[2].x = pVertexDataInternal[0]; |
vertex[2].y = pVertexDataInternal[1]; |
vertex[2].z = pVertexDataInternal[2]; |
|
// Texture Coordinates |
|
dataIndex = index[0] * texCoordStride + texCoordOffset; |
|
pTexCoordDataInternal = &vertexDataInternal[dataIndex]; |
|
texCoord[0].x = pTexCoordDataInternal[0]; |
texCoord[0].y = pTexCoordDataInternal[1]; |
|
dataIndex = index[1] * texCoordStride + texCoordOffset; |
|
pTexCoordDataInternal = &vertexDataInternal[dataIndex]; |
|
texCoord[1].x = pTexCoordDataInternal[0]; |
texCoord[1].y = pTexCoordDataInternal[1]; |
|
dataIndex = index[2] * texCoordStride + texCoordOffset; |
|
pTexCoordDataInternal = &vertexDataInternal[dataIndex]; |
|
texCoord[2].x = pTexCoordDataInternal[0]; |
texCoord[2].y = pTexCoordDataInternal[1]; |
|
// Edges |
|
vertexEdge[0] = vertex[1] - vertex[0]; |
vertexEdge[1] = vertex[2] - vertex[0]; |
|
texCoordEdge[0] = texCoord[1] - texCoord[0]; |
texCoordEdge[1] = texCoord[2] - texCoord[0]; |
|
cp = texCoordEdge[0].x * texCoordEdge[1].y - texCoordEdge[0].y * texCoordEdge[1].x; |
|
if (cp != 0.0f) |
{ |
scale = 1.0f / cp; |
|
tangent_3f = (vertexEdge[0] * texCoordEdge[1].y) - (vertexEdge[1] * texCoordEdge[0].y); |
tangent_3f *= scale; |
|
biTangent_3f = (vertexEdge[1] * texCoordEdge[0].x) - (vertexEdge[0] * texCoordEdge[1].x); |
biTangent_3f *= scale; |
|
tangent_4f.x = tangent_3f.x; |
tangent_4f.y = tangent_3f.y; |
tangent_4f.z = tangent_3f.z; |
tangent_4f.w = 0.0f; |
|
tangents[index[0]] = tangents[index[0]] + tangent_4f; |
tangents[index[1]] = tangents[index[1]] + tangent_4f; |
tangents[index[2]] = tangents[index[2]] + tangent_4f; |
|
biTangents[index[0]] = biTangents[index[0]] + biTangent_3f; |
biTangents[index[1]] = biTangents[index[1]] + biTangent_3f; |
biTangents[index[2]] = biTangents[index[2]] + biTangent_3f; |
} // if |
} // for |
} // for |
|
simd::float3 normal_3f = 0.0f; |
|
uint32_t normalStride = uint32_t(normalAttrib->stride/kSzFloat); |
uint32_t normalOffset = uint32_t(normalAttrib->offset/kSzFloat); |
uint32_t tangentStride = uint32_t(tangentAttrib->stride/kSzFloat); |
uint32_t tangentOffset = uint32_t(tangentAttrib->offset/kSzFloat); |
|
float *pNormalDataInternal = NULL; |
|
for (i=0; i < currentVertexDataIndex; ++i) |
{ |
dataIndex = i * normalStride + normalOffset; |
|
pNormalDataInternal = &vertexDataInternal[dataIndex]; |
|
normal_3f.x = pNormalDataInternal[0]; |
normal_3f.y = pNormalDataInternal[1]; |
normal_3f.z = pNormalDataInternal[2]; |
|
tangent_3f.x = tangents[i].x; |
tangent_3f.y = tangents[i].y; |
tangent_3f.z = tangents[i].z; |
|
tangent_3f -= (normal_3f * simd::dot(tangent_3f, normal_3f)); |
tangent_3f = simd::normalize(tangent_3f); |
|
dataIndex = i * tangentStride + tangentOffset; |
|
vertexDataInternal[dataIndex + 0] = tangents[i].x; |
vertexDataInternal[dataIndex + 1] = tangents[i].y; |
vertexDataInternal[dataIndex + 2] = tangents[i].z; |
|
vertexDataInternal[dataIndex + 3] = biTangents[i].x; |
vertexDataInternal[dataIndex + 4] = biTangents[i].y; |
vertexDataInternal[dataIndex + 5] = biTangents[i].z; |
} // for |
|
delete [] tangents; |
delete [] biTangents; |
} // if |
|
// create vertex data |
vertexData = [[NSData alloc] initWithBytesNoCopy:vertexDataInternal |
length:stride * currentVertexDataIndex |
freeWhenDone:NO]; |
|
if(!vertexData) |
{ |
NSLog(@">> ERROR: failed creating a backing-store for vertex data!"); |
|
return NO; |
} // if |
|
AAPLObjMaterialUsage *materialUsage = nil; |
AAPLObjMaterialUsage *nextMaterialUsage = nil; |
|
for (AAPLOBJModelGroup *group in faceGroups) |
{ |
// update group's material usage index range |
if ([group->materialUsages count] > 0) |
{ |
for (i=0; [group->materialUsages count] > 1 && i < [group->materialUsages count]-1; ++i) |
{ |
materialUsage = [group->materialUsages objectAtIndex:i]; |
nextMaterialUsage = [group->materialUsages objectAtIndex:i+1]; |
|
materialUsage->indexRange.length = nextMaterialUsage->indexRange.location - materialUsage->indexRange.location; |
} // for |
|
materialUsage = [group->materialUsages objectAtIndex:i]; |
|
materialUsage->indexRange.length = (group->indexCount/3) - materialUsage->indexRange.location; |
} // if |
|
for (i=0; i < [group->materialUsages count]; ++i) |
{ |
materialUsage = [group->materialUsages objectAtIndex:i]; |
|
materialUsage->indexRange.location *= group->actualRawFaceVertexCount; |
materialUsage->indexRange.length *= group->actualRawFaceVertexCount; |
|
NSLog(@"Range for material: %@ in group: %@: %lu %lu", |
materialUsage->name, |
group->name, |
(unsigned long)materialUsage->indexRange.location, |
(unsigned long)materialUsage->indexRange.length); |
} // for |
|
// update group's material reference for each material usage |
for (materialUsage in group->materialUsages) |
{ |
for (AAPLObjMaterial *material in materials) |
{ |
if ([[material name] isEqualToString:[materialUsage name]]) |
{ |
materialUsage->material = material; |
break; |
} // if |
} // for |
} // for |
} // for |
|
return YES; |
} |
|
@end |
|
#pragma mark - |
#pragma mark AAPLOBJModelGroup |
#pragma mark - |
|
@implementation AAPLOBJModelGroup |
|
@synthesize name; |
|
@synthesize indexData; |
@synthesize bytesPerIndex; |
@synthesize indexCount; |
|
@synthesize materialUsages; |
|
- (id)init |
{ |
self = [super init]; |
if (self) |
{ |
bytesPerIndex = 2; |
|
materialUsages = [[NSMutableArray alloc] initWithCapacity:10]; |
} |
|
return self; |
} |
|
- (void)dealloc |
{ |
name = nil; |
materialUsages = nil; |
} |
|
@end |
|
#pragma mark - |
#pragma mark AAPLObjVertexAttribute |
#pragma mark - |
|
@implementation AAPLObjVertexAttribute |
|
@synthesize indexType; |
@synthesize size; |
@synthesize stride; |
@synthesize offset; |
|
@end |
|
#pragma mark - |
#pragma mark AAPLObjMaterial |
#pragma mark - |
|
@implementation AAPLObjMaterial |
|
@synthesize name; |
|
@synthesize ambientColor; |
@synthesize diffuseColor; |
@synthesize specularColor; |
|
@synthesize specularExponent; |
|
@synthesize indexOfRefraction; |
|
@synthesize illuminationModel; |
|
@synthesize dissolve; |
|
@synthesize transparency; |
|
@synthesize transmissionFilter; |
|
@synthesize ambientMapName; |
@synthesize diffuseMapName; |
@synthesize specularMapName; |
@synthesize bumpMapName; |
|
- (id)init |
{ |
self = [super init]; |
if (self) |
{ |
|
} |
|
return self; |
} |
|
- (void)dealloc |
{ |
name= nil; |
|
ambientColor = nil; |
diffuseColor = nil; |
specularColor = nil; |
|
specularExponent = nil; |
|
indexOfRefraction = nil; |
|
illuminationModel = nil; |
|
dissolve = nil; |
|
transparency = nil; |
|
transmissionFilter = nil; |
|
ambientMapName = nil; |
diffuseMapName = nil; |
specularMapName = nil; |
bumpMapName = nil; |
} |
|
@end |
|
#pragma mark - |
#pragma mark AAPLObjMaterialUsage |
#pragma mark - |
|
@implementation AAPLObjMaterialUsage |
|
@synthesize name; |
@synthesize material; |
|
@synthesize indexRange; |
|
- (id)init |
{ |
self = [super init]; |
if (self) |
{ |
|
} |
|
return self; |
} |
|
- (void)dealloc |
{ |
name = nil; |
material = nil; |
} |
|
@end |