From c3c154e4ff3d052681abe28f27b16656ea4bb2e2 Mon Sep 17 00:00:00 2001 From: Austin Morlan Date: Fri, 14 Sep 2018 18:46:30 -0700 Subject: [PATCH] Add MTL file loading --- Makefile | 2 +- include/camera.h | 4 +- include/engine.h | 2 +- include/geometry.h | 78 ++++++++------ include/loader.h | 4 +- include/point.h | 38 ++++--- src/engine.cpp | 185 ++++++++++++++++++++------------ src/geometry.cpp | 143 +++++++++++++------------ src/loader.cpp | 257 ++++++++++++++++++++++++--------------------- src/main.cpp | 9 +- src/platform.cpp | 1 - 11 files changed, 415 insertions(+), 308 deletions(-) diff --git a/Makefile b/Makefile index 31e1d24..b37f383 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ WARNINGS_OFF=-Wno-missing-braces -Wno-gnu-anonymous-struct -Wno-old-style-cast\ CFLAGS=$(D) $(O) -std=c++11 $(WARNINGS_ON) $(WARNINGS_OFF) -I$(INCLUDE_DIR) LIBS=-lSDL2 -_HEADERS = color.h engine.h geometry.h light.h loader.h platform.h point.h\ +_HEADERS = camera.h color.h engine.h geometry.h light.h loader.h platform.h point.h\ transform.h util.h vec.h HEADERS = $(patsubst %,$(INCLUDE_DIR)/%,$(_HEADERS)) diff --git a/include/camera.h b/include/camera.h index 09ae634..e106509 100644 --- a/include/camera.h +++ b/include/camera.h @@ -18,8 +18,8 @@ struct Camera rotation[1] = 0.0f; rotation[2] = 0.0f; - nearClip = 1.0f; - farClip = 1000.0f; + nearClip = 5.0f; + farClip = 600.0f; } inline void SetFOV(float fov, int winWidth, int winHeight) diff --git a/include/engine.h b/include/engine.h index bb9d214..84370e6 100644 --- a/include/engine.h +++ b/include/engine.h @@ -34,7 +34,7 @@ struct Engine_Buffer // FUNCTIONS -int Engine_Init(Engine_Buffer &buffer, char *filename); +int Engine_Init(Engine_Buffer &buffer, char *objFilename, char *mtlFilename); void Engine_Render(Engine_Buffer &buffer, uint32_t input); void Engine_Shutdown(void); diff --git a/include/geometry.h b/include/geometry.h index 8a3e6d8..a751f6d 100644 --- a/include/geometry.h +++ b/include/geometry.h @@ -4,16 +4,12 @@ #include "engine.h" #include "point.h" #include -#include -// STRUCTURES -struct Material -{ - bool smooth; - ColorF32 kAmbient; - ColorF32 kDiffuse; -}; +#define VERTEX_LIMIT (20000) +#define FACE_LIMIT (30000) +#define MATERIAL_LIMIT (10) + struct Texture { @@ -22,18 +18,37 @@ struct Texture unsigned int height; }; -struct TextureCoord +struct TextureList +{ + Texture data[MATERIAL_LIMIT]; + size_t size; +}; + +struct Material +{ + ColorF32 ambient; + ColorF32 diffuse; + ColorF32 specular; + float specularExp; + float opacity; +}; + +struct MaterialList +{ + Material data[MATERIAL_LIMIT]; + size_t size; +}; + +struct UV { float u; float v; }; -struct Face +struct UVList { - unsigned int vertIndex[3]; - unsigned int textureIndex[3]; - Vector normal; - ColorF32 color; + UV data[VERTEX_LIMIT]; + size_t size; }; struct Vertex @@ -43,17 +58,23 @@ struct Vertex ColorF32 color; }; -struct Mesh_LocalData +struct VertexList { - std::vector verts; - std::vector faces; - std::vector uvs; + Vertex data[VERTEX_LIMIT]; + size_t size; }; -struct Mesh_TransformedData +struct Face { - std::vector verts; - std::vector faces; + int vertIndex[3]; + int uvIndex[3]; + int materialIndex; +}; + +struct FaceList +{ + Face data[FACE_LIMIT]; + size_t size; }; struct Mesh @@ -74,22 +95,17 @@ struct Mesh Point position; float rotation[3]; float scale; - - Material material; - - Mesh_LocalData local; - Mesh_TransformedData transformed; }; // PUBLIC FUNCTIONS -void CullBackfaces( - Mesh_LocalData &local, Mesh_TransformedData &transformed, - Point &camPosition); +void ClipAndCull( + VertexList &verts, FaceList &localFaces, + FaceList &transFaces, Point &camPosition); void RenderMesh( - Engine_Buffer &buffer, Mesh_TransformedData &mesh, - Texture &texture, std::vector &uvs); + Engine_Buffer &buffer, FaceList &faces, VertexList &verts, + UVList &uvs, TextureList &textures); #define GEOMETRY_H diff --git a/include/loader.h b/include/loader.h index 12bd10f..3ffa3c9 100644 --- a/include/loader.h +++ b/include/loader.h @@ -3,8 +3,8 @@ #include "geometry.h" -int LoadMesh(char *filename, Mesh &mesh); -int LoadTexture(char *filename, Texture &texture); +int ParseOBJ(char *filename, VertexList &verts, FaceList &faces, UVList &uvs); +int ParseMTL(char *filename, MaterialList &materials, TextureList &textures); #define LOADER_H diff --git a/include/point.h b/include/point.h index 0c51f86..2f4725e 100644 --- a/include/point.h +++ b/include/point.h @@ -22,36 +22,48 @@ struct Point // OPERATORS -// v / f -inline Point operator/(Point v, float f) +// p / f +inline Point operator/(Point p, float f) { Point result; float inverse = 1.0f / f; - result.x = v.x * inverse; - result.y = v.y * inverse; - result.z = v.z * inverse; + result.x = p.x * inverse; + result.y = p.y * inverse; + result.z = p.z * inverse; return result; } // v /= f -inline Point &operator/=(Point &v, float f) +inline Point &operator/=(Point &p, float f) { - v = v / f; + p = p / f; - return v; + return p; } -// v1 - v2 -inline Vector operator-(Point v1, Point v2) +// p1 - p2 +inline Vector operator-(Point p1, Point p2) { Vector result; - result.x = v1.x - v2.x; - result.y = v1.y - v2.y; - result.z = v1.z - v2.z; + result.x = p1.x - p2.x; + result.y = p1.y - p2.y; + result.z = p1.z - p2.z; + + return result; +} + +// -p +inline Point operator-(Point p) +{ + Point result; + + result.x = -p.x; + result.y = -p.y; + result.z = -p.z; return result; } diff --git a/src/engine.cpp b/src/engine.cpp index 04396c1..ff9cbc8 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -9,41 +9,61 @@ #include "util.h" #include "vec.h" #include +#include // GLOBALS static Mesh mesh; static Camera camera; static LightList lights; -static Texture texture; +static VertexList localVerts; +static VertexList transVerts; +static FaceList localFaces; +static FaceList transFaces; +static UVList uvs; +static MaterialList materials; +static TextureList textures; // PRIVATE PROTOTYPES static void CheckInputs(uint32_t input); +static void ComputeNormals(VertexList &verts, FaceList &faces); // PUBLIC FUNCTIONS -int Engine_Init(Engine_Buffer &buffer, char *filename) +int Engine_Init(Engine_Buffer &buffer, char *objFilename, char *mtlFilename) { - int result = LoadMesh(filename, mesh); + int result = ParseOBJ(objFilename, localVerts, localFaces, uvs); + if (result < 0) + { + return result; + } + + result = ParseMTL(mtlFilename, materials, textures); if (result < 0) { return -1; } - mesh.position.z = 250; - mesh.material.kDiffuse = {1.0,1.0,1.0,1.0}; - mesh.material.kAmbient = {1.0,1.0,1.0,1.0}; - mesh.material.smooth = true; + printf("Verts: %lu\n", localVerts.size); + printf("Faces: %lu\n", localFaces.size); + printf("Materials: %lu\n", materials.size); + + transVerts.size = localVerts.size; + + ComputeNormals(localVerts, localFaces); + + mesh.position.z = 50; + mesh.position.y = -50; camera.SetFOV(90.0f, buffer.width, buffer.height); lights.diffuse = (LightDiffuse*)malloc(sizeof(LightDiffuse)); lights.diffuseCount = 1; - lights.ambient.intensity = 1.0; - lights.diffuse[0].intensity = 0.5; - lights.diffuse[0].direction = Vector(1,1,1); + lights.ambient.intensity = 1.0f; + lights.diffuse[0].intensity = 1.0f; + lights.diffuse[0].direction = Vector(1.0f, 1.0f, 1.0f); return 0; } @@ -66,57 +86,6 @@ void Engine_Render(Engine_Buffer &buffer, uint32_t input) Matrix tScale = Transform_Scale(mesh.scale); - for (size_t v = 0; v < mesh.local.verts.size(); ++v) - { - mesh.transformed.verts[v].point = mesh.local.verts[v].point * tScale * tRotate * tTranslate; - mesh.transformed.verts[v].normal = mesh.local.verts[v].normal * tScale * tRotate * tTranslate; - } - - - // Cull backfaces before computing colors - CullBackfaces(mesh.local, mesh.transformed, camera.position); - - - // Color the vertices for Gouraud shading - if (mesh.material.smooth) - { - for (size_t f = 0; f < mesh.transformed.faces.size(); ++f) - { - for (int i = 0; i < 3; ++i) - { - unsigned int v = mesh.transformed.faces[f].vertIndex[i]; - - ColorF32 totalColor = lights.ambient.ComputeColor(mesh.material.kAmbient); - - for (int c = 0; c < lights.diffuseCount; ++c) - { - totalColor += lights.diffuse[c].ComputeColor( - mesh.material.kDiffuse, mesh.transformed.verts[v].normal); - } - - mesh.transformed.verts[v].color = totalColor; - } - } - } - - // Color the face for flat shading - else - { - for (size_t f = 0; f < mesh.transformed.faces.size(); ++f) - { - ColorF32 totalColor = lights.ambient.ComputeColor(mesh.material.kAmbient); - - for (int c = 0; c < lights.diffuseCount; ++c) - { - totalColor += lights.diffuse[c].ComputeColor( - mesh.material.kDiffuse, mesh.transformed.faces[f].normal); - } - - mesh.transformed.faces[f].color = totalColor; - } - } - - // World space to camera (view) space Matrix tView = Transform_View(camera.position, camera.rotation); @@ -124,18 +93,59 @@ void Engine_Render(Engine_Buffer &buffer, uint32_t input) Matrix tPersp = Transform_Perspective( camera.xZoom, camera.yZoom, camera.nearClip, camera.farClip); + for (size_t v = 0; v < localVerts.size; ++v) + { + transVerts.data[v].point = localVerts.data[v].point * tScale * tRotate * tTranslate * tView * tPersp; + transVerts.data[v].normal = localVerts.data[v].normal * tScale * tRotate * tTranslate; + } + + + // Clip near and far Z, and cull backfaces + ClipAndCull(transVerts, localFaces, transFaces, camera.position); + + + // Lighting + for (size_t f = 0; f < transFaces.size; ++f) + { + Face &face = transFaces.data[f]; + Material &material = materials.data[face.materialIndex]; + + // TODO: Fix weird lighting + material.ambient = {1.0,1.0,1.0,1.0}; + material.diffuse = {0.5,0.5,0.5,0.5}; + + // Gouraud shading + for (int i = 0; i < 3; ++i) + { + Vertex &vert = transVerts.data[face.vertIndex[i]]; + + ColorF32 totalColor = lights.ambient.ComputeColor(material.ambient); + + for (int c = 0; c < lights.diffuseCount; ++c) + { + totalColor += lights.diffuse[c].ComputeColor( + material.diffuse, vert.normal); + } + + vert.color = totalColor; + } + } + + // Perspective to screen Matrix tScreen = Transform_Screen(camera.xScale, camera.yScale); - for (size_t v = 0; v < mesh.transformed.verts.size(); ++v) + for (size_t v = 0; v < transVerts.size; ++v) { - mesh.transformed.verts[v].point *= tView * tPersp * tScreen; - mesh.transformed.verts[v].point.x /= mesh.transformed.verts[v].point.w; - mesh.transformed.verts[v].point.y /= mesh.transformed.verts[v].point.w; - mesh.transformed.verts[v].point.z /= mesh.transformed.verts[v].point.w; + transVerts.data[v].point *= tScreen; + transVerts.data[v].point.x /= transVerts.data[v].point.w; + transVerts.data[v].point.y /= transVerts.data[v].point.w; + transVerts.data[v].point.z /= transVerts.data[v].point.w; } - RenderMesh(buffer, mesh.transformed, texture, mesh.local.uvs); + + // Render + RenderMesh(buffer, transFaces, transVerts, uvs, textures); } void Engine_Shutdown(void) @@ -209,3 +219,44 @@ static void CheckInputs(uint32_t input) mesh.scale -= 0.1f; } } + + +static void ComputeNormals(VertexList &verts, FaceList &faces) +{ + int vertexNormalCount[VERTEX_LIMIT]; + + for (size_t f = 0; f < faces.size; ++f) + { + size_t v0 = (size_t)faces.data[f].vertIndex[0]; + size_t v1 = (size_t)faces.data[f].vertIndex[1]; + size_t v2 = (size_t)faces.data[f].vertIndex[2]; + + Point &p0 = verts.data[v0].point; + Point &p1 = verts.data[v1].point; + Point &p2 = verts.data[v2].point; + + Vector v01 = p1 - p0; + Vector v02 = p2 - p0; + + Vector normal = Vector::Cross(v01, v02); + + // Add each vertex's normal to the sum for future averaging + verts.data[v0].normal += normal; + verts.data[v1].normal += normal; + verts.data[v2].normal += normal; + + ++vertexNormalCount[v0]; + ++vertexNormalCount[v1]; + ++vertexNormalCount[v2]; + } + + for (size_t v = 0; v < localVerts.size; ++v) + { + if (vertexNormalCount[v] > 0) + { + // Compute the average normal for this vertex + verts.data[v].normal /= vertexNormalCount[v]; + verts.data[v].normal.Normalize(); + } + } +} diff --git a/src/geometry.cpp b/src/geometry.cpp index a598e71..ac24ad0 100644 --- a/src/geometry.cpp +++ b/src/geometry.cpp @@ -2,7 +2,6 @@ #include "engine.h" #include "geometry.h" #include "util.h" -#include // MACROS @@ -30,77 +29,77 @@ struct BoundingBox // PUBLIC FUNCTIONS -void CullBackfaces( - Mesh_LocalData &local, Mesh_TransformedData &transformed, - Point &camPosition) +void ClipAndCull( + VertexList &verts, FaceList &localFaces, + FaceList &transFaces, Point &camPosition) { - transformed.faces.clear(); + int faceIndex = 0; - for (size_t f = 0; f < local.faces.size(); ++f) + for (size_t f = 0; f < localFaces.size; ++f) { - unsigned int v0 = local.faces[f].vertIndex[0]; - unsigned int v1 = local.faces[f].vertIndex[1]; - unsigned int v2 = local.faces[f].vertIndex[2]; + Face &face = localFaces.data[f]; - Vector v01 = transformed.verts[v1].point - transformed.verts[v0].point; - Vector v02 = transformed.verts[v2].point - transformed.verts[v0].point; - Vector normal = Vector::Cross(v01, v02); + Point &p0 = verts.data[face.vertIndex[0]].point; + Point &p1 = verts.data[face.vertIndex[1]].point; + Point &p2 = verts.data[face.vertIndex[2]].point; - // Store normal for flat shading - local.faces[f].normal = normal; - local.faces[f].normal.Normalize(); + // Ignore this face if its Z is outside the Z clip planes + if ( (p0.z < -p0.w) + || (p0.z > p0.w) + || (p1.z < -p1.w) + || (p1.z > p1.w) + || (p2.z < -p2.w) + || (p2.z > p2.w)) + { + continue; + } - // Invert for Blender-compatibility - normal = -normal; + + // Calculate the face's normal (inverted for Blender-compatibility) + Vector v01 = p1 - p0; + Vector v02 = p2 - p0; + Vector normal = -Vector::Cross(v01, v02); // Eye vector to viewport - Vector view = camPosition - transformed.verts[v0].point; + Vector view = camPosition - p0; float dot = Vector::Dot(normal, view); + + // Not a backface; add it to the list if (dot < EPSILON_E3) { - transformed.faces.push_back(local.faces[f]); + transFaces.data[faceIndex] = face; + ++faceIndex; + transFaces.size = (size_t)faceIndex; } } - } void RenderMesh( - Engine_Buffer &buffer, Mesh_TransformedData &mesh, - Texture &texture, std::vector &uvs) + Engine_Buffer &buffer, FaceList &faces, VertexList &verts, + UVList &uvs, TextureList &textures) { - for(size_t f = 0; f < mesh.faces.size(); ++f) + for(size_t f = 0; f < faces.size; ++f) { - // The vertices of this face - unsigned int vIndex0 = mesh.faces[f].vertIndex[0]; - unsigned int vIndex1 = mesh.faces[f].vertIndex[1]; - unsigned int vIndex2 = mesh.faces[f].vertIndex[2]; - Vertex &v0 = mesh.verts[vIndex0]; - Vertex &v1 = mesh.verts[vIndex1]; - Vertex &v2 = mesh.verts[vIndex2]; + Face &face = faces.data[f]; - - // The UVs of this face's vertices - unsigned int tIndex0 = mesh.faces[f].textureIndex[0]; - unsigned int tIndex1 = mesh.faces[f].textureIndex[1]; - unsigned int tIndex2 = mesh.faces[f].textureIndex[2]; - TextureCoord &t0 = uvs[tIndex0]; - TextureCoord &t1 = uvs[tIndex1]; - TextureCoord &t2 = uvs[tIndex2]; + Point &p0 = verts.data[face.vertIndex[0]].point; + Point &p1 = verts.data[face.vertIndex[1]].point; + Point &p2 = verts.data[face.vertIndex[2]].point; // Bounding box for barycentric calculations (top-left fill convention) - BoundingBox box(v0.point, v1.point, v2.point); - int yMin = (int)ceilf(box.yMin); - int yMax = (int)ceilf(box.yMax) - 1; - int xMin = (int)ceilf(box.xMin); - int xMax = (int)ceilf(box.xMax) - 1; + BoundingBox box(p0, p1, p2); + int yMin = (int)MAX(ceilf(box.yMin), 0); + int yMax = (int)MIN(ceilf(box.yMax) - 1, buffer.height - 1); + int xMin = (int)MAX(ceilf(box.xMin), 0); + int xMax = (int)MIN(ceilf(box.xMax) - 1, buffer.width - 1); // Constants for this triangle used for barycentric calculations - Vector v01 = v1.point - v0.point; - Vector v02 = v2.point - v0.point; + Vector v01 = p1 - p0; + Vector v02 = p2 - p0; float dot0101 = Vector::Dot(v01, v01); float dot0102 = Vector::Dot(v01, v02); float dot0202 = Vector::Dot(v02, v02); @@ -113,7 +112,7 @@ void RenderMesh( { // Constant terms used for barycentric calculation Point p(x, y, 1.0f); - Vector v0P = p - v0.point; + Vector v0P = p - p0; float dot0P01 = Vector::Dot(v0P, v01); float dot0P02 = Vector::Dot(v0P, v02); float denomInv = 1.0f / ((dot0101 * dot0202) - (dot0102 * dot0102)); @@ -128,43 +127,51 @@ void RenderMesh( // Point is inside the triangle if ( (barycenter[0] >= 0.0f) - && (barycenter[1] >= 0.0f) - && (barycenter[2] >= 0.0f)) + && (barycenter[1] >= 0.0f) + && (barycenter[2] >= 0.0f)) { + ColorF32 &c0 = verts.data[face.vertIndex[0]].color; + ColorF32 &c1 = verts.data[face.vertIndex[1]].color; + ColorF32 &c2 = verts.data[face.vertIndex[2]].color; + + // Gouraud shading + ColorF32 shading = + (barycenter[0] * c0) + + (barycenter[1] * c1) + + (barycenter[2] * c2); + + // Constant terms used in the U and V interpolation - float a = barycenter[0] * v1.point.w * v2.point.w; - float b = barycenter[1] * v0.point.w * v2.point.w; - float c = barycenter[2] * v0.point.w * v1.point.w; + float a = barycenter[0] * p1.w * p2.w; + float b = barycenter[1] * p0.w * p2.w; + float c = barycenter[2] * p0.w * p1.w; float abc = 1.0f / (a + b + c); + UV &uv0 = uvs.data[face.uvIndex[0]]; + UV &uv1 = uvs.data[face.uvIndex[1]]; + UV &uv2 = uvs.data[face.uvIndex[2]]; // Interpolate U and V - float uInterp = ((a * t0.u) + (b * t1.u) + (c * t2.u)) * abc; - float vInterp = ((a * t0.v) + (b * t1.v) + (c * t2.v)) * abc; + float uInterp = ((a * uv0.u) + (b * uv1.u) + (c * uv2.u)) * abc; + float vInterp = ((a * uv0.v) + (b * uv1.v) + (c * uv2.v)) * abc; // Convert U and V to pixels in the texture image + Texture &texture = textures.data[faces.data[f].materialIndex]; uInterp *= texture.width; vInterp *= texture.height; unsigned int u = (unsigned int)uInterp; unsigned int v = (unsigned int)vInterp; - - // Gouraud shading - ColorF32 shading = - (barycenter[0] * v0.color) - + (barycenter[1] * v1.color) - + (barycenter[2] * v2.color); + // Constant terms for bilinear filtering + float du = uInterp - u; + float dv = vInterp - v; + float duDiff = 1 - du; + float dvDiff = 1 - dv; // Bilinear filtering - float du = uInterp - u; - float dv = vInterp - v; - float duDiff = 1-du; - float dvDiff = 1-dv; - ColorU32 color; - color.b = (uint8_t) (duDiff * dvDiff * texture.texels[v][u].b + du * dvDiff * texture.texels[v][u+1].b @@ -193,9 +200,9 @@ void RenderMesh( // Interpolate 1/z for the z-buffer float zInv = 1.0f / - ((barycenter[0] * v0.point.w) - + (barycenter[1] * v1.point.w) - + (barycenter[2] * v2.point.w)); + ((barycenter[0] * p0.w) + + (barycenter[1] * p1.w) + + (barycenter[2] * p2.w)); // Draw the pixel if it's closer than what's in the z-buffer diff --git a/src/loader.cpp b/src/loader.cpp index ca5b808..d6e43a2 100644 --- a/src/loader.cpp +++ b/src/loader.cpp @@ -7,8 +7,8 @@ // STATIC PROTOTYPES -static char *GetLine(char *buffer, int maxLength, FILE *fp); -static void ComputeNormals(Mesh &mesh); +static int LoadTexture(char *filename, Texture &texture); + #pragma pack(push, 1) struct BMP_Header @@ -34,15 +34,9 @@ struct BMP_Header // PUBLIC FUNCTIONS -int LoadMesh(char *filename, Mesh &mesh) +int ParseOBJ(char *filename, VertexList &verts, FaceList &faces, UVList &uvs) { - FILE *fp; - char buffer[256]; - char *token; - - char garbage[5]; - - fp = fopen(filename, "r"); + FILE *fp = fopen(filename, "r"); if (fp == NULL) { @@ -50,77 +44,158 @@ int LoadMesh(char *filename, Mesh &mesh) return -1; } - token = GetLine(buffer, sizeof(buffer), fp); - while (token != NULL) + char line[256]; + int vertIndex = 0; + int uvIndex = 0; + int faceIndex = 0; + int materialIndex = -1; + + + while (fgets(line, sizeof(line), fp)) { - if (token[0] == 'v') + char *separator = strchr(line, ' '); + + if (separator != NULL) { - if (token[1] == ' ') + *separator = '\0'; + char *type = line; + char *data = separator + 1; + + if (strcmp(type, "v") == 0) { - Vertex v; + sscanf( data, "%f %f %f", + &verts.data[vertIndex].point.x, + &verts.data[vertIndex].point.y, + &verts.data[vertIndex].point.z); - sscanf( - token, "%s %f %f %f", - garbage, - &v.point.x, - &v.point.y, - &v.point.z); - - mesh.local.verts.push_back(v); + ++vertIndex; } - else if (token[1] == 't') + + else if (strcmp(type, "vt") == 0) { - TextureCoord textureCoord; + sscanf( data, "%f %f", + &uvs.data[uvIndex].u, + &uvs.data[uvIndex].v); - sscanf( - token, "%s %f %f", - garbage, - &textureCoord.u, - &textureCoord.v); + ++uvIndex; + } + else if (strcmp(type, "usemtl") == 0) + { + ++materialIndex; + } - mesh.local.uvs.push_back(textureCoord); + else if (strcmp(type, "f") == 0) + { + sscanf( data, "%d/%d %d/%d %d/%d", + &faces.data[faceIndex].vertIndex[0], + &faces.data[faceIndex].uvIndex[0], + &faces.data[faceIndex].vertIndex[1], + &faces.data[faceIndex].uvIndex[1], + &faces.data[faceIndex].vertIndex[2], + &faces.data[faceIndex].uvIndex[2]); + + // Convert to 0-indexed + faces.data[faceIndex].vertIndex[0] -= 1; + faces.data[faceIndex].vertIndex[1] -= 1; + faces.data[faceIndex].vertIndex[2] -= 1; + faces.data[faceIndex].uvIndex[0] -= 1; + faces.data[faceIndex].uvIndex[1] -= 1; + faces.data[faceIndex].uvIndex[2] -= 1; + + faces.data[faceIndex].materialIndex = materialIndex; + + ++faceIndex; } } - else if (token[0] == 'f') - { - Face f; - - sscanf(token, "%s %d/%d %d/%d %d/%d", - garbage, - &f.vertIndex[0], - &f.textureIndex[0], - &f.vertIndex[1], - &f.textureIndex[1], - &f.vertIndex[2], - &f.textureIndex[2]); - - // Convert to 0-indexed - f.vertIndex[0] -= 1; - f.vertIndex[1] -= 1; - f.vertIndex[2] -= 1; - f.textureIndex[0] -= 1; - f.textureIndex[1] -= 1; - f.textureIndex[2] -= 1; - - mesh.local.faces.push_back(f); - } - - token = GetLine(buffer, sizeof(buffer), fp); } - ComputeNormals(mesh); - - printf("Mesh: %s [v: %lu f: %lu]\n", filename, mesh.local.verts.size(), mesh.local.faces.size()); - - mesh.transformed.verts.resize(mesh.local.verts.size()); - - fclose(fp); + verts.size = (size_t)vertIndex; + uvs.size = (size_t)uvIndex; + faces.size = (size_t)faceIndex; return 0; } -int LoadTexture(char *filename, Texture &texture) +int ParseMTL(char *filename, MaterialList &materials, TextureList &textures) +{ + FILE *fp = fopen(filename, "r"); + + if (fp == NULL) + { + fprintf(stderr, "Error loading file: %s\n", filename); + return -1; + } + + char line[256]; + int materialIndex = -1; + + while (fgets(line, sizeof(line), fp)) + { + char *separator = strchr(line, ' '); + + if (separator != NULL) + { + *separator = '\0'; + char *type = line; + char *data = separator + 1; + + if (strcmp(type, "newmtl") == 0) + { + ++materialIndex; + } + + else if (strcmp(type, "Ns") == 0) + { + sscanf( data, "%f", + &materials.data[materialIndex].specularExp); + } + + else if (strcmp(type, "Ka") == 0) + { + sscanf( data, "%f %f %f", + &materials.data[materialIndex].ambient.r, + &materials.data[materialIndex].ambient.g, + &materials.data[materialIndex].ambient.b); + } + + else if (strcmp(type, "Kd") == 0) + { + sscanf( data, "%f %f %f", + &materials.data[materialIndex].diffuse.r, + &materials.data[materialIndex].diffuse.g, + &materials.data[materialIndex].diffuse.b); + } + + else if (strcmp(type, "Ks") == 0) + { + sscanf( data, "%f %f %f", + &materials.data[materialIndex].specular.r, + &materials.data[materialIndex].specular.g, + &materials.data[materialIndex].specular.b); + } + + else if (strcmp(type, "d") == 0) + { + sscanf( data, "%f", + &materials.data[materialIndex].opacity); + } + + else if (strcmp(type, "map_Kd") == 0) + { + char *textureFilename = data; + textureFilename[strcspn(textureFilename, "\r\n")] = 0; + LoadTexture(textureFilename, textures.data[materialIndex]); + } + } + } + + materials.size = (size_t)(materialIndex + 1); + + return 0; +} + +static int LoadTexture(char *filename, Texture &texture) { FILE *fp = fopen(filename, "r"); if (fp == NULL) @@ -167,57 +242,3 @@ int LoadTexture(char *filename, Texture &texture) return 0; } -static char *GetLine(char *buffer, int maxLength, FILE *fp) -{ - while(true) - { - if (!fgets(buffer, maxLength, fp)) - { - return NULL; - } - - if (buffer[0] != 'v' && buffer[0] != 'f') - { - continue; - } - - return buffer; - } -} - - -static void ComputeNormals(Mesh &mesh) -{ - int *vertexNormalCount = (int*)calloc((size_t)(mesh.local.verts.size()), sizeof(int)); - - for (size_t f = 0; f < mesh.local.faces.size(); ++f) - { - unsigned int v0 = mesh.local.faces[f].vertIndex[0]; - unsigned int v1 = mesh.local.faces[f].vertIndex[1]; - unsigned int v2 = mesh.local.faces[f].vertIndex[2]; - - Vector v01 = mesh.local.verts[v1].point - mesh.local.verts[v0].point; - Vector v02 = mesh.local.verts[v2].point - mesh.local.verts[v0].point; - - Vector normal = Vector::Cross(v01, v02); - - // Add each vertex's normal to the sum for future averaging - mesh.local.verts[v0].normal += normal; - mesh.local.verts[v1].normal += normal; - mesh.local.verts[v2].normal += normal; - - ++vertexNormalCount[v0]; - ++vertexNormalCount[v1]; - ++vertexNormalCount[v2]; - } - - for (size_t v = 0; v < mesh.local.verts.size(); ++v) - { - if (vertexNormalCount[v] > 0) - { - // Compute the average normal for this vertex - mesh.local.verts[v].normal /= vertexNormalCount[v]; - mesh.local.verts[v].normal.Normalize(); - } - } -} diff --git a/src/main.cpp b/src/main.cpp index 3a465d9..f9f5494 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -14,13 +14,14 @@ const unsigned int WINDOW_FPS = 30; // MAIN int main(int argc, char *argv[]) { - if (argc != 2) + if (argc != 3) { - fprintf(stderr, "Usage: %s \n", argv[0]); + fprintf(stderr, "Usage: %s \n", argv[0]); return EXIT_FAILURE; } - char *filename = argv[1]; + char *objFilename = argv[1]; + char *mtlFilename = argv[2]; Platform platform = {}; int result = Platform_Init(platform, WINDOW_WIDTH, WINDOW_HEIGHT); @@ -34,7 +35,7 @@ int main(int argc, char *argv[]) buffer.width = platform.surface->w; buffer.height = platform.surface->h; - result = Engine_Init(buffer, filename); + result = Engine_Init(buffer, objFilename, mtlFilename); if (result < 0) { diff --git a/src/platform.cpp b/src/platform.cpp index bd1bf8f..38cbb31 100644 --- a/src/platform.cpp +++ b/src/platform.cpp @@ -2,7 +2,6 @@ #include "platform.h" #include "util.h" #include -#include #include