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.
This commit is contained in:
Kevin Caccamo 2018-06-03 04:11:38 -04:00 committed by Christoph Oelckers
parent 1c15fb2408
commit bb8c66b3a0
2 changed files with 490 additions and 334 deletions

View file

@ -23,322 +23,485 @@
#include "cmdlib.h" #include "cmdlib.h"
#include "r_data/models/models_obj.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) bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length)
{ {
FString objName = Wads.GetLumpFullPath(lumpnum); FString objName = Wads.GetLumpFullPath(lumpnum);
sc.OpenMem(objName, buffer, length); FString objBuf(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();
curMtl = LoadSkin("", sc.String); // Do some replacements before we parse the OBJ string
if (!curMtl.isValid()) {
{ // Ensure usemtl statements remain intact
// Relative to model file path? TArray<FString> mtlUsages;
curMtl = LoadSkin(fn, sc.String); TArray<long> 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()) // Replace forward slashes with percent signs so they aren't parsed as line comments
{ objBuf.ReplaceChars('/', *newSideSep);
// 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();
// No valid materials detected // Substitute broken usemtl statements with old ones
if (curSurface == nullptr) bpos = 0, nlpos = 0;
{ for (size_t i = 0; i < mtlUsages.Size(); i++)
FTextureID dummyMtl = LoadSkin("", "-NOFLAT-"); // Built-in to GZDoom {
curSurface = new OBJSurface(dummyMtl); bpos = mtlUsageIdxs[i];
} nlpos = objBuf.IndexOf('\n', bpos);
curSurface->numFaces = curSurfFaceCount; if (nlpos == -1)
curSurface->faceStart = aggSurfFaceCount; {
surfaces.Push(*curSurface); nlpos = objBuf.Len();
delete curSurface; }
FString lineStr(objBuf.GetChars() + bpos, nlpos - bpos);
objBuf.Substitute(lineStr, mtlUsages[i]);
}
/* // Find each OBJ line comment, and convert each to a C-style line comment
Printf("%d vertices\n", verts.Size()); while (1)
Printf("%d normals\n", norms.Size()); {
Printf("%d UVs\n", uvs.Size()); bpos = objBuf.IndexOf('#');
Printf("%d faces\n", faces.Size()); if (bpos == -1) break;
Printf("%d surfaces\n", surfaces.Size()); objBuf.Remove(bpos, 1);
*/ objBuf.Insert(bpos, "//", 2);
}
}
sc.OpenString(objName, objBuf);
//Printf("Parsing %s\n", objName.GetChars());
mLumpNum = lumpnum; FTextureID curMtl = FNullTextureID();
for (size_t i = 0; i < surfaces.Size(); i++) OBJSurface *curSurface = nullptr;
{ int aggSurfFaceCount = 0;
ConstructSurfaceTris(&surfaces[i]); int curSurfFaceCount = 0;
}
return true; 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 * Parse a 2D vector
* *
* @param start The buffer to parse from * @param start The buffer to parse from
* @param array The array to append the parsed vector to * @param array The array to append the parsed vector to
*/ */
void FOBJModel::ParseVector2(TArray<FVector2> &array) void FOBJModel::ParseVector2(TArray<FVector2> &array)
{ {
float coord[2]; float coord[2];
for (int axis = 0; axis < 2; axis++) for (int axis = 0; axis < 2; axis++)
{ {
sc.MustGetFloat(); sc.MustGetFloat();
coord[axis] = (float)sc.Float; coord[axis] = (float)sc.Float;
} }
FVector2 vec(coord); FVector2 vec(coord);
array.Push(vec); array.Push(vec);
} }
/** /**
* Parse a 3D vector * Parse a 3D vector
* *
* @param start The buffer to parse from * @param start The buffer to parse from
* @param array The array to append the parsed vector to * @param array The array to append the parsed vector to
*/ */
void FOBJModel::ParseVector3(TArray<FVector3> &array) void FOBJModel::ParseVector3(TArray<FVector3> &array)
{ {
float coord[3]; float coord[3];
for (int axis = 0; axis < 3; axis++) for (int axis = 0; axis < 3; axis++)
{ {
sc.MustGetFloat(); sc.MustGetFloat();
coord[axis] = (float)sc.Float; coord[axis] = (float)sc.Float;
} }
FVector3 vec(coord); FVector3 vec(coord);
array.Push(vec); array.Push(vec);
} }
void FOBJModel::ParseFaceSide(const FString &sideStr, OBJFace &face, int sidx) void FOBJModel::ParseFaceSide(const FString &sideStr, OBJFace &face, int sidx)
{ {
OBJFaceSide side; OBJFaceSide side;
int origIdx; int origIdx;
if (sideStr.IndexOf("/") >= 0) if (sideStr.IndexOf(newSideSep) >= 0)
{ {
TArray<FString> sides = sideStr.Split("/"); TArray<FString> sides = sideStr.Split(newSideSep, FString::TOK_KEEPEMPTY);
if (sides[0].Len() > 0)
{ if (sides[0].Len() > 0)
origIdx = atoi(sides[0].GetChars()); {
side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex); origIdx = atoi(sides[0].GetChars());
} side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex);
if (sides[1].Len() > 0) }
{ else
origIdx = atoi(sides[1].GetChars()); {
side.uvref = ResolveIndex(origIdx, FaceElement::UVIndex); sc.ScriptError("Vertex reference is not optional!");
} }
if (sides.Size() > 2)
{ if (sides[1].Len() > 0)
if (sides[2].Len() > 0) {
{ origIdx = atoi(sides[1].GetChars());
origIdx = atoi(sides[2].GetChars()); side.uvref = ResolveIndex(origIdx, FaceElement::UVIndex);
side.normref = ResolveIndex(origIdx, FaceElement::VNormalIndex); }
} else
} {
} side.uvref = -1;
else }
{
origIdx = atoi(sideStr.GetChars()); if (sides.Size() > 2)
side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex); {
side.normref = 0; if (sides[2].Len() > 0)
side.uvref = 0; {
} origIdx = atoi(sides[2].GetChars());
face.sides[sidx] = side; 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) int FOBJModel::ResolveIndex(int origIndex, FaceElement el)
{ {
if (origIndex > 0) if (origIndex > 0)
{ {
return origIndex - 1; // OBJ indices start at 1 return origIndex - 1; // OBJ indices start at 1
} }
else if (origIndex < 0) else if (origIndex < 0)
{ {
if (el == FaceElement::VertexIndex) if (el == FaceElement::VertexIndex)
{ {
return this->verts.Size() + origIndex; // origIndex is negative return verts.Size() + origIndex; // origIndex is negative
} }
else if (el == FaceElement::UVIndex) else if (el == FaceElement::UVIndex)
{ {
return this->uvs.Size() + origIndex; return uvs.Size() + origIndex;
} }
else if (el == FaceElement::VNormalIndex) else if (el == FaceElement::VNormalIndex)
{ {
return this->norms.Size() + origIndex; return norms.Size() + origIndex;
} }
} }
return 0; return -1;
}
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));
}
*/
} }
void FOBJModel::BuildVertexBuffer(FModelRenderer *renderer) 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) 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() FOBJModel::~FOBJModel()
{ {
for (size_t i = 0; i < surfaces.Size(); i++) verts.Clear();
{ norms.Clear();
delete[] surfaces[i].tris; uvs.Clear();
} faces.Clear();
for (size_t i = 0; i < surfaces.Size(); i++)
{
delete[] surfaces[i].tris;
}
surfaces.Clear();
} }

