mirror of
https://github.com/ZDoom/Raze.git
synced 2024-12-11 13:21:49 +00:00
84173ee09b
The main bulk of this is the new start screen code. To make this work in Raze some more work on the startup procedure is needed. What this does provide is support for the DOS end-of-game text screens in Duke and SW on non-Windows systems.
704 lines
18 KiB
C++
704 lines
18 KiB
C++
//
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
// 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 "filesystem.h"
|
|
#include "model_obj.h"
|
|
#include "texturemanager.h"
|
|
#include "modelrenderer.h"
|
|
#include "printf.h"
|
|
#include "textureid.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
|
|
* @return Whether or not the model was parsed successfully
|
|
*/
|
|
bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length)
|
|
{
|
|
FString objName = fileSystem.GetFileFullPath(lumpnum);
|
|
FString objBuf(buffer, length);
|
|
|
|
// Do some replacements before we parse the OBJ string
|
|
{
|
|
// Ensure usemtl statements remain intact
|
|
TArray<FString> mtlUsages;
|
|
TArray<ptrdiff_t> mtlUsageIdxs;
|
|
ptrdiff_t 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);
|
|
}
|
|
|
|
// Replace forward slashes with percent signs so they aren't parsed as line comments
|
|
objBuf.ReplaceChars('/', *newSideSep);
|
|
char* wObjBuf = objBuf.LockBuffer();
|
|
|
|
// Substitute broken usemtl statements with old ones
|
|
for (size_t i = 0; i < mtlUsages.Size(); i++)
|
|
{
|
|
bpos = mtlUsageIdxs[i];
|
|
nlpos = objBuf.IndexOf('\n', bpos);
|
|
if (nlpos == -1)
|
|
{
|
|
nlpos = objBuf.Len();
|
|
}
|
|
memcpy(wObjBuf + bpos, mtlUsages[i].GetChars(), nlpos - bpos);
|
|
}
|
|
|
|
bpos = 0;
|
|
// Find each OBJ line comment, and convert each to a C-style line comment
|
|
while (1)
|
|
{
|
|
bpos = objBuf.IndexOf('#', bpos);
|
|
if (bpos == -1) break;
|
|
if (objBuf[(unsigned int)bpos + 1] == '\n')
|
|
{
|
|
wObjBuf[bpos] = ' ';
|
|
}
|
|
else
|
|
{
|
|
wObjBuf[bpos] = '/';
|
|
wObjBuf[bpos+1] = '/';
|
|
}
|
|
bpos += 1;
|
|
}
|
|
wObjBuf = nullptr;
|
|
objBuf.UnlockBuffer();
|
|
}
|
|
sc.OpenString(objName, objBuf);
|
|
|
|
FTextureID curMtl = FNullTextureID();
|
|
OBJSurface *curSurface = nullptr;
|
|
unsigned int aggSurfFaceCount = 0;
|
|
unsigned int curSurfFaceCount = 0;
|
|
unsigned int curSmoothGroup = 0;
|
|
|
|
while(sc.GetString())
|
|
{
|
|
if (sc.Compare("v")) // Vertex
|
|
{
|
|
ParseVector<FVector3, 3>(this->verts);
|
|
}
|
|
else if (sc.Compare("vn")) // Vertex normal
|
|
{
|
|
ParseVector<FVector3, 3>(this->norms);
|
|
}
|
|
else if (sc.Compare("vt")) // UV Coordinates
|
|
{
|
|
ParseVector<FVector2, 2>(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())
|
|
{
|
|
sc.ScriptMessage("Material %s (#%u) not found.", sc.String, surfaces.Size());
|
|
}
|
|
|
|
// 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;
|
|
if (!ParseFaceSide(sides[i], face, i)) return false;
|
|
}
|
|
face.sideCount = 3;
|
|
if (sc.GetString())
|
|
{
|
|
if (!sc.Compare("f") && FString(sc.String).IndexOfAny("-0123456789") == 0)
|
|
{
|
|
sides[3] = sc.String;
|
|
face.sideCount += 1;
|
|
if (!ParseFaceSide(sides[3], face, 3)) return false;
|
|
}
|
|
else
|
|
{
|
|
sc.UnGet(); // No 4th side, move back
|
|
}
|
|
}
|
|
face.smoothGroup = curSmoothGroup;
|
|
faces.Push(face);
|
|
curSurfFaceCount += 1;
|
|
}
|
|
else if (sc.Compare("s"))
|
|
{
|
|
sc.MustGetString();
|
|
if (sc.Compare("off"))
|
|
{
|
|
curSmoothGroup = 0;
|
|
}
|
|
else
|
|
{
|
|
sc.UnGet();
|
|
sc.MustGetNumber();
|
|
curSmoothGroup = sc.Number;
|
|
hasSmoothGroups = hasSmoothGroups || curSmoothGroup > 0;
|
|
}
|
|
}
|
|
}
|
|
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));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Parse an x-Dimensional vector
|
|
*
|
|
* @tparam T A subclass of TVector2 to be used
|
|
* @tparam L The length of the vector to parse
|
|
* @param[out] array The array to append the parsed vector to
|
|
*/
|
|
template<typename T, size_t L> void FOBJModel::ParseVector(TArray<T> &array)
|
|
{
|
|
T vec;
|
|
for (unsigned axis = 0; axis < L; axis++)
|
|
{
|
|
sc.MustGetFloat();
|
|
vec[axis] = (float)sc.Float;
|
|
}
|
|
array.Push(vec);
|
|
}
|
|
|
|
/**
|
|
* Parse a side of a face
|
|
*
|
|
* @param[in] sideStr The side definition string
|
|
* @param[out] face The face to assign the parsed side data to
|
|
* @param sidx The 0-based index of the side
|
|
* @return Whether or not the face side was parsed successfully
|
|
*/
|
|
bool FOBJModel::ParseFaceSide(const FString &sideStr, OBJFace &face, int sidx)
|
|
{
|
|
OBJFaceSide side;
|
|
int origIdx;
|
|
if (sideStr.IndexOf(newSideSep) >= 0)
|
|
{
|
|
TArray<FString> 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!");
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
hasMissingNormals = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
side.normref = -1;
|
|
hasMissingNormals = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
origIdx = atoi(sideStr.GetChars());
|
|
side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex);
|
|
side.normref = -1;
|
|
hasMissingNormals = true;
|
|
side.uvref = -1;
|
|
}
|
|
face.sides[sidx] = side;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Resolve an OBJ index to an absolute index
|
|
*
|
|
* OBJ indices are 1-based, and can also be negative
|
|
*
|
|
* @param origIndex The original OBJ index to resolve
|
|
* @param el What type of element the index references
|
|
* @return The absolute index of the element
|
|
*/
|
|
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 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;
|
|
}
|
|
|
|
/**
|
|
* Construct the vertex buffer for this model
|
|
*
|
|
* @param renderer A pointer to the model renderer. Used to allocate the vertex buffer.
|
|
*/
|
|
void FOBJModel::BuildVertexBuffer(FModelRenderer *renderer)
|
|
{
|
|
if (GetVertexBuffer(renderer->GetType()))
|
|
{
|
|
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;
|
|
}
|
|
// Initialize/populate vertFaces
|
|
if (hasMissingNormals && hasSmoothGroups)
|
|
{
|
|
AddVertFaces();
|
|
}
|
|
|
|
auto vbuf = renderer->CreateVertexBuffer(false,true);
|
|
SetVertexBuffer(renderer->GetType(), vbuf);
|
|
|
|
FModelVertex *vertptr = vbuf->LockVertexBuffer(vbufsize);
|
|
|
|
for (unsigned int i = 0; i < surfaces.Size(); i++)
|
|
{
|
|
for (unsigned int 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[2 - side];
|
|
|
|
int vidx = curSide.vertref;
|
|
int uvidx = (curSide.uvref >= 0 && (unsigned int)curSide.uvref < uvs.Size()) ? curSide.uvref : 0;
|
|
int nidx = curSide.normref;
|
|
|
|
FVector3 curVvec = RealignVector(verts[vidx]);
|
|
FVector2 curUvec = FixUV(uvs[uvidx]);
|
|
FVector3 nvec;
|
|
|
|
mdv->Set(curVvec.X, curVvec.Y, curVvec.Z, curUvec.X, curUvec.Y);
|
|
|
|
if (nidx >= 0 && (unsigned int)nidx < norms.Size())
|
|
{
|
|
nvec = RealignVector(norms[nidx]);
|
|
}
|
|
else
|
|
{
|
|
if (surfaces[i].tris[j].smoothGroup == 0)
|
|
{
|
|
nvec = CalculateNormalFlat(i, j);
|
|
}
|
|
else
|
|
{
|
|
nvec = CalculateNormalSmooth(vidx, surfaces[i].tris[j].smoothGroup);
|
|
}
|
|
}
|
|
mdv->SetNormal(nvec.X, nvec.Y, nvec.Z);
|
|
}
|
|
}
|
|
delete[] surfaces[i].tris;
|
|
}
|
|
|
|
// Destroy vertFaces
|
|
if (hasMissingNormals && hasSmoothGroups)
|
|
{
|
|
for (size_t i = 0; i < verts.Size(); i++)
|
|
{
|
|
vertFaces[i].Clear();
|
|
}
|
|
delete[] vertFaces;
|
|
}
|
|
vbuf->UnlockVertexBuffer();
|
|
}
|
|
|
|
/**
|
|
* Fill in the triangle data for a surface
|
|
*
|
|
* @param[in,out] surf The surface to fill in the triangle data for
|
|
*/
|
|
void FOBJModel::ConstructSurfaceTris(OBJSurface &surf)
|
|
{
|
|
unsigned 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)
|
|
{
|
|
surf.tris[triIdx].smoothGroup = faces[i].smoothGroup;
|
|
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
|
|
}
|
|
DPrintf(DMSG_SPAMMY, "Smooth group: %d\n", surf.tris[triIdx].smoothGroup);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Triangulate a 4-sided face
|
|
*
|
|
* @param[in] quad The 4-sided face to triangulate
|
|
* @param[out] tris The resultant triangle data
|
|
*/
|
|
void FOBJModel::TriangulateQuad(const OBJFace &quad, OBJFace *tris)
|
|
{
|
|
tris[0].sideCount = 3;
|
|
tris[0].smoothGroup = quad.smoothGroup;
|
|
tris[1].sideCount = 3;
|
|
tris[1].smoothGroup = quad.smoothGroup;
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add the vertices of all surfaces' triangles to the array of vertex->triangle references
|
|
*/
|
|
void FOBJModel::AddVertFaces() {
|
|
// Initialize and populate vertFaces - this array stores references to triangles per vertex
|
|
vertFaces = new TArray<OBJTriRef>[verts.Size()];
|
|
for (unsigned int i = 0; i < surfaces.Size(); i++)
|
|
{
|
|
for (unsigned int j = 0; j < surfaces[i].numTris; j++)
|
|
{
|
|
OBJTriRef otr = OBJTriRef(i, j);
|
|
for (size_t k = 0; k < surfaces[i].tris[j].sideCount; k++)
|
|
{
|
|
int vidx = surfaces[i].tris[j].sides[k].vertref;
|
|
vertFaces[vidx].Push(otr);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Re-align a vector to match MD3 alignment
|
|
*
|
|
* @param vecToRealign The vector to re-align
|
|
* @return The re-aligned vector
|
|
*/
|
|
inline FVector3 FOBJModel::RealignVector(FVector3 vecToRealign)
|
|
{
|
|
vecToRealign.Z *= -1;
|
|
return vecToRealign;
|
|
}
|
|
|
|
/**
|
|
* Fix UV coordinates of a UV vector
|
|
*
|
|
* @param vecToRealign The vector to fix
|
|
* @return The fixed UV coordinate vector
|
|
*/
|
|
inline FVector2 FOBJModel::FixUV(FVector2 vecToRealign)
|
|
{
|
|
vecToRealign.Y *= -1;
|
|
return vecToRealign;
|
|
}
|
|
|
|
/**
|
|
* Calculate the surface normal for a triangle
|
|
*
|
|
* @param surfIdx The surface index
|
|
* @param triIdx The triangle Index
|
|
* @return The surface normal vector
|
|
*/
|
|
FVector3 FOBJModel::CalculateNormalFlat(unsigned int surfIdx, unsigned int triIdx)
|
|
{
|
|
// https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
|
|
int curVert = surfaces[surfIdx].tris[triIdx].sides[0].vertref;
|
|
int nextVert = surfaces[surfIdx].tris[triIdx].sides[2].vertref;
|
|
int lastVert = surfaces[surfIdx].tris[triIdx].sides[1].vertref;
|
|
|
|
// Cross-multiply the U-vector and V-vector
|
|
FVector3 curVvec = RealignVector(verts[curVert]);
|
|
FVector3 uvec = RealignVector(verts[nextVert]) - curVvec;
|
|
FVector3 vvec = RealignVector(verts[lastVert]) - curVvec;
|
|
|
|
return uvec ^ vvec;
|
|
}
|
|
|
|
/**
|
|
* Calculate the surface normal for a triangle
|
|
*
|
|
* @param otr A reference to the surface, and a triangle within that surface, as an OBJTriRef
|
|
* @return The surface normal vector
|
|
*/
|
|
FVector3 FOBJModel::CalculateNormalFlat(OBJTriRef otr)
|
|
{
|
|
return CalculateNormalFlat(otr.surf, otr.tri);
|
|
}
|
|
|
|
/**
|
|
* Calculate the normal of a vertex in a specific smooth group
|
|
*
|
|
* @param vidx The index of the vertex in the array of vertices
|
|
* @param smoothGroup The smooth group number
|
|
*/
|
|
FVector3 FOBJModel::CalculateNormalSmooth(unsigned int vidx, unsigned int smoothGroup)
|
|
{
|
|
unsigned int connectedFaces = 0;
|
|
TArray<OBJTriRef>& vTris = vertFaces[vidx];
|
|
|
|
FVector3 vNormal(0,0,0);
|
|
for (size_t face = 0; face < vTris.Size(); face++)
|
|
{
|
|
OBJFace& tri = surfaces[vTris[face].surf].tris[vTris[face].tri];
|
|
if (tri.smoothGroup == smoothGroup)
|
|
{
|
|
FVector3 fNormal = CalculateNormalFlat(vTris[face]);
|
|
connectedFaces += 1;
|
|
vNormal += fNormal;
|
|
}
|
|
}
|
|
vNormal /= (float)connectedFaces;
|
|
return vNormal;
|
|
}
|
|
|
|
/**
|
|
* Find the index of the frame with the given name
|
|
*
|
|
* OBJ models are not animated, so this always returns 0
|
|
*
|
|
* @param name The name of the frame
|
|
* @return The index of the frame
|
|
*/
|
|
int FOBJModel::FindFrame(const char* name)
|
|
{
|
|
return 0; // OBJs are not animated.
|
|
}
|
|
|
|
/**
|
|
* Render the model
|
|
*
|
|
* @param renderer The model renderer
|
|
* @param skin The loaded skin for the surface
|
|
* @param frameno The first frame to interpolate between. Only prevents the model from rendering if it is < 0, since OBJ models are static.
|
|
* @param frameno2 The second frame to interpolate between.
|
|
* @param inter The amount to interpolate the two frames.
|
|
* @param translation The translation for the skin
|
|
*/
|
|
void FOBJModel::RenderFrame(FModelRenderer *renderer, FGameTexture * skin, int frameno, int frameno2, double inter, int translation)
|
|
{
|
|
// Prevent the model from rendering if the frame number is < 0
|
|
if (frameno < 0 || frameno2 < 0) return;
|
|
|
|
for (unsigned int i = 0; i < surfaces.Size(); i++)
|
|
{
|
|
OBJSurface *surf = &surfaces[i];
|
|
|
|
FGameTexture *userSkin = skin;
|
|
if (!userSkin && curSpriteMDLFrame)
|
|
{
|
|
int ssIndex = i + curMDLIndex * MD3_MAX_SURFACES;
|
|
if (i < MD3_MAX_SURFACES && curSpriteMDLFrame->surfaceskinIDs[ssIndex].isValid())
|
|
{
|
|
userSkin = TexMan.GetGameTexture(curSpriteMDLFrame->surfaceskinIDs[ssIndex], true);
|
|
}
|
|
else if (surf->skin.isValid())
|
|
{
|
|
userSkin = TexMan.GetGameTexture(surf->skin, true);
|
|
}
|
|
}
|
|
|
|
// Still no skin after checking for one?
|
|
if (!userSkin)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
renderer->SetMaterial(userSkin, false, translation);
|
|
renderer->SetupFrame(this, surf->vbStart, surf->vbStart, surf->numTris * 3);
|
|
renderer->DrawArrays(0, surf->numTris * 3);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Pre-cache skins for the model
|
|
*
|
|
* @param hitlist The list of textures
|
|
*/
|
|
void FOBJModel::AddSkins(uint8_t* hitlist)
|
|
{
|
|
for (size_t i = 0; i < surfaces.Size(); i++)
|
|
{
|
|
size_t ssIndex = i + curMDLIndex * MD3_MAX_SURFACES;
|
|
if (curSpriteMDLFrame && i < MD3_MAX_SURFACES && curSpriteMDLFrame->surfaceskinIDs[ssIndex].isValid())
|
|
{
|
|
// Precache skins manually reassigned by the user.
|
|
// On OBJs with lots of skins, such as Doom map OBJs exported from GZDB,
|
|
// there may be too many skins for the user to manually change, unless
|
|
// the limit is bumped or surfaceskinIDs is changed to a TArray<FTextureID>.
|
|
hitlist[curSpriteMDLFrame->surfaceskinIDs[ssIndex].GetIndex()] |= FTextureManager::HIT_Flat;
|
|
return; // No need to precache skin that was replaced
|
|
}
|
|
|
|
OBJSurface * surf = &surfaces[i];
|
|
if (surf->skin.isValid())
|
|
{
|
|
hitlist[surf->skin.GetIndex()] |= FTextureManager::HIT_Flat;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove the data that was loaded
|
|
*/
|
|
FOBJModel::~FOBJModel()
|
|
{
|
|
verts.Clear();
|
|
norms.Clear();
|
|
uvs.Clear();
|
|
faces.Clear();
|
|
surfaces.Clear();
|
|
}
|