324 lines
9.6 KiB
C++
324 lines
9.6 KiB
C++
#include "CModel.h"
|
|
#include "Core/Render/CDrawUtil.h"
|
|
#include "Core/Render/CRenderer.h"
|
|
#include "Core/Resource/Area/CGameArea.h"
|
|
#include "Core/OpenGL/GLCommon.h"
|
|
#include <Common/Macros.h>
|
|
|
|
CModel::CModel(CResourceEntry *pEntry /*= 0*/)
|
|
: CBasicModel(pEntry)
|
|
{
|
|
mHasOwnMaterials = true;
|
|
mHasOwnSurfaces = true;
|
|
}
|
|
|
|
CModel::CModel(CMaterialSet *pSet, bool OwnsMatSet)
|
|
: CBasicModel()
|
|
{
|
|
mHasOwnMaterials = OwnsMatSet;
|
|
mHasOwnSurfaces = true;
|
|
|
|
mMaterialSets.resize(1);
|
|
mMaterialSets[0] = pSet;
|
|
}
|
|
|
|
CModel::~CModel()
|
|
{
|
|
if (mHasOwnMaterials)
|
|
for (uint32 iMat = 0; iMat < mMaterialSets.size(); iMat++)
|
|
delete mMaterialSets[iMat];
|
|
}
|
|
|
|
|
|
CDependencyTree* CModel::BuildDependencyTree() const
|
|
{
|
|
CDependencyTree *pTree = new CDependencyTree();
|
|
std::set<CAssetID> TextureIDs;
|
|
|
|
for (uint32 iSet = 0; iSet < mMaterialSets.size(); iSet++)
|
|
{
|
|
CMaterialSet *pSet = mMaterialSets[iSet];
|
|
pSet->GetUsedTextureIDs(TextureIDs);
|
|
}
|
|
|
|
for (auto Iter = TextureIDs.begin(); Iter != TextureIDs.end(); Iter++)
|
|
pTree->AddDependency(*Iter);
|
|
|
|
return pTree;
|
|
}
|
|
|
|
void CModel::BufferGL()
|
|
{
|
|
if (!mBuffered)
|
|
{
|
|
mVBO.Clear();
|
|
mSurfaceIndexBuffers.clear();
|
|
|
|
mSurfaceIndexBuffers.resize(mSurfaces.size());
|
|
|
|
for (uint32 iSurf = 0; iSurf < mSurfaces.size(); iSurf++)
|
|
{
|
|
SSurface *pSurf = mSurfaces[iSurf];
|
|
|
|
uint16 VBOStartOffset = (uint16) mVBO.Size();
|
|
mVBO.Reserve((uint16) pSurf->VertexCount);
|
|
|
|
for (uint32 iPrim = 0; iPrim < pSurf->Primitives.size(); iPrim++)
|
|
{
|
|
SSurface::SPrimitive *pPrim = &pSurf->Primitives[iPrim];
|
|
CIndexBuffer *pIBO = InternalGetIBO(iSurf, pPrim->Type);
|
|
pIBO->Reserve(pPrim->Vertices.size() + 1); // Allocate enough space for this primitive, plus the restart index
|
|
|
|
std::vector<uint16> Indices(pPrim->Vertices.size());
|
|
for (uint32 iVert = 0; iVert < pPrim->Vertices.size(); iVert++)
|
|
Indices[iVert] = mVBO.AddIfUnique(pPrim->Vertices[iVert], VBOStartOffset);
|
|
|
|
// then add the indices to the IBO. We convert some primitives to strips to minimize draw calls.
|
|
switch (pPrim->Type)
|
|
{
|
|
case EPrimitiveType::Triangles:
|
|
pIBO->TrianglesToStrips(Indices.data(), Indices.size());
|
|
break;
|
|
case EPrimitiveType::TriangleFan:
|
|
pIBO->FansToStrips(Indices.data(), Indices.size());
|
|
break;
|
|
case EPrimitiveType::Quads:
|
|
pIBO->QuadsToStrips(Indices.data(), Indices.size());
|
|
break;
|
|
default:
|
|
pIBO->AddIndices(Indices.data(), Indices.size());
|
|
pIBO->AddIndex(0xFFFF); // primitive restart
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (uint32 iIBO = 0; iIBO < mSurfaceIndexBuffers[iSurf].size(); iIBO++)
|
|
mSurfaceIndexBuffers[iSurf][iIBO].Buffer();
|
|
}
|
|
|
|
mBuffered = true;
|
|
}
|
|
}
|
|
|
|
void CModel::GenerateMaterialShaders()
|
|
{
|
|
for (uint32 iSet = 0; iSet < mMaterialSets.size(); iSet++)
|
|
{
|
|
CMaterialSet *pSet = mMaterialSets[iSet];
|
|
|
|
for (uint32 iMat = 0; iMat < pSet->NumMaterials(); iMat++)
|
|
{
|
|
pSet->MaterialByIndex(iMat, false)->GenerateShader(false);
|
|
pSet->MaterialByIndex(iMat, true)->GenerateShader(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CModel::ClearGLBuffer()
|
|
{
|
|
mVBO.Clear();
|
|
mSurfaceIndexBuffers.clear();
|
|
mBuffered = false;
|
|
}
|
|
|
|
void CModel::Draw(FRenderOptions Options, uint32 MatSet)
|
|
{
|
|
if (!mBuffered) BufferGL();
|
|
for (uint32 iSurf = 0; iSurf < mSurfaces.size(); iSurf++)
|
|
DrawSurface(Options, iSurf, MatSet);
|
|
}
|
|
|
|
void CModel::DrawSurface(FRenderOptions Options, uint32 Surface, uint32 MatSet)
|
|
{
|
|
if (!mBuffered) BufferGL();
|
|
|
|
// Check that mat set index is valid
|
|
if (MatSet >= mMaterialSets.size())
|
|
MatSet = mMaterialSets.size() - 1;
|
|
|
|
auto DoDraw = [this, Surface]()
|
|
{
|
|
// Draw IBOs
|
|
mVBO.Bind();
|
|
glLineWidth(1.f);
|
|
|
|
for (uint32 iIBO = 0; iIBO < mSurfaceIndexBuffers[Surface].size(); iIBO++)
|
|
{
|
|
CIndexBuffer *pIBO = &mSurfaceIndexBuffers[Surface][iIBO];
|
|
pIBO->DrawElements();
|
|
}
|
|
|
|
mVBO.Unbind();
|
|
};
|
|
|
|
// Bind material
|
|
if ((Options & ERenderOption::NoMaterialSetup) == 0)
|
|
{
|
|
SSurface *pSurf = mSurfaces[Surface];
|
|
CMaterial *pMat = mMaterialSets[MatSet]->MaterialByIndex(pSurf->MaterialID, Options.HasFlag(ERenderOption::EnableBloom));
|
|
|
|
if (!Options.HasFlag(ERenderOption::EnableOccluders) && pMat->Options().HasFlag(EMaterialOption::Occluder))
|
|
return;
|
|
|
|
for (CMaterial* passMat = pMat; passMat; passMat = passMat->GetNextDrawPass())
|
|
{
|
|
passMat->SetCurrent(Options);
|
|
DoDraw();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DoDraw();
|
|
}
|
|
}
|
|
|
|
void CModel::DrawWireframe(FRenderOptions Options, CColor WireColor /*= CColor::skWhite*/)
|
|
{
|
|
if (!mBuffered) BufferGL();
|
|
|
|
// Set up wireframe
|
|
WireColor.A = 0;
|
|
CDrawUtil::UseColorShader(WireColor);
|
|
Options |= ERenderOption::NoMaterialSetup;
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
|
glBlendFunc(GL_ONE, GL_ZERO);
|
|
|
|
// Draw surfaces
|
|
for (uint32 iSurf = 0; iSurf < mSurfaces.size(); iSurf++)
|
|
DrawSurface(Options, iSurf, 0);
|
|
|
|
// Cleanup
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
}
|
|
|
|
void CModel::SetSkin(CSkin *pSkin)
|
|
{
|
|
// Assert commented out because it actually failed somewhere! Needs to be addressed.
|
|
//ASSERT(!mpSkin || !pSkin || mpSkin == pSkin); // This is to verify no model has more than one unique skin applied
|
|
|
|
//@todo this is actually really dumb and could be handled much better (and much more inline with how the game does it)
|
|
// by simply storing skinning data in a different vertex buffer that isn't tied to the model's vertex buffer
|
|
if (mpSkin != pSkin)
|
|
{
|
|
const FVertexDescription kBoneFlags = (EVertexAttribute::BoneIndices | EVertexAttribute::BoneWeights);
|
|
|
|
mpSkin = pSkin;
|
|
mVBO.SetSkin(pSkin);
|
|
ClearGLBuffer();
|
|
|
|
if (pSkin && !mVBO.VertexDesc().HasAllFlags(kBoneFlags))
|
|
mVBO.SetVertexDesc(mVBO.VertexDesc() | kBoneFlags);
|
|
else if (!pSkin && mVBO.VertexDesc().HasAnyFlags(kBoneFlags))
|
|
mVBO.SetVertexDesc(mVBO.VertexDesc() & ~kBoneFlags);
|
|
|
|
for (uint32 iSet = 0; iSet < mMaterialSets.size(); iSet++)
|
|
{
|
|
CMaterialSet *pSet = mMaterialSets[iSet];
|
|
|
|
for (uint32 iMat = 0; iMat < pSet->NumMaterials(); iMat++)
|
|
{
|
|
for (bool iBloom = false; !iBloom; iBloom = true)
|
|
{
|
|
CMaterial *pMat = pSet->MaterialByIndex(iMat, iBloom);
|
|
FVertexDescription VtxDesc = pMat->VtxDesc();
|
|
|
|
if (pSkin && !VtxDesc.HasAllFlags(kBoneFlags))
|
|
{
|
|
VtxDesc |= kBoneFlags;
|
|
pMat->SetVertexDescription(VtxDesc);
|
|
}
|
|
|
|
else if (!pSkin && VtxDesc.HasAnyFlags(kBoneFlags))
|
|
{
|
|
VtxDesc &= ~kBoneFlags;
|
|
pMat->SetVertexDescription(VtxDesc);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32 CModel::GetMatSetCount()
|
|
{
|
|
return mMaterialSets.size();
|
|
}
|
|
|
|
uint32 CModel::GetMatCount()
|
|
{
|
|
if (mMaterialSets.empty()) return 0;
|
|
else return mMaterialSets[0]->NumMaterials();
|
|
}
|
|
|
|
CMaterialSet* CModel::GetMatSet(uint32 MatSet)
|
|
{
|
|
return mMaterialSets[MatSet];
|
|
}
|
|
|
|
CMaterial* CModel::GetMaterialByIndex(uint32 MatSet, uint32 Index)
|
|
{
|
|
if (MatSet >= mMaterialSets.size())
|
|
MatSet = mMaterialSets.size() - 1;
|
|
|
|
if (GetMatCount() == 0)
|
|
return nullptr;
|
|
|
|
return mMaterialSets[MatSet]->MaterialByIndex(Index, false);
|
|
}
|
|
|
|
CMaterial* CModel::GetMaterialBySurface(uint32 MatSet, uint32 Surface)
|
|
{
|
|
return GetMaterialByIndex(MatSet, mSurfaces[Surface]->MaterialID);
|
|
}
|
|
|
|
bool CModel::HasTransparency(uint32 MatSet)
|
|
{
|
|
if (MatSet >= mMaterialSets.size())
|
|
MatSet = mMaterialSets.size() - 1;
|
|
|
|
for (uint32 iMat = 0; iMat < mMaterialSets[MatSet]->NumMaterials(); iMat++)
|
|
if (mMaterialSets[MatSet]->MaterialByIndex(iMat, true)->Options() & EMaterialOption::Transparent ) return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool CModel::IsSurfaceTransparent(uint32 Surface, uint32 MatSet)
|
|
{
|
|
if (MatSet >= mMaterialSets.size())
|
|
MatSet = mMaterialSets.size() - 1;
|
|
|
|
uint32 matID = mSurfaces[Surface]->MaterialID;
|
|
return (mMaterialSets[MatSet]->MaterialByIndex(matID, true)->Options() & EMaterialOption::Transparent) != 0;
|
|
}
|
|
|
|
bool CModel::IsLightmapped() const
|
|
{
|
|
for (uint32 iSet = 0; iSet < mMaterialSets.size(); iSet++)
|
|
{
|
|
CMaterialSet *pSet = mMaterialSets[iSet];
|
|
|
|
for (uint32 iMat = 0; iMat < pSet->NumMaterials(); iMat++)
|
|
{
|
|
CMaterial *pMat = pSet->MaterialByIndex(iMat, true);
|
|
if (pMat->Options().HasFlag(EMaterialOption::Lightmap))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
CIndexBuffer* CModel::InternalGetIBO(uint32 Surface, EPrimitiveType Primitive)
|
|
{
|
|
std::vector<CIndexBuffer> *pIBOs = &mSurfaceIndexBuffers[Surface];
|
|
GLenum Type = GXPrimToGLPrim(Primitive);
|
|
|
|
for (uint32 iIBO = 0; iIBO < pIBOs->size(); iIBO++)
|
|
{
|
|
if ((*pIBOs)[iIBO].GetPrimitiveType() == Type)
|
|
return &(*pIBOs)[iIBO];
|
|
}
|
|
|
|
pIBOs->emplace_back(CIndexBuffer(Type));
|
|
return &pIBOs->back();
|
|
}
|