From bb8c66b3a0a0d68a667e2779b6307fe791a80dfd Mon Sep 17 00:00:00 2001 From: Kevin Caccamo Date: Sun, 3 Jun 2018 04:11:38 -0400 Subject: [PATCH] Fix rendering and parsing of OBJ models Create a new surface for each 'usemtl' statement in the OBJ file, and fix memory errors caused by TriangulateQuad. Calculate missing normals, and fix incorrect UV coordinates Fix construction of vertex buffer for objects with multiple surfaces Localize curMtl, curSurface, aggSurfFaceCount, and curSurfFaceCount to FOBJModel::Load(), since they are not used anywhere else. Fix parsing of OBJs without UV references Internally, I replaced hashtag line comments with C-style line comments, and I replaced each forward slash with newSideSep. If no UV coordinates are available, add a default vector of (0,0). Also, remove "this->" from ResolveIndex to make the code a bit cleaner, and fix a minor garbage issue I failed to notice earlier (normref would pick up garbage if there was no normal reference). Ensure usemtl statements remain intact It may be a bit inefficient, but I tried modifying the buffer directly, and I got memory corruption errors. In this case, it's a lot better to be safe than sorry. --- src/r_data/models/models_obj.cpp | 721 +++++++++++++++++++------------ src/r_data/models/models_obj.h | 103 ++--- 2 files changed, 490 insertions(+), 334 deletions(-) diff --git a/src/r_data/models/models_obj.cpp b/src/r_data/models/models_obj.cpp index e754d2daa..65abfd7c2 100644 --- a/src/r_data/models/models_obj.cpp +++ b/src/r_data/models/models_obj.cpp @@ -23,322 +23,485 @@ #include "cmdlib.h" #include "r_data/models/models_obj.h" +/** + * Load an OBJ model + * + * @param fn The path to the model file + * @param lumpnum The lump index in the wad collection + * @param buffer The contents of the model file + * @param length The size of the model file + */ bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length) { - FString objName = Wads.GetLumpFullPath(lumpnum); - sc.OpenMem(objName, buffer, length); - Printf("Parsing %s\n", objName.GetChars()); - while(sc.GetString()) - { - if (sc.Compare("#")) // Line comment - { - sc.Line += 1; // I don't think this does anything, though... - } - else if (sc.Compare("v")) // Vertex - { - ParseVector3(this->verts); - } - else if (sc.Compare("vn")) // Vertex normal - { - ParseVector3(this->norms); - } - else if (sc.Compare("vt")) // UV Coordinates - { - ParseVector2(this->uvs); - } - else if (sc.Compare("usemtl")) - { - // Get material name and try to load it - sc.MustGetString(); + FString objName = Wads.GetLumpFullPath(lumpnum); + FString objBuf(buffer, length); - curMtl = LoadSkin("", sc.String); - if (!curMtl.isValid()) - { - // Relative to model file path? - curMtl = LoadSkin(fn, sc.String); - } + // Do some replacements before we parse the OBJ string + { + // Ensure usemtl statements remain intact + TArray mtlUsages; + TArray mtlUsageIdxs; + long bpos = 0, nlpos = 0, slashpos = 0; + while (1) + { + bpos = objBuf.IndexOf("\nusemtl", bpos); + if (bpos == -1) break; + slashpos = objBuf.IndexOf('/', bpos); + nlpos = objBuf.IndexOf('\n', ++bpos); + if (slashpos > nlpos || slashpos == -1) + { + continue; + } + if (nlpos == -1) + { + nlpos = objBuf.Len(); + } + FString lineStr(objBuf.GetChars() + bpos, nlpos - bpos); + mtlUsages.Push(lineStr); + mtlUsageIdxs.Push(bpos); + } - if (!curMtl.isValid()) - { - // Don't use materials that don't exist - continue; - } - // Most OBJs are sorted by material, but just in case the one we're loading isn't... - // Build surface... - if (curSurface == nullptr) - { - // First surface - curSurface = new OBJSurface(curMtl); - } - else - { - if (curSurfFaceCount > 0) - { - // Search for existing surface with current material - /* - for(size_t i = 0; i < surfaces.Size(); i++) - { - if (surfaces[i].skin == curMtl) - { - curSurface = &surfaces[i]; - surfExists = true; - } - } - */ - // Add previous surface - curSurface->numFaces = curSurfFaceCount; - curSurface->faceStart = aggSurfFaceCount; - surfaces.Push(*curSurface); - delete curSurface; - // Go to next surface - curSurface = new OBJSurface(curMtl); - aggSurfFaceCount += curSurfFaceCount; - } - else - { - curSurface->skin = curMtl; - } - } - curSurfFaceCount = 0; - } - else if (sc.Compare("f")) - { - FString sides[4]; - OBJFace face; - for (int i = 0; i < 3; i++) - { - // A face must have at least 3 sides - sc.MustGetString(); - sides[i] = sc.String; - ParseFaceSide(sides[i], face, i); - } - face.sideCount = 3; - if (sc.GetString()) - { - if (!sc.Compare("f")) - { - sides[3] = sc.String; - face.sideCount += 1; - ParseFaceSide(sides[3], face, 3); - } - else - { - sc.UnGet(); // No 4th side, move back - } - } - faces.Push(face); - curSurfFaceCount += 1; - } - } - sc.Close(); + // Replace forward slashes with percent signs so they aren't parsed as line comments + objBuf.ReplaceChars('/', *newSideSep); - // No valid materials detected - if (curSurface == nullptr) - { - FTextureID dummyMtl = LoadSkin("", "-NOFLAT-"); // Built-in to GZDoom - curSurface = new OBJSurface(dummyMtl); - } - curSurface->numFaces = curSurfFaceCount; - curSurface->faceStart = aggSurfFaceCount; - surfaces.Push(*curSurface); - delete curSurface; + // Substitute broken usemtl statements with old ones + bpos = 0, nlpos = 0; + for (size_t i = 0; i < mtlUsages.Size(); i++) + { + bpos = mtlUsageIdxs[i]; + nlpos = objBuf.IndexOf('\n', bpos); + if (nlpos == -1) + { + nlpos = objBuf.Len(); + } + FString lineStr(objBuf.GetChars() + bpos, nlpos - bpos); + objBuf.Substitute(lineStr, mtlUsages[i]); + } - /* - Printf("%d vertices\n", verts.Size()); - Printf("%d normals\n", norms.Size()); - Printf("%d UVs\n", uvs.Size()); - Printf("%d faces\n", faces.Size()); - Printf("%d surfaces\n", surfaces.Size()); - */ + // Find each OBJ line comment, and convert each to a C-style line comment + while (1) + { + bpos = objBuf.IndexOf('#'); + if (bpos == -1) break; + objBuf.Remove(bpos, 1); + objBuf.Insert(bpos, "//", 2); + } + } + sc.OpenString(objName, objBuf); + //Printf("Parsing %s\n", objName.GetChars()); - mLumpNum = lumpnum; - for (size_t i = 0; i < surfaces.Size(); i++) - { - ConstructSurfaceTris(&surfaces[i]); - } - return true; + FTextureID curMtl = FNullTextureID(); + OBJSurface *curSurface = nullptr; + int aggSurfFaceCount = 0; + int curSurfFaceCount = 0; + + while(sc.GetString()) + { + if /*(sc.Compare("#")) // Line comment + { + sc.Line += 1; // I don't think this does anything, though... + } + else if*/ (sc.Compare("v")) // Vertex + { + ParseVector3(this->verts); + } + else if (sc.Compare("vn")) // Vertex normal + { + ParseVector3(this->norms); + } + else if (sc.Compare("vt")) // UV Coordinates + { + ParseVector2(this->uvs); + } + else if (sc.Compare("usemtl")) + { + // Get material name and try to load it + sc.MustGetString(); + + curMtl = LoadSkin("", sc.String); + if (!curMtl.isValid()) + { + // Relative to model file path? + curMtl = LoadSkin(fn, sc.String); + } + + // Build surface... + if (curSurface == nullptr) + { + // First surface + curSurface = new OBJSurface(curMtl); + } + else + { + if (curSurfFaceCount > 0) + { + // Add previous surface + curSurface->numFaces = curSurfFaceCount; + curSurface->faceStart = aggSurfFaceCount; + surfaces.Push(*curSurface); + delete curSurface; + // Go to next surface + curSurface = new OBJSurface(curMtl); + aggSurfFaceCount += curSurfFaceCount; + } + else + { + curSurface->skin = curMtl; + } + } + curSurfFaceCount = 0; + } + else if (sc.Compare("f")) + { + FString sides[4]; + OBJFace face; + for (int i = 0; i < 3; i++) + { + // A face must have at least 3 sides + sc.MustGetString(); + sides[i] = sc.String; + ParseFaceSide(sides[i], face, i); + } + face.sideCount = 3; + if (sc.GetString()) + { + if (!sc.Compare("f") && FString(sc.String).IndexOfAny("-0123456789") == 0) + { + sides[3] = sc.String; + face.sideCount += 1; + ParseFaceSide(sides[3], face, 3); + } + else + { + sc.UnGet(); // No 4th side, move back + } + } + faces.Push(face); + curSurfFaceCount += 1; + } + } + sc.Close(); + + if (curSurface == nullptr) + { // No valid materials detected + FTextureID dummyMtl = LoadSkin("", "-NOFLAT-"); // Built-in to GZDoom + curSurface = new OBJSurface(dummyMtl); + } + curSurface->numFaces = curSurfFaceCount; + curSurface->faceStart = aggSurfFaceCount; + surfaces.Push(*curSurface); + delete curSurface; + + if (uvs.Size() == 0) + { // Needed so that OBJs without UVs can work + uvs.Push(FVector2(0.0, 0.0)); + } + + /* + Printf("%d vertices\n", verts.Size()); + Printf("%d normals\n", norms.Size()); + Printf("%d UVs\n", uvs.Size()); + Printf("%d faces\n", faces.Size()); + Printf("%d surfaces\n", surfaces.Size()); + */ + + mLumpNum = lumpnum; + return true; } /** - * Parse a 2D vector - * - * @param start The buffer to parse from - * @param array The array to append the parsed vector to - */ + * Parse a 2D vector + * + * @param start The buffer to parse from + * @param array The array to append the parsed vector to + */ void FOBJModel::ParseVector2(TArray &array) { - float coord[2]; - for (int axis = 0; axis < 2; axis++) - { - sc.MustGetFloat(); - coord[axis] = (float)sc.Float; - } - FVector2 vec(coord); - array.Push(vec); + float coord[2]; + for (int axis = 0; axis < 2; axis++) + { + sc.MustGetFloat(); + coord[axis] = (float)sc.Float; + } + FVector2 vec(coord); + array.Push(vec); } /** - * Parse a 3D vector - * - * @param start The buffer to parse from - * @param array The array to append the parsed vector to - */ + * Parse a 3D vector + * + * @param start The buffer to parse from + * @param array The array to append the parsed vector to + */ void FOBJModel::ParseVector3(TArray &array) { - float coord[3]; - for (int axis = 0; axis < 3; axis++) - { - sc.MustGetFloat(); - coord[axis] = (float)sc.Float; - } - FVector3 vec(coord); - array.Push(vec); + float coord[3]; + for (int axis = 0; axis < 3; axis++) + { + sc.MustGetFloat(); + coord[axis] = (float)sc.Float; + } + FVector3 vec(coord); + array.Push(vec); } void FOBJModel::ParseFaceSide(const FString &sideStr, OBJFace &face, int sidx) { - OBJFaceSide side; - int origIdx; - if (sideStr.IndexOf("/") >= 0) - { - TArray sides = sideStr.Split("/"); - if (sides[0].Len() > 0) - { - origIdx = atoi(sides[0].GetChars()); - side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex); - } - if (sides[1].Len() > 0) - { - origIdx = atoi(sides[1].GetChars()); - side.uvref = ResolveIndex(origIdx, FaceElement::UVIndex); - } - if (sides.Size() > 2) - { - if (sides[2].Len() > 0) - { - origIdx = atoi(sides[2].GetChars()); - side.normref = ResolveIndex(origIdx, FaceElement::VNormalIndex); - } - } - } - else - { - origIdx = atoi(sideStr.GetChars()); - side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex); - side.normref = 0; - side.uvref = 0; - } - face.sides[sidx] = side; + OBJFaceSide side; + int origIdx; + if (sideStr.IndexOf(newSideSep) >= 0) + { + TArray sides = sideStr.Split(newSideSep, FString::TOK_KEEPEMPTY); + + if (sides[0].Len() > 0) + { + origIdx = atoi(sides[0].GetChars()); + side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex); + } + else + { + sc.ScriptError("Vertex reference is not optional!"); + } + + if (sides[1].Len() > 0) + { + origIdx = atoi(sides[1].GetChars()); + side.uvref = ResolveIndex(origIdx, FaceElement::UVIndex); + } + else + { + side.uvref = -1; + } + + if (sides.Size() > 2) + { + if (sides[2].Len() > 0) + { + origIdx = atoi(sides[2].GetChars()); + side.normref = ResolveIndex(origIdx, FaceElement::VNormalIndex); + } + else + { + side.normref = -1; + } + } + else + { + side.normref = -1; + } + } + else + { + origIdx = atoi(sideStr.GetChars()); + side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex); + side.normref = -1; + side.uvref = -1; + } + face.sides[sidx] = side; } int FOBJModel::ResolveIndex(int origIndex, FaceElement el) { - if (origIndex > 0) - { - return origIndex - 1; // OBJ indices start at 1 - } - else if (origIndex < 0) - { - if (el == FaceElement::VertexIndex) - { - return this->verts.Size() + origIndex; // origIndex is negative - } - else if (el == FaceElement::UVIndex) - { - return this->uvs.Size() + origIndex; - } - else if (el == FaceElement::VNormalIndex) - { - return this->norms.Size() + origIndex; - } - } - return 0; -} - -void FOBJModel::TriangulateQuad(const OBJFace &quad, OBJFace *tris) -{ - // if (quad.sides < 3 || quad.sides > 4) exception - // if (quad.sides == 3) return &quad; - tris[0].sideCount = 3; - tris[1].sideCount = 3; - - for (int i = 0; i < 3; i++) - { - tris[0].sides[i].vertref = quad.sides[i].vertref; - tris[1].sides[i].vertref = quad.sides[i+1].vertref; - tris[0].sides[i].uvref = quad.sides[i].uvref; - tris[1].sides[i].uvref = quad.sides[i+1].uvref; - tris[0].sides[i].normref = quad.sides[i].normref; - tris[1].sides[i].normref = quad.sides[i+1].normref; - } -} - -void FOBJModel::ConstructSurfaceTris(OBJSurface *surf) -{ - int triCount = 0; - - size_t start = surf->faceStart; - size_t end = start + surf->numFaces; - for (size_t i = start; i < end; i++) - { - triCount += (faces[i].sideCount > 3) ? 2 : 1; - } - - surf->numTris = triCount; - surf->tris = new OBJFace[triCount]; - - int quadCount = 0; - for (size_t i = start; i < end; i++) - { - surf->tris[i+quadCount].sideCount = 3; - if (faces[i].sideCount == 3) - { - memcpy(surf->tris[i+quadCount].sides, faces[i].sides, sizeof(OBJFaceSide) * 3); - } - else if (faces[i].sideCount == 4) - { - OBJFace *triangulated = new OBJFace[2]; - TriangulateQuad(faces[i], triangulated); - memcpy(surf->tris[i+quadCount].sides, triangulated->sides, sizeof(OBJFaceSide) * 3); - memcpy(surf->tris[i+quadCount+1].sides, (triangulated+1)->sides, sizeof(OBJFaceSide) * 3); - delete[] triangulated; - quadCount += 1; - } - } -} - -int FOBJModel::FindFrame(const char* name) -{ - return 0; // OBJs are not animated. -} - -// Stubs - I don't know what these do exactly -void FOBJModel::RenderFrame(FModelRenderer *renderer, FTexture * skin, int frameno, int frameno2, double inter, int translation) -{ - /* - for (int i = 0; i < numMtls; i++) - { - renderer->SetMaterial(skin, false, translation); - GetVertexBuffer(renderer)->SetupFrame(renderer, surf->vindex + frameno * surf->numVertices, surf->vindex + frameno2 * surf->numVertices, surf->numVertices); - renderer->DrawElements(surf->numTriangles * 3, surf->iindex * sizeof(unsigned int)); - } - */ + if (origIndex > 0) + { + return origIndex - 1; // OBJ indices start at 1 + } + else if (origIndex < 0) + { + if (el == FaceElement::VertexIndex) + { + return verts.Size() + origIndex; // origIndex is negative + } + else if (el == FaceElement::UVIndex) + { + return uvs.Size() + origIndex; + } + else if (el == FaceElement::VNormalIndex) + { + return norms.Size() + origIndex; + } + } + return -1; } void FOBJModel::BuildVertexBuffer(FModelRenderer *renderer) { + if (GetVertexBuffer(renderer)) + { + return; + } + unsigned int vbufsize = 0; + + for (size_t i = 0; i < surfaces.Size(); i++) + { + ConstructSurfaceTris(surfaces[i]); + surfaces[i].vbStart = vbufsize; + vbufsize += surfaces[i].numTris * 3; + } + + auto vbuf = renderer->CreateVertexBuffer(false,true); + SetVertexBuffer(renderer, vbuf); + + FModelVertex *vertptr = vbuf->LockVertexBuffer(vbufsize); + + for (size_t i = 0; i < surfaces.Size(); i++) + { + for (size_t j = 0; j < surfaces[i].numTris; j++) + { + for (size_t side = 0; side < 3; side++) + { + FModelVertex *mdv = vertptr + + side + j * 3 + // Current surface and previous triangles + surfaces[i].vbStart; // Previous surfaces + + OBJFaceSide &curSide = surfaces[i].tris[j].sides[side]; + + int vidx = curSide.vertref; + int uvidx = (curSide.uvref >= 0 && curSide.uvref < uvs.Size()) ? curSide.uvref : 0; + int nidx = curSide.normref; + + mdv->Set(verts[vidx].X, verts[vidx].Y, verts[vidx].Z, uvs[uvidx].X, uvs[uvidx].Y * -1); + + if (nidx >= 0 && nidx < norms.Size()) + { + mdv->SetNormal(norms[nidx].X, norms[nidx].Y, norms[nidx].Z); + } + else + { + // https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal + // Find other sides of triangle + int nextSidx = side + 1; + if (nextSidx >= 3) nextSidx -= 3; + + int lastSidx = side + 2; + if (lastSidx >= 3) lastSidx -= 3; + + OBJFaceSide &nextSide = surfaces[i].tris[j].sides[nextSidx]; + OBJFaceSide &lastSide = surfaces[i].tris[j].sides[lastSidx]; + + // Cross-multiply the U-vector and V-vector + FVector3 uvec = verts[nextSide.vertref] - verts[curSide.vertref]; + FVector3 vvec = verts[lastSide.vertref] - verts[curSide.vertref]; + + FVector3 nvec = uvec ^ vvec; + mdv->SetNormal(nvec.X, nvec.Y, nvec.Z); + } + } + } + } + + vbuf->UnlockVertexBuffer(); +} + +void FOBJModel::ConstructSurfaceTris(OBJSurface &surf) +{ + int triCount = 0; + + size_t start = surf.faceStart; + size_t end = start + surf.numFaces; + for (size_t i = start; i < end; i++) + { + triCount += faces[i].sideCount - 2; + } + + surf.numTris = triCount; + surf.tris = new OBJFace[triCount]; + + for (size_t i = start, triIdx = 0; i < end; i++, triIdx++) + { + surf.tris[triIdx].sideCount = 3; + if (faces[i].sideCount == 3) + { + memcpy(surf.tris[triIdx].sides, faces[i].sides, sizeof(OBJFaceSide) * 3); + } + else if (faces[i].sideCount == 4) // Triangulate face + { + OBJFace *triangulated = new OBJFace[2]; + TriangulateQuad(faces[i], triangulated); + memcpy(surf.tris[triIdx].sides, triangulated[0].sides, sizeof(OBJFaceSide) * 3); + memcpy(surf.tris[triIdx+1].sides, triangulated[1].sides, sizeof(OBJFaceSide) * 3); + delete[] triangulated; + triIdx += 1; // Filling out two faces + } + } +} + +void FOBJModel::TriangulateQuad(const OBJFace &quad, OBJFace *tris) +{ + tris[0].sideCount = 3; + tris[1].sideCount = 3; + + int tsidx[2][3] = {{0, 1, 3}, {1, 2, 3}}; + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 2; j++) + { + tris[j].sides[i].vertref = quad.sides[tsidx[j][i]].vertref; + tris[j].sides[i].uvref = quad.sides[tsidx[j][i]].uvref; + tris[j].sides[i].normref = quad.sides[tsidx[j][i]].normref; + } + } +} + +int FOBJModel::FindFrame(const char* name) +{ + return 0; // OBJs are not animated. +} + +void FOBJModel::RenderFrame(FModelRenderer *renderer, FTexture * skin, int frameno, int frameno2, double inter, int translation) +{ + for (unsigned int i = 0; i < surfaces.Size(); i++) + { + OBJSurface *surf = &surfaces[i]; + + FTexture *userSkin = skin; + if (!userSkin) + { + if (curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i].isValid()) + { + userSkin = TexMan(curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i]); + } + else if (surf->skin.isValid()) + { + userSkin = TexMan(surf->skin); + } + } + if (!userSkin) return; + + renderer->SetMaterial(userSkin, false, translation); + GetVertexBuffer(renderer)->SetupFrame(renderer, 0, 0, surf->numTris * 3); + renderer->DrawArrays(surf->vbStart, surf->numTris * 3); + } } void FOBJModel::AddSkins(uint8_t* hitlist) { + for (size_t i = 0; i < surfaces.Size(); i++) + { + if (curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i].isValid()) + { + hitlist[curSpriteMDLFrame->surfaceskinIDs[curMDLIndex][i].GetIndex()] |= FTextureManager::HIT_Flat; + } + OBJSurface * surf = &surfaces[i]; + if (surf->skin.isValid()) + { + hitlist[surf->skin.GetIndex()] |= FTextureManager::HIT_Flat; + } + } } FOBJModel::~FOBJModel() { - for (size_t i = 0; i < surfaces.Size(); i++) - { - delete[] surfaces[i].tris; - } + verts.Clear(); + norms.Clear(); + uvs.Clear(); + faces.Clear(); + for (size_t i = 0; i < surfaces.Size(); i++) + { + delete[] surfaces[i].tris; + } + surfaces.Clear(); } diff --git a/src/r_data/models/models_obj.h b/src/r_data/models/models_obj.h index 6fa2034c2..76af2e649 100644 --- a/src/r_data/models/models_obj.h +++ b/src/r_data/models/models_obj.h @@ -29,66 +29,59 @@ class FOBJModel : public FModel { private: - int mLumpNum; - int numMtls; + int mLumpNum; + const char *newSideSep = "$"; // OBJ side separator is /, which is parsed as a line comment by FScanner if two of them are next to each other. - enum class FaceElement - { - VertexIndex, - UVIndex, - VNormalIndex - }; + enum class FaceElement + { + VertexIndex, + UVIndex, + VNormalIndex + }; - struct OBJFaceSide - { - int vertref; - int normref; - int uvref; - }; - struct OBJFace - { - int sideCount; - OBJFaceSide sides[4]; - }; - struct OBJTriangle - { - int vertref[3]; - }; - struct OBJSurface // 1 surface per 'usemtl' - { - int numTris; // Number of triangulated faces - int numFaces; // Number of faces - int faceStart; // Index of first face in faces array - OBJFace* tris; // Triangles - FTextureID skin; - OBJSurface(FTextureID skin): numTris(0), numFaces(0), faceStart(0), tris(nullptr), skin(skin) {} - }; + struct OBJFaceSide + { + int vertref; + int normref; + int uvref; + }; + struct OBJFace + { + int sideCount; + OBJFaceSide sides[4]; + }; + struct OBJSurface // 1 surface per 'usemtl' + { + unsigned int numTris; // Number of triangulated faces + unsigned int numFaces; // Number of faces + unsigned int vbStart; // First index in vertex buffer + unsigned int faceStart; // Index of first face in faces array + OBJFace* tris; // Triangles + FTextureID skin; + OBJSurface(FTextureID skin): numTris(0), numFaces(0), vbStart(0), faceStart(0), tris(nullptr), skin(skin) {} + }; - TArray verts; - TArray norms; - TArray uvs; - TArray faces; - TArray surfaces; - FTextureID curMtl; - OBJSurface* curSurface; - int aggSurfFaceCount; - int curSurfFaceCount; - FScanner sc; + TArray verts; + TArray norms; + TArray uvs; + TArray faces; + TArray surfaces; + FScanner sc; - void ParseVector2(TArray &array); - void ParseVector3(TArray &array); - void ParseFaceSide(const FString &side, OBJFace &face, int sidx); - void ConstructSurfaceTris(OBJSurface *surf); - int ResolveIndex(int origIndex, FaceElement el); - void TriangulateQuad(const OBJFace &quad, OBJFace *tris); + void ParseVector2(TArray &array); + void ParseVector3(TArray &array); + void ParseFaceSide(const FString &side, OBJFace &face, int sidx); + void ConstructSurfaceTris(OBJSurface &surf); + int ResolveIndex(int origIndex, FaceElement el); + void TriangulateQuad(const OBJFace &quad, OBJFace *tris); public: - FOBJModel(): curSurface(nullptr), aggSurfFaceCount(0), curSurfFaceCount(0) {} - ~FOBJModel(); - bool Load(const char* fn, int lumpnum, const char* buffer, int length) override; - int FindFrame(const char* name) override; - void RenderFrame(FModelRenderer* renderer, FTexture* skin, int frame, int frame2, double inter, int translation=0) override; - void BuildVertexBuffer(FModelRenderer* renderer) override; - void AddSkins(uint8_t* hitlist) override; + FOBJModel() {} + ~FOBJModel(); + bool Load(const char* fn, int lumpnum, const char* buffer, int length) override; + int FindFrame(const char* name) override; + void RenderFrame(FModelRenderer* renderer, FTexture* skin, int frame, int frame2, double inter, int translation=0) override; + void BuildVertexBuffer(FModelRenderer* renderer) override; + void AddSkins(uint8_t* hitlist) override; }; #endif