PrimeWorldEditor/src/Core/Resource/Model/CModel.cpp

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();
}