diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 021661d312..0c880459d2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1154,6 +1154,7 @@ set (PCH_SOURCES r_data/models/models_md2.cpp r_data/models/models_voxel.cpp r_data/models/models_ue1.cpp + r_data/models/models_obj.cpp scripting/symbols.cpp scripting/types.cpp scripting/thingdef.cpp diff --git a/src/r_data/models/models.cpp b/src/r_data/models/models.cpp index 44d70b493c..fefbb88a06 100644 --- a/src/r_data/models/models.cpp +++ b/src/r_data/models/models.cpp @@ -40,6 +40,7 @@ #include "r_utility.h" #include "r_data/models/models.h" #include "r_data/models/models_ue1.h" +#include "r_data/models/models_obj.h" #include "i_time.h" #ifdef _MSC_VER @@ -437,6 +438,10 @@ static unsigned FindModel(const char * path, const char * modelfile) 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)) { model = new FDMDModel; @@ -937,4 +942,3 @@ bool IsHUDModelForPlayerAvailable (player_t * player) FSpriteModelFrame *smf = FindModelFrame(player->ReadyWeapon->GetClass(), state->sprite, state->GetFrame(), false); return ( smf != nullptr ); } - diff --git a/src/r_data/models/models_obj.cpp b/src/r_data/models/models_obj.cpp new file mode 100644 index 0000000000..e754d2daa6 --- /dev/null +++ b/src/r_data/models/models_obj.cpp @@ -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 &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 &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 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; + } +} diff --git a/src/r_data/models/models_obj.h b/src/r_data/models/models_obj.h new file mode 100644 index 0000000000..6fa2034c2d --- /dev/null +++ b/src/r_data/models/models_obj.h @@ -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 verts; + TArray norms; + TArray uvs; + TArray faces; + TArray surfaces; + FTextureID curMtl; + OBJSurface* curSurface; + int aggSurfFaceCount; + int curSurfFaceCount; + 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); +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