mirror of
https://github.com/ZDoom/qzdoom.git
synced 2025-02-17 17:41:23 +00:00
Merge pull request #586 from Talon1024/feature/objSmoothCalc
Calculate normals for OBJ models with smooth groups
This commit is contained in:
commit
d4a64284ea
2 changed files with 152 additions and 26 deletions
|
@ -101,8 +101,9 @@ bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length
|
||||||
|
|
||||||
FTextureID curMtl = FNullTextureID();
|
FTextureID curMtl = FNullTextureID();
|
||||||
OBJSurface *curSurface = nullptr;
|
OBJSurface *curSurface = nullptr;
|
||||||
int aggSurfFaceCount = 0;
|
unsigned int aggSurfFaceCount = 0;
|
||||||
int curSurfFaceCount = 0;
|
unsigned int curSurfFaceCount = 0;
|
||||||
|
unsigned int curSmoothGroup = 0;
|
||||||
|
|
||||||
while(sc.GetString())
|
while(sc.GetString())
|
||||||
{
|
{
|
||||||
|
@ -186,9 +187,25 @@ bool FOBJModel::Load(const char* fn, int lumpnum, const char* buffer, int length
|
||||||
sc.UnGet(); // No 4th side, move back
|
sc.UnGet(); // No 4th side, move back
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
face.smoothGroup = curSmoothGroup;
|
||||||
faces.Push(face);
|
faces.Push(face);
|
||||||
curSurfFaceCount += 1;
|
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();
|
sc.Close();
|
||||||
|
|
||||||
|
@ -277,11 +294,13 @@ bool FOBJModel::ParseFaceSide(const FString &sideStr, OBJFace &face, int sidx)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
side.normref = -1;
|
side.normref = -1;
|
||||||
|
hasMissingNormals = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
side.normref = -1;
|
side.normref = -1;
|
||||||
|
hasMissingNormals = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -289,6 +308,7 @@ bool FOBJModel::ParseFaceSide(const FString &sideStr, OBJFace &face, int sidx)
|
||||||
origIdx = atoi(sideStr.GetChars());
|
origIdx = atoi(sideStr.GetChars());
|
||||||
side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex);
|
side.vertref = ResolveIndex(origIdx, FaceElement::VertexIndex);
|
||||||
side.normref = -1;
|
side.normref = -1;
|
||||||
|
hasMissingNormals = true;
|
||||||
side.uvref = -1;
|
side.uvref = -1;
|
||||||
}
|
}
|
||||||
face.sides[sidx] = side;
|
face.sides[sidx] = side;
|
||||||
|
@ -348,15 +368,20 @@ void FOBJModel::BuildVertexBuffer(FModelRenderer *renderer)
|
||||||
surfaces[i].vbStart = vbufsize;
|
surfaces[i].vbStart = vbufsize;
|
||||||
vbufsize += surfaces[i].numTris * 3;
|
vbufsize += surfaces[i].numTris * 3;
|
||||||
}
|
}
|
||||||
|
// Initialize/populate vertFaces
|
||||||
|
if (hasMissingNormals && hasSmoothGroups)
|
||||||
|
{
|
||||||
|
AddVertFaces();
|
||||||
|
}
|
||||||
|
|
||||||
auto vbuf = renderer->CreateVertexBuffer(false,true);
|
auto vbuf = renderer->CreateVertexBuffer(false,true);
|
||||||
SetVertexBuffer(renderer, vbuf);
|
SetVertexBuffer(renderer, vbuf);
|
||||||
|
|
||||||
FModelVertex *vertptr = vbuf->LockVertexBuffer(vbufsize);
|
FModelVertex *vertptr = vbuf->LockVertexBuffer(vbufsize);
|
||||||
|
|
||||||
for (size_t i = 0; i < surfaces.Size(); i++)
|
for (unsigned int i = 0; i < surfaces.Size(); i++)
|
||||||
{
|
{
|
||||||
for (size_t j = 0; j < surfaces[i].numTris; j++)
|
for (unsigned int j = 0; j < surfaces[i].numTris; j++)
|
||||||
{
|
{
|
||||||
for (size_t side = 0; side < 3; side++)
|
for (size_t side = 0; side < 3; side++)
|
||||||
{
|
{
|
||||||
|
@ -372,39 +397,40 @@ void FOBJModel::BuildVertexBuffer(FModelRenderer *renderer)
|
||||||
|
|
||||||
FVector3 curVvec = RealignVector(verts[vidx]);
|
FVector3 curVvec = RealignVector(verts[vidx]);
|
||||||
FVector2 curUvec = FixUV(uvs[uvidx]);
|
FVector2 curUvec = FixUV(uvs[uvidx]);
|
||||||
FVector3 *nvec = nullptr;
|
FVector3 nvec;
|
||||||
|
|
||||||
mdv->Set(curVvec.X, curVvec.Y, curVvec.Z, curUvec.X, curUvec.Y);
|
mdv->Set(curVvec.X, curVvec.Y, curVvec.Z, curUvec.X, curUvec.Y);
|
||||||
|
|
||||||
if (nidx >= 0 && (unsigned int)nidx < norms.Size())
|
if (nidx >= 0 && (unsigned int)nidx < norms.Size())
|
||||||
{
|
{
|
||||||
nvec = new FVector3(RealignVector(norms[nidx]));
|
nvec = RealignVector(norms[nidx]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// https://www.khronos.org/opengl/wiki/Calculating_a_Surface_Normal
|
if (surfaces[i].tris[j].smoothGroup == 0)
|
||||||
// Find other sides of triangle
|
{
|
||||||
auto nextSidx = side + 2;
|
nvec = CalculateNormalFlat(i, j);
|
||||||
if (nextSidx >= 3) nextSidx -= 3;
|
|
||||||
|
|
||||||
auto lastSidx = side + 1;
|
|
||||||
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 = RealignVector(verts[nextSide.vertref]) - curVvec;
|
|
||||||
FVector3 vvec = RealignVector(verts[lastSide.vertref]) - curVvec;
|
|
||||||
|
|
||||||
nvec = new FVector3(uvec ^ vvec);
|
|
||||||
}
|
}
|
||||||
mdv->SetNormal(nvec->X, nvec->Y, nvec->Z);
|
else
|
||||||
delete nvec;
|
{
|
||||||
|
nvec = CalculateNormalSmooth(vidx, surfaces[i].tris[j].smoothGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mdv->SetNormal(nvec.X, nvec.Y, nvec.Z);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete[] surfaces[i].tris;
|
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();
|
vbuf->UnlockVertexBuffer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -432,6 +458,7 @@ void FOBJModel::ConstructSurfaceTris(OBJSurface &surf)
|
||||||
surf.tris[triIdx].sideCount = 3;
|
surf.tris[triIdx].sideCount = 3;
|
||||||
if (faces[i].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);
|
memcpy(surf.tris[triIdx].sides, faces[i].sides, sizeof(OBJFaceSide) * 3);
|
||||||
}
|
}
|
||||||
else if (faces[i].sideCount == 4) // Triangulate face
|
else if (faces[i].sideCount == 4) // Triangulate face
|
||||||
|
@ -443,6 +470,7 @@ void FOBJModel::ConstructSurfaceTris(OBJSurface &surf)
|
||||||
delete[] triangulated;
|
delete[] triangulated;
|
||||||
triIdx += 1; // Filling out two faces
|
triIdx += 1; // Filling out two faces
|
||||||
}
|
}
|
||||||
|
DPrintf(DMSG_SPAMMY, "Smooth group: %d\n", surf.tris[triIdx].smoothGroup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -455,7 +483,9 @@ void FOBJModel::ConstructSurfaceTris(OBJSurface &surf)
|
||||||
void FOBJModel::TriangulateQuad(const OBJFace &quad, OBJFace *tris)
|
void FOBJModel::TriangulateQuad(const OBJFace &quad, OBJFace *tris)
|
||||||
{
|
{
|
||||||
tris[0].sideCount = 3;
|
tris[0].sideCount = 3;
|
||||||
|
tris[0].smoothGroup = quad.smoothGroup;
|
||||||
tris[1].sideCount = 3;
|
tris[1].sideCount = 3;
|
||||||
|
tris[1].smoothGroup = quad.smoothGroup;
|
||||||
|
|
||||||
int tsidx[2][3] = {{0, 1, 3}, {1, 2, 3}};
|
int tsidx[2][3] = {{0, 1, 3}, {1, 2, 3}};
|
||||||
|
|
||||||
|
@ -470,6 +500,26 @@ void FOBJModel::TriangulateQuad(const OBJFace &quad, OBJFace *tris)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
* Re-align a vector to match MD3 alignment
|
||||||
*
|
*
|
||||||
|
@ -494,6 +544,65 @@ inline FVector2 FOBJModel::FixUV(FVector2 vecToRealign)
|
||||||
return vecToRealign;
|
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
|
* Find the index of the frame with the given name
|
||||||
*
|
*
|
||||||
|
|
|
@ -30,6 +30,8 @@ class FOBJModel : public FModel
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
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.
|
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.
|
||||||
|
bool hasMissingNormals;
|
||||||
|
bool hasSmoothGroups;
|
||||||
|
|
||||||
enum class FaceElement
|
enum class FaceElement
|
||||||
{
|
{
|
||||||
|
@ -38,6 +40,14 @@ private:
|
||||||
VNormalIndex
|
VNormalIndex
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct OBJTriRef
|
||||||
|
{
|
||||||
|
unsigned int surf;
|
||||||
|
unsigned int tri;
|
||||||
|
OBJTriRef(): surf(0), tri(0) {}
|
||||||
|
OBJTriRef(unsigned int surf, unsigned int tri): surf(surf), tri(tri) {}
|
||||||
|
bool operator== (OBJTriRef other) { return surf == other.surf && tri == other.tri; }
|
||||||
|
};
|
||||||
struct OBJFaceSide
|
struct OBJFaceSide
|
||||||
{
|
{
|
||||||
int vertref;
|
int vertref;
|
||||||
|
@ -47,7 +57,9 @@ private:
|
||||||
struct OBJFace
|
struct OBJFace
|
||||||
{
|
{
|
||||||
unsigned int sideCount;
|
unsigned int sideCount;
|
||||||
|
unsigned int smoothGroup;
|
||||||
OBJFaceSide sides[4];
|
OBJFaceSide sides[4];
|
||||||
|
OBJFace(): sideCount(0), smoothGroup(0) {}
|
||||||
};
|
};
|
||||||
struct OBJSurface // 1 surface per 'usemtl'
|
struct OBJSurface // 1 surface per 'usemtl'
|
||||||
{
|
{
|
||||||
|
@ -66,16 +78,21 @@ private:
|
||||||
TArray<OBJFace> faces;
|
TArray<OBJFace> faces;
|
||||||
TArray<OBJSurface> surfaces;
|
TArray<OBJSurface> surfaces;
|
||||||
FScanner sc;
|
FScanner sc;
|
||||||
|
TArray<OBJTriRef>* vertFaces;
|
||||||
|
|
||||||
|
int ResolveIndex(int origIndex, FaceElement el);
|
||||||
template<typename T, size_t L> void ParseVector(TArray<T> &array);
|
template<typename T, size_t L> void ParseVector(TArray<T> &array);
|
||||||
bool ParseFaceSide(const FString &side, OBJFace &face, int sidx);
|
bool ParseFaceSide(const FString &side, OBJFace &face, int sidx);
|
||||||
void ConstructSurfaceTris(OBJSurface &surf);
|
void ConstructSurfaceTris(OBJSurface &surf);
|
||||||
int ResolveIndex(int origIndex, FaceElement el);
|
void AddVertFaces();
|
||||||
void TriangulateQuad(const OBJFace &quad, OBJFace *tris);
|
void TriangulateQuad(const OBJFace &quad, OBJFace *tris);
|
||||||
FVector3 RealignVector(FVector3 vecToRealign);
|
FVector3 RealignVector(FVector3 vecToRealign);
|
||||||
FVector2 FixUV(FVector2 vecToRealign);
|
FVector2 FixUV(FVector2 vecToRealign);
|
||||||
|
FVector3 CalculateNormalFlat(unsigned int surfIdx, unsigned int triIdx);
|
||||||
|
FVector3 CalculateNormalFlat(OBJTriRef otr);
|
||||||
|
FVector3 CalculateNormalSmooth(unsigned int vidx, unsigned int smoothGroup);
|
||||||
public:
|
public:
|
||||||
FOBJModel() {}
|
FOBJModel(): hasMissingNormals(false), hasSmoothGroups(false), vertFaces(nullptr) {}
|
||||||
~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;
|
||||||
|
|
Loading…
Reference in a new issue