Initial work on OBJ model support

What works:
- Parsing the model
- Constructing geometry (surfaces) for triangulated models

What doesn't:
- Rendering the model
- Building the vertex buffer
- Triangulating quads
This commit is contained in:
Kevin Caccamo 2018-06-03 03:26:42 -04:00 committed by Christoph Oelckers
parent 7c4dd9408d
commit 1c15fb2408
4 changed files with 444 additions and 1 deletions

View file

@ -1154,6 +1154,7 @@ set (PCH_SOURCES
r_data/models/models_md2.cpp r_data/models/models_md2.cpp
r_data/models/models_voxel.cpp r_data/models/models_voxel.cpp
r_data/models/models_ue1.cpp r_data/models/models_ue1.cpp
r_data/models/models_obj.cpp
scripting/symbols.cpp scripting/symbols.cpp
scripting/types.cpp scripting/types.cpp
scripting/thingdef.cpp scripting/thingdef.cpp

View file

@ -40,6 +40,7 @@
#include "r_utility.h" #include "r_utility.h"
#include "r_data/models/models.h" #include "r_data/models/models.h"
#include "r_data/models/models_ue1.h" #include "r_data/models/models_ue1.h"
#include "r_data/models/models_obj.h"
#include "i_time.h" #include "i_time.h"
#ifdef _MSC_VER #ifdef _MSC_VER
@ -437,6 +438,10 @@ static unsigned FindModel(const char * path, const char * modelfile)
model = new FUE1Model; model = new FUE1Model;
} }
} }
else if ( (size_t)fullname.LastIndexOf(".obj") == fullname.Len() - 1 )
{ // LastIndexOf works differently than it does in JavaScript
model = new FOBJModel;
}
else if (!memcmp(buffer, "DMDM", 4)) else if (!memcmp(buffer, "DMDM", 4))
{ {
model = new FDMDModel; model = new FDMDModel;
@ -937,4 +942,3 @@ bool IsHUDModelForPlayerAvailable (player_t * player)
FSpriteModelFrame *smf = FindModelFrame(player->ReadyWeapon->GetClass(), state->sprite, state->GetFrame(), false); FSpriteModelFrame *smf = FindModelFrame(player->ReadyWeapon->GetClass(), state->sprite, state->GetFrame(), false);
return ( smf != nullptr ); return ( smf != nullptr );
} }

View file

@ -0,0 +1,344 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2018 Kevin Caccamo
// All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
//--------------------------------------------------------------------------
#include "w_wad.h"
#include "cmdlib.h"
#include "r_data/models/models_obj.h"
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();
curMtl = LoadSkin("", sc.String);
if (!curMtl.isValid())
{
// Relative to model file path?
curMtl = LoadSkin(fn, sc.String);
}
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();
// 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;
/*
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;
for (size_t i = 0; i < surfaces.Size(); i++)
{
ConstructSurfaceTris(&surfaces[i]);
}
return true;
}
/**
* 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<FVector2> &array)
{
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
*/
void FOBJModel::ParseVector3(TArray<FVector3> &array)
{
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<FString> 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;
}
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));
}
*/
}
void FOBJModel::BuildVertexBuffer(FModelRenderer *renderer)
{
}
void FOBJModel::AddSkins(uint8_t* hitlist)
{
}
FOBJModel::~FOBJModel()
{
for (size_t i = 0; i < surfaces.Size(); i++)
{
delete[] surfaces[i].tris;
}
}

View file

@ -0,0 +1,94 @@
//
//---------------------------------------------------------------------------
//
// Copyright(C) 2018 Kevin Caccamo
// All rights reserved.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this program. If not, see http://www.gnu.org/licenses/
//
//--------------------------------------------------------------------------
//
#ifndef __GL_MODELS_OBJ_H__
#define __GL_MODELS_OBJ_H__
#include "models.h"
#include "sc_man.h"
class FOBJModel : public FModel
{
private:
int mLumpNum;
int numMtls;
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) {}
};
TArray<FVector3> verts;
TArray<FVector3> norms;
TArray<FVector2> uvs;
TArray<OBJFace> faces;
TArray<OBJSurface> surfaces;
FTextureID curMtl;
OBJSurface* curSurface;
int aggSurfFaceCount;
int curSurfFaceCount;
FScanner sc;
void ParseVector2(TArray<FVector2> &array);
void ParseVector3(TArray<FVector3> &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;
};
#endif