View file

@ -29,66 +29,59 @@
class FOBJModel : public FModel class FOBJModel : public FModel
{ {
private: private:
int mLumpNum; int mLumpNum;
int numMtls; 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 enum class FaceElement
{ {
VertexIndex, VertexIndex,
UVIndex, UVIndex,
VNormalIndex VNormalIndex
}; };
struct OBJFaceSide struct OBJFaceSide
{ {
int vertref; int vertref;
int normref; int normref;
int uvref; int uvref;
}; };
struct OBJFace struct OBJFace
{ {
int sideCount; int sideCount;
OBJFaceSide sides[4]; OBJFaceSide sides[4];
}; };
struct OBJTriangle struct OBJSurface // 1 surface per 'usemtl'
{ {
int vertref[3]; unsigned int numTris; // Number of triangulated faces
}; unsigned int numFaces; // Number of faces
struct OBJSurface // 1 surface per 'usemtl' unsigned int vbStart; // First index in vertex buffer
{ unsigned int faceStart; // Index of first face in faces array
int numTris; // Number of triangulated faces OBJFace* tris; // Triangles
int numFaces; // Number of faces FTextureID skin;
int faceStart; // Index of first face in faces array OBJSurface(FTextureID skin): numTris(0), numFaces(0), vbStart(0), faceStart(0), tris(nullptr), skin(skin) {}
OBJFace* tris; // Triangles };
FTextureID skin;
OBJSurface(FTextureID skin): numTris(0), numFaces(0), faceStart(0), tris(nullptr), skin(skin) {}
};
TArray<FVector3> verts; TArray<FVector3> verts;
TArray<FVector3> norms; TArray<FVector3> norms;
TArray<FVector2> uvs; TArray<FVector2> uvs;
TArray<OBJFace> faces; TArray<OBJFace> faces;
TArray<OBJSurface> surfaces; TArray<OBJSurface> surfaces;
FTextureID curMtl; FScanner sc;
OBJSurface* curSurface;
int aggSurfFaceCount;
int curSurfFaceCount;
FScanner sc;
void ParseVector2(TArray<FVector2> &array); void ParseVector2(TArray<FVector2> &array);
void ParseVector3(TArray<FVector3> &array); void ParseVector3(TArray<FVector3> &array);
void ParseFaceSide(const FString &side, OBJFace &face, int sidx); void ParseFaceSide(const FString &side, OBJFace &face, int sidx);
void ConstructSurfaceTris(OBJSurface *surf); void ConstructSurfaceTris(OBJSurface &surf);
int ResolveIndex(int origIndex, FaceElement el); int ResolveIndex(int origIndex, FaceElement el);
void TriangulateQuad(const OBJFace &quad, OBJFace *tris); void TriangulateQuad(const OBJFace &quad, OBJFace *tris);
public: public:
FOBJModel(): curSurface(nullptr), aggSurfFaceCount(0), curSurfFaceCount(0) {} FOBJModel() {}
~FOBJModel(); ~FOBJModel();
bool Load(const char* fn, int lumpnum, const char* buffer, int length) override; bool Load(const char* fn, int lumpnum, const char* buffer, int length) override;
int FindFrame(const char* name) override; int FindFrame(const char* name) override;
void RenderFrame(FModelRenderer* renderer, FTexture* skin, int frame, int frame2, double inter, int translation=0) override; void RenderFrame(FModelRenderer* renderer, FTexture* skin, int frame, int frame2, double inter, int translation=0) override;
void BuildVertexBuffer(FModelRenderer* renderer) override; void BuildVertexBuffer(FModelRenderer* renderer) override;
void AddSkins(uint8_t* hitlist) override; void AddSkins(uint8_t* hitlist) override;
}; };
#endif #endif