mirror of
https://github.com/ZDoom/qzdoom.git
synced 2024-11-28 15:02:01 +00:00
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:
parent
7c4dd9408d
commit
1c15fb2408
4 changed files with 444 additions and 1 deletions
|
@ -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
|
||||||
|
|
|
@ -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 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
344
src/r_data/models/models_obj.cpp
Normal file
344
src/r_data/models/models_obj.cpp
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
94
src/r_data/models/models_obj.h
Normal file
94
src/r_data/models/models_obj.h
Normal 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
|
Loading…
Reference in a new issue