MD3
- shaders are now processed - multi-part player models are handled correctly Material system - added flags for 'usealpha' or 'ignorealpha' setting of textures LWO - fixed texture assignment bug due to invalid tag list 3DS - improved handling of dummy nodes Viewer: - lines&points are now displayed - improved animation control via slider (still some stuff missing) - skeleton is now displayed - some other minor fixes Validator: - some material issues are warnings now (no errors anymore) git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@349 67173fc5-114c-0410-ac8e-9d2fd5bffc1f
This commit is contained in:
@@ -438,11 +438,11 @@ void Discreet3DSImporter::AddNodeToGraph(aiScene* pcSOut,aiNode* pcOut,
|
||||
iArray.reserve(3);
|
||||
|
||||
aiMatrix4x4 abs;
|
||||
if (pcIn->mName == "$$$DUMMY") {
|
||||
/*if (pcIn->mName == "$$$DUMMY") {
|
||||
// FIX: Append the "real" name of the dummy to the string
|
||||
pcIn->mName = "Dummy." + pcIn->mDummyName;
|
||||
}
|
||||
else // if (pcIn->mName != "$$$DUMMY")
|
||||
else*/ // if (pcIn->mName != "$$$DUMMY")
|
||||
{
|
||||
// Find all meshes with the same name as the node
|
||||
for (unsigned int a = 0; a < pcSOut->mNumMeshes;++a)
|
||||
|
||||
@@ -669,14 +669,16 @@ void Discreet3DSImporter::ParseHierarchyChunk(uint16_t parent)
|
||||
|
||||
// This is the "real" name of a $$$DUMMY object
|
||||
{
|
||||
if (mCurrentNode->mName != "$$$DUMMY") {
|
||||
DefaultLogger::get()->warn("3DS: Skipping dummy object name for non-dummy object");
|
||||
const char* sz = (const char*) stream->GetPtr();
|
||||
while (stream->GetI1());
|
||||
// mCurrentNode->mDummyName = std::string(sz);
|
||||
|
||||
// FIX: if object name is DUMMY, take this one instead
|
||||
if (mCurrentNode->mName == "$$$DUMMY") {
|
||||
//DefaultLogger::get()->warn("3DS: Skipping dummy object name for non-dummy object");
|
||||
mCurrentNode->mName = std::string(sz);
|
||||
break;
|
||||
}
|
||||
|
||||
const char* sz = (const char*)stream->GetPtr();
|
||||
while (stream->GetI1());
|
||||
mCurrentNode->mDummyName = std::string(sz);
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
@@ -76,10 +76,9 @@ bool GenVertexNormalsProcess::IsActive( unsigned int pFlags) const
|
||||
// Executes the post processing step on the given imported data.
|
||||
void GenVertexNormalsProcess::SetupProperties(const Importer* pImp)
|
||||
{
|
||||
// get the current value of the property
|
||||
this->configMaxAngle = pImp->GetPropertyFloat(AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE,175.f);
|
||||
this->configMaxAngle = std::max(std::min(this->configMaxAngle,175.0f),0.0f);
|
||||
this->configMaxAngle = AI_DEG_TO_RAD(this->configMaxAngle);
|
||||
// Get the current value of the AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE property
|
||||
configMaxAngle = pImp->GetPropertyFloat(AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE,175.f);
|
||||
configMaxAngle = AI_DEG_TO_RAD(std::max(std::min(configMaxAngle,175.0f),0.0f));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@@ -98,8 +97,7 @@ void GenVertexNormalsProcess::Execute( aiScene* pScene)
|
||||
bHas = true;
|
||||
}
|
||||
|
||||
if (bHas)
|
||||
{
|
||||
if (bHas) {
|
||||
DefaultLogger::get()->info("GenVertexNormalsProcess finished. "
|
||||
"Vertex normals have been calculated");
|
||||
}
|
||||
@@ -154,8 +152,7 @@ bool GenVertexNormalsProcess::GenMeshVertexNormals (aiMesh* pMesh, unsigned int
|
||||
SpatialSort _vertexFinder;
|
||||
float posEpsilon;
|
||||
const float epsilon = 1e-5f;
|
||||
if (shared)
|
||||
{
|
||||
if (shared) {
|
||||
std::vector<std::pair<SpatialSort,float> >* avf;
|
||||
shared->GetProperty(AI_SPP_SPATIAL_SORT,avf);
|
||||
if (avf)
|
||||
@@ -165,8 +162,7 @@ bool GenVertexNormalsProcess::GenMeshVertexNormals (aiMesh* pMesh, unsigned int
|
||||
posEpsilon = blubb.second;
|
||||
}
|
||||
}
|
||||
if (!vertexFinder)
|
||||
{
|
||||
if (!vertexFinder) {
|
||||
_vertexFinder.Fill(pMesh->mVertices, pMesh->mNumVertices, sizeof( aiVector3D));
|
||||
vertexFinder = &_vertexFinder;
|
||||
posEpsilon = ComputePositionEpsilon(pMesh);
|
||||
@@ -174,22 +170,19 @@ bool GenVertexNormalsProcess::GenMeshVertexNormals (aiMesh* pMesh, unsigned int
|
||||
std::vector<unsigned int> verticesFound;
|
||||
aiVector3D* pcNew = new aiVector3D[pMesh->mNumVertices];
|
||||
|
||||
if (configMaxAngle >= AI_DEG_TO_RAD( 175.f ))
|
||||
{
|
||||
if (configMaxAngle >= AI_DEG_TO_RAD( 175.f )) {
|
||||
// There is no angle limit. Thus all vertices with positions close
|
||||
// to each other will receive the same vertex normal. This allows us
|
||||
// to optimize the whole algorithm a little bit ...
|
||||
std::vector<bool> abHad(pMesh->mNumVertices,false);
|
||||
for (unsigned int i = 0; i < pMesh->mNumVertices;++i)
|
||||
{
|
||||
for (unsigned int i = 0; i < pMesh->mNumVertices;++i) {
|
||||
if (abHad[i])continue;
|
||||
|
||||
// Get all vertices that share this one ...
|
||||
vertexFinder->FindPositions( pMesh->mVertices[i], posEpsilon, verticesFound);
|
||||
|
||||
aiVector3D pcNor;
|
||||
for (unsigned int a = 0; a < verticesFound.size(); ++a)
|
||||
{
|
||||
for (unsigned int a = 0; a < verticesFound.size(); ++a) {
|
||||
const aiVector3D& v = pMesh->mNormals[verticesFound[a]];
|
||||
if (is_not_qnan(v.x))pcNor += v;
|
||||
}
|
||||
@@ -204,17 +197,16 @@ bool GenVertexNormalsProcess::GenMeshVertexNormals (aiMesh* pMesh, unsigned int
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Slower code path if a smooth angle is set. There are many ways to achieve
|
||||
// the effect, this one is the most straightforward one.
|
||||
else {
|
||||
const float fLimit = ::cos(configMaxAngle);
|
||||
for (unsigned int i = 0; i < pMesh->mNumVertices;++i)
|
||||
{
|
||||
for (unsigned int i = 0; i < pMesh->mNumVertices;++i) {
|
||||
// Get all vertices that share this one ...
|
||||
vertexFinder->FindPositions( pMesh->mVertices[i] , posEpsilon, verticesFound);
|
||||
|
||||
aiVector3D pcNor;
|
||||
for (unsigned int a = 0; a < verticesFound.size(); ++a)
|
||||
{
|
||||
for (unsigned int a = 0; a < verticesFound.size(); ++a) {
|
||||
const aiVector3D& v = pMesh->mNormals[verticesFound[a]];
|
||||
|
||||
// check whether the angle between the two normals is not too large
|
||||
|
||||
@@ -39,7 +39,9 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
---------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/** @file Implementation of the LWO importer class */
|
||||
/** @file LWOLoader.cpp
|
||||
* @brief Implementation of the LWO importer class
|
||||
*/
|
||||
|
||||
#include "AssimpPCH.h"
|
||||
#ifndef ASSIMP_BUILD_NO_LWO_IMPORTER
|
||||
@@ -230,6 +232,7 @@ void LWOImporter::InternReadFile( const std::string& pFile,
|
||||
mSurfaces->push_back(LWO::Surface());
|
||||
LWO::Surface& surf = mSurfaces->back();
|
||||
surf.mColor.r = surf.mColor.g = surf.mColor.b = 0.6f;
|
||||
surf.mName = "LWODefaultSurface";
|
||||
}
|
||||
idx = iDefaultSurface;
|
||||
}
|
||||
@@ -329,8 +332,7 @@ void LWOImporter::InternReadFile( const std::string& pFile,
|
||||
}
|
||||
|
||||
// process normals (MODO extension)
|
||||
if (nrm)
|
||||
{
|
||||
if (nrm) {
|
||||
*nrm++ = ((aiVector3D*)&layer.mNormals.rawData[0])[idx];
|
||||
}
|
||||
|
||||
@@ -401,7 +403,7 @@ void LWOImporter::InternReadFile( const std::string& pFile,
|
||||
}
|
||||
|
||||
// copy the meshes to the output structure
|
||||
if (apcMeshes.size()) // shouldn't occur, just to be sure we don't crash
|
||||
if (apcMeshes.size()) // shouldn't happen, just to be sure we don't crash
|
||||
{
|
||||
pScene->mMeshes = new aiMesh*[ pScene->mNumMeshes = (unsigned int)apcMeshes.size() ];
|
||||
::memcpy(pScene->mMeshes,&apcMeshes[0],pScene->mNumMeshes*sizeof(void*));
|
||||
@@ -415,24 +417,23 @@ void LWOImporter::InternReadFile( const std::string& pFile,
|
||||
void LWOImporter::ComputeNormals(aiMesh* mesh, const std::vector<unsigned int>& smoothingGroups,
|
||||
const LWO::Surface& surface)
|
||||
{
|
||||
// allocate output storage
|
||||
// Allocate output storage
|
||||
mesh->mNormals = new aiVector3D[mesh->mNumVertices];
|
||||
|
||||
// First generate per-face normals
|
||||
aiVector3D* out;
|
||||
std::vector<aiVector3D> faceNormals;
|
||||
|
||||
// ... in some cases that's already enough
|
||||
if (!surface.mMaximumSmoothAngle)
|
||||
out = mesh->mNormals;
|
||||
else
|
||||
{
|
||||
else {
|
||||
faceNormals.resize(mesh->mNumVertices);
|
||||
out = &faceNormals[0];
|
||||
}
|
||||
|
||||
aiFace* begin = mesh->mFaces, *const end = mesh->mFaces+mesh->mNumFaces;
|
||||
for (; begin != end; ++begin)
|
||||
{
|
||||
for (; begin != end; ++begin) {
|
||||
aiFace& face = *begin;
|
||||
|
||||
// LWO doc: "the normal is defined as the cross product of the first and last edges"
|
||||
@@ -447,7 +448,7 @@ void LWOImporter::ComputeNormals(aiMesh* mesh, const std::vector<unsigned int>&
|
||||
if (!surface.mMaximumSmoothAngle)return;
|
||||
const float posEpsilon = ComputePositionEpsilon(mesh);
|
||||
|
||||
// now generate the spatial sort tree
|
||||
// Now generate the spatial sort tree
|
||||
SGSpatialSort sSort;
|
||||
std::vector<unsigned int>::const_iterator it = smoothingGroups.begin();
|
||||
for( begin = mesh->mFaces; begin != end; ++begin, ++it)
|
||||
@@ -459,70 +460,57 @@ void LWOImporter::ComputeNormals(aiMesh* mesh, const std::vector<unsigned int>&
|
||||
sSort.Add(mesh->mVertices[tt],tt,*it);
|
||||
}
|
||||
}
|
||||
// sort everything - this takes O(nlogn) time
|
||||
// Sort everything - this takes O(nlogn) time
|
||||
sSort.Prepare();
|
||||
std::vector<unsigned int> poResult;
|
||||
poResult.reserve(20);
|
||||
|
||||
// generate vertex normals. We have O(logn) for the binary lookup, which we need
|
||||
// Generate vertex normals. We have O(logn) for the binary lookup, which we need
|
||||
// for n elements, thus the EXPECTED complexity is O(nlogn)
|
||||
if (surface.mMaximumSmoothAngle < 3.f && !configSpeedFlag)
|
||||
{
|
||||
if (surface.mMaximumSmoothAngle < 3.f && !configSpeedFlag) {
|
||||
const float fLimit = cos(surface.mMaximumSmoothAngle);
|
||||
|
||||
for( begin = mesh->mFaces, it = smoothingGroups.begin(); begin != end; ++begin, ++it)
|
||||
{
|
||||
register unsigned int sg = *it;
|
||||
|
||||
aiFace& face = *begin;
|
||||
for( begin = mesh->mFaces, it = smoothingGroups.begin(); begin != end; ++begin, ++it) {
|
||||
const aiFace& face = *begin;
|
||||
unsigned int* beginIdx = face.mIndices, *const endIdx = face.mIndices+face.mNumIndices;
|
||||
for (; beginIdx != endIdx; ++beginIdx)
|
||||
{
|
||||
register unsigned int idx = *beginIdx;
|
||||
sSort.FindPositions(mesh->mVertices[idx],sg,posEpsilon,poResult,true);
|
||||
|
||||
sSort.FindPositions(mesh->mVertices[idx],*it,posEpsilon,poResult,true);
|
||||
std::vector<unsigned int>::const_iterator a, end = poResult.end();
|
||||
|
||||
aiVector3D vNormals;
|
||||
for (a = poResult.begin();a != end;++a)
|
||||
{
|
||||
for (a = poResult.begin();a != end;++a) {
|
||||
const aiVector3D& v = faceNormals[*a];
|
||||
if (v * faceNormals[idx] < fLimit)continue;
|
||||
if (v * faceNormals[idx] < fLimit)
|
||||
continue;
|
||||
vNormals += v;
|
||||
}
|
||||
vNormals.Normalize();
|
||||
mesh->mNormals[idx] = vNormals;
|
||||
mesh->mNormals[idx] = vNormals.Normalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
else // faster code path in case there is no smooth angle
|
||||
{
|
||||
// faster code path in case there is no smooth angle
|
||||
else {
|
||||
std::vector<bool> vertexDone(mesh->mNumVertices,false);
|
||||
|
||||
for( begin = mesh->mFaces, it = smoothingGroups.begin(); begin != end; ++begin, ++it)
|
||||
{
|
||||
register unsigned int sg = *it;
|
||||
|
||||
aiFace& face = *begin;
|
||||
for( begin = mesh->mFaces, it = smoothingGroups.begin(); begin != end; ++begin, ++it) {
|
||||
const aiFace& face = *begin;
|
||||
unsigned int* beginIdx = face.mIndices, *const endIdx = face.mIndices+face.mNumIndices;
|
||||
for (; beginIdx != endIdx; ++beginIdx)
|
||||
{
|
||||
register unsigned int idx = *beginIdx;
|
||||
|
||||
if (vertexDone[idx])continue;
|
||||
sSort.FindPositions(mesh->mVertices[idx],sg,posEpsilon,poResult,true);
|
||||
|
||||
if (vertexDone[idx])
|
||||
continue;
|
||||
sSort.FindPositions(mesh->mVertices[idx],*it,posEpsilon,poResult,true);
|
||||
std::vector<unsigned int>::const_iterator a, end = poResult.end();
|
||||
|
||||
aiVector3D vNormals;
|
||||
for (a = poResult.begin();a != end;++a)
|
||||
{
|
||||
for (a = poResult.begin();a != end;++a) {
|
||||
const aiVector3D& v = faceNormals[*a];
|
||||
vNormals += v;
|
||||
}
|
||||
vNormals.Normalize();
|
||||
for (a = poResult.begin();a != end;++a)
|
||||
{
|
||||
for (a = poResult.begin();a != end;++a) {
|
||||
mesh->mNormals[*a] = vNormals;
|
||||
vertexDone[*a] = true;
|
||||
}
|
||||
@@ -617,9 +605,9 @@ void LWOImporter::ResolveTags()
|
||||
mMapping->resize(mTags->size(),0xffffffff);
|
||||
for (unsigned int a = 0; a < mTags->size();++a)
|
||||
{
|
||||
const std::string& c = (*mTags)[a];
|
||||
for (unsigned int i = 0; i < mSurfaces->size();++i)
|
||||
{
|
||||
const std::string& c = (*mTags)[a];
|
||||
const std::string& d = (*mSurfaces)[i].mName;
|
||||
if (!ASSIMP_stricmp(c,d))
|
||||
{
|
||||
@@ -688,9 +676,11 @@ void LWOImporter::LoadLWOTags(unsigned int size)
|
||||
{
|
||||
if (!(*szCur))
|
||||
{
|
||||
const unsigned int len = (unsigned int)(szCur-szLast);
|
||||
mTags->push_back(std::string(szLast,len));
|
||||
szCur += len & 1;
|
||||
const size_t len = (size_t)(szCur-szLast);
|
||||
// FIX: skip empty-sized tags
|
||||
if (len)
|
||||
mTags->push_back(std::string(szLast,len));
|
||||
szCur += (len&0x1 ? 1 : 2);
|
||||
szLast = szCur;
|
||||
}
|
||||
szCur++;
|
||||
@@ -881,7 +871,7 @@ inline void CreateNewEntry(std::vector< T >& list, unsigned int srcIdx)
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void LWOImporter::DoRecursiveVMAPAssignment(VMapEntry* base, unsigned int numRead,
|
||||
inline void LWOImporter::DoRecursiveVMAPAssignment(VMapEntry* base, unsigned int numRead,
|
||||
unsigned int idx, float* data)
|
||||
{
|
||||
ai_assert(NULL != data);
|
||||
@@ -897,7 +887,7 @@ void LWOImporter::DoRecursiveVMAPAssignment(VMapEntry* base, unsigned int numRea
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void AddToSingleLinkedList(ReferrerList& refList, unsigned int srcIdx, unsigned int destIdx)
|
||||
inline void AddToSingleLinkedList(ReferrerList& refList, unsigned int srcIdx, unsigned int destIdx)
|
||||
{
|
||||
if(0xffffffff == refList[srcIdx])
|
||||
{
|
||||
|
||||
@@ -455,7 +455,7 @@ inline void LWOImporter::GetS0(std::string& out,unsigned int max)
|
||||
}
|
||||
++mFileBuffer;
|
||||
}
|
||||
unsigned int len = (unsigned int) ((const char*)mFileBuffer-sz);
|
||||
size_t len = (size_t) ((const char*)mFileBuffer-sz);
|
||||
out = std::string(sz,len);
|
||||
mFileBuffer += (len&0x1 ? 1 : 2);
|
||||
}
|
||||
|
||||
@@ -663,8 +663,7 @@ void LWOImporter::LoadLWO2Surface(unsigned int size)
|
||||
for (SurfaceList::iterator it = mSurfaces->begin(), end = mSurfaces->end()-1;
|
||||
it != end; ++it)
|
||||
{
|
||||
if ((*it).mName == derived)
|
||||
{
|
||||
if ((*it).mName == derived) {
|
||||
// we have it ...
|
||||
surf = *it;
|
||||
derived.clear();
|
||||
|
||||
@@ -60,13 +60,36 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
using namespace Assimp;
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Convert a Q3 shader blend function to the appropriate enum value
|
||||
Q3Shader::BlendFunc StringToBlendFunc(const std::string& m)
|
||||
{
|
||||
if (m == "GL_ONE") {
|
||||
return Q3Shader::BLEND_GL_ONE;
|
||||
}
|
||||
if (m == "GL_ZERO") {
|
||||
return Q3Shader::BLEND_GL_ZERO;
|
||||
}
|
||||
if (m == "GL_SRC_ALPHA") {
|
||||
return Q3Shader::BLEND_GL_SRC_ALPHA;
|
||||
}
|
||||
if (m == "GL_ONE_MINUS_SRC_ALPHA") {
|
||||
return Q3Shader::BLEND_GL_ONE_MINUS_SRC_ALPHA;
|
||||
}
|
||||
if (m == "GL_ONE_MINUS_DST_COLOR") {
|
||||
return Q3Shader::BLEND_GL_ONE_MINUS_DST_COLOR;
|
||||
}
|
||||
DefaultLogger::get()->error("Q3Shader: Unknown blend function: " + m);
|
||||
return Q3Shader::BLEND_NONE;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Load a Quake 3 shader
|
||||
void Q3Shader::LoadShader(ShaderData& fill, const std::string& pFile,IOSystem* io)
|
||||
bool Q3Shader::LoadShader(ShaderData& fill, const std::string& pFile,IOSystem* io)
|
||||
{
|
||||
boost::scoped_ptr<IOStream> file( io->Open( pFile, "rt"));
|
||||
if (!file.get())
|
||||
return; // if we can't access the file, don't worry and return
|
||||
return false; // if we can't access the file, don't worry and return
|
||||
|
||||
DefaultLogger::get()->info("Loading Quake3 shader file " + pFile);
|
||||
|
||||
@@ -84,59 +107,93 @@ void Q3Shader::LoadShader(ShaderData& fill, const std::string& pFile,IOSystem* i
|
||||
Q3Shader::ShaderMapBlock* curMap = NULL;
|
||||
|
||||
// read line per line
|
||||
for (;;SkipLine(&buff)) {
|
||||
for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) {
|
||||
|
||||
if(!SkipSpacesAndLineEnd(&buff))
|
||||
break;
|
||||
|
||||
if (*buff == '{') {
|
||||
++buff;
|
||||
|
||||
// append to last section, if any
|
||||
if (!curData) {
|
||||
DefaultLogger::get()->error("Q3Shader: Unexpected shader section token \'{\'");
|
||||
return;
|
||||
return true; // still no failure, the file is there
|
||||
}
|
||||
|
||||
// read this map section
|
||||
for (;;SkipLine(&buff)) {
|
||||
if(!SkipSpacesAndLineEnd(&buff))
|
||||
break;
|
||||
|
||||
// read this data section
|
||||
for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) {
|
||||
if (*buff == '{') {
|
||||
++buff;
|
||||
// add new map section
|
||||
curData->maps.push_back(Q3Shader::ShaderMapBlock());
|
||||
curMap = &curData->maps.back();
|
||||
|
||||
for (;SkipSpacesAndLineEnd(&buff);SkipLine(&buff)) {
|
||||
// 'map' - Specifies texture file name
|
||||
if (TokenMatchI(buff,"map",3) || TokenMatchI(buff,"clampmap",8)) {
|
||||
curMap->name = GetNextToken(buff);
|
||||
}
|
||||
// 'blendfunc' - Alpha blending mode
|
||||
else if (TokenMatchI(buff,"blendfunc",9)) {
|
||||
const std::string blend_src = GetNextToken(buff);
|
||||
if (blend_src == "add") {
|
||||
curMap->blend_src = Q3Shader::BLEND_GL_ONE;
|
||||
curMap->blend_dest = Q3Shader::BLEND_GL_ONE;
|
||||
}
|
||||
else if (blend_src == "filter") {
|
||||
curMap->blend_src = Q3Shader::BLEND_GL_DST_COLOR;
|
||||
curMap->blend_dest = Q3Shader::BLEND_GL_ZERO;
|
||||
}
|
||||
else if (blend_src == "blend") {
|
||||
curMap->blend_src = Q3Shader::BLEND_GL_SRC_ALPHA;
|
||||
curMap->blend_dest = Q3Shader::BLEND_GL_ONE_MINUS_SRC_ALPHA;
|
||||
}
|
||||
else {
|
||||
curMap->blend_src = StringToBlendFunc(blend_src);
|
||||
curMap->blend_dest = StringToBlendFunc(GetNextToken(buff));
|
||||
}
|
||||
}
|
||||
// 'alphafunc' - Alpha testing mode
|
||||
else if (TokenMatchI(buff,"alphafunc",9)) {
|
||||
const std::string at = GetNextToken(buff);
|
||||
if (at == "GT0") {
|
||||
curMap->alpha_test = Q3Shader::AT_GT0;
|
||||
}
|
||||
else if (at == "LT128") {
|
||||
curMap->alpha_test = Q3Shader::AT_LT128;
|
||||
}
|
||||
else if (at == "GE128") {
|
||||
curMap->alpha_test = Q3Shader::AT_GE128;
|
||||
}
|
||||
}
|
||||
else if (*buff == '}') {
|
||||
++buff;
|
||||
// close this map section
|
||||
curMap = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else if (*buff == '}') {
|
||||
// close this map section
|
||||
if (curMap)
|
||||
curMap = NULL;
|
||||
else {
|
||||
curData = NULL;
|
||||
break;
|
||||
}
|
||||
++buff;
|
||||
curData = NULL;
|
||||
break;
|
||||
}
|
||||
// 'map' - Specifies texture file name
|
||||
else if (TokenMatchI(buff,"map",3) || TokenMatchI(buff,"clampmap",8)) {
|
||||
curMap->name = GetNextToken(buff);
|
||||
}
|
||||
// 'blendfunc' - Alpha blending mode
|
||||
else if (TokenMatchI(buff,"blendfunc",9)) {
|
||||
// fixme
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 'cull' specifies culling behaviour for the model
|
||||
else if (TokenMatch(buff,"cull",4)) {
|
||||
SkipSpaces(&buff);
|
||||
if (!ASSIMP_strincmp(buff,"back",4)) {
|
||||
curData->cull = Q3Shader::CULL_CCW;
|
||||
// 'cull' specifies culling behaviour for the model
|
||||
else if (TokenMatchI(buff,"cull",4)) {
|
||||
SkipSpaces(&buff);
|
||||
if (!ASSIMP_strincmp(buff,"back",4)) {
|
||||
curData->cull = Q3Shader::CULL_CCW;
|
||||
}
|
||||
else if (!ASSIMP_strincmp(buff,"front",5)) {
|
||||
curData->cull = Q3Shader::CULL_CW;
|
||||
}
|
||||
else if (!ASSIMP_strincmp(buff,"none",4) || !ASSIMP_strincmp(buff,"disable",7)) {
|
||||
curData->cull = Q3Shader::CULL_NONE;
|
||||
}
|
||||
else DefaultLogger::get()->error("Q3Shader: Unrecognized cull mode");
|
||||
}
|
||||
}
|
||||
else if (!ASSIMP_strincmp(buff,"front",5)) {
|
||||
curData->cull = Q3Shader::CULL_CW;
|
||||
}
|
||||
//else curData->cull = Q3Shader::CULL_NONE;
|
||||
}
|
||||
|
||||
else {
|
||||
@@ -148,15 +205,16 @@ void Q3Shader::LoadShader(ShaderData& fill, const std::string& pFile,IOSystem* i
|
||||
curData->name = GetNextToken(buff);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Load a Quake 3 skin
|
||||
void Q3Shader::LoadSkin(SkinData& fill, const std::string& pFile,IOSystem* io)
|
||||
bool Q3Shader::LoadSkin(SkinData& fill, const std::string& pFile,IOSystem* io)
|
||||
{
|
||||
boost::scoped_ptr<IOStream> file( io->Open( pFile, "rt"));
|
||||
if (!file.get())
|
||||
return; // if we can't access the file, don't worry and return
|
||||
return false; // if we can't access the file, don't worry and return
|
||||
|
||||
DefaultLogger::get()->info("Loading Quake3 skin file " + pFile);
|
||||
|
||||
@@ -177,7 +235,7 @@ void Q3Shader::LoadSkin(SkinData& fill, const std::string& pFile,IOSystem* io)
|
||||
std::string ss = GetNextToken(buff);
|
||||
|
||||
// ignore tokens starting with tag_
|
||||
if (!::strncmp(&ss[0],"_tag",std::min((size_t)4, ss.length())))
|
||||
if (!::strncmp(&ss[0],"tag_",std::min((size_t)4, ss.length())))
|
||||
continue;
|
||||
|
||||
fill.textures.push_back(SkinData::TextureEntry());
|
||||
@@ -186,6 +244,90 @@ void Q3Shader::LoadSkin(SkinData& fill, const std::string& pFile,IOSystem* io)
|
||||
s.first = ss;
|
||||
s.second = GetNextToken(buff);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Convert Q3Shader to material
|
||||
void Q3Shader::ConvertShaderToMaterial(MaterialHelper* out, const ShaderDataBlock& shader)
|
||||
{
|
||||
ai_assert(NULL != out);
|
||||
|
||||
/* IMPORTANT: This is not a real conversion. Actually we're just guessing and
|
||||
* hacking around to build an aiMaterial that looks nearly equal to the
|
||||
* original Quake 3 shader. We're missing some important features like
|
||||
* animatable material properties in our material system, but at least
|
||||
* multiple textures should be handled correctly.
|
||||
*/
|
||||
|
||||
// Two-sided material?
|
||||
if (shader.cull == Q3Shader::CULL_NONE) {
|
||||
const int twosided = 1;
|
||||
out->AddProperty(&twosided,1,AI_MATKEY_TWOSIDED);
|
||||
}
|
||||
|
||||
unsigned int cur_emissive = 0, cur_diffuse = 0, cur_lm =0;
|
||||
|
||||
// Iterate through all textures
|
||||
for (std::list< Q3Shader::ShaderMapBlock >::const_iterator it = shader.maps.begin(); it != shader.maps.end();++it) {
|
||||
|
||||
// CONVERSION BEHAVIOUR:
|
||||
//
|
||||
//
|
||||
// If the texture is additive
|
||||
// - if it is the first texture, assume additive blending for the whole material
|
||||
// - otherwise register it as emissive texture.
|
||||
//
|
||||
// If the texture is using standard blend (or if the blend mode is unknown)
|
||||
// - if first texture: assume default blending for material
|
||||
// - in any case: set it as diffuse texture
|
||||
//
|
||||
// If the texture is using 'filter' blending
|
||||
// - take as lightmap
|
||||
//
|
||||
// Textures with alpha funcs
|
||||
// - aiTextureFlags_UseAlpha is set (otherwise aiTextureFlags_NoAlpha is explicitly set)
|
||||
aiString s((*it).name);
|
||||
aiTextureType type; unsigned int index;
|
||||
|
||||
if ((*it).blend_src == Q3Shader::BLEND_GL_ONE && (*it).blend_dest == Q3Shader::BLEND_GL_ONE) {
|
||||
if (it == shader.maps.begin()) {
|
||||
const int additive = aiBlendMode_Additive;
|
||||
out->AddProperty(&additive,1,AI_MATKEY_BLEND_FUNC);
|
||||
|
||||
index = cur_diffuse++;
|
||||
type = aiTextureType_DIFFUSE;
|
||||
}
|
||||
else {
|
||||
index = cur_emissive++;
|
||||
type = aiTextureType_EMISSIVE;
|
||||
}
|
||||
}
|
||||
else if ((*it).blend_src == Q3Shader::BLEND_GL_DST_COLOR && Q3Shader::BLEND_GL_ZERO) {
|
||||
index = cur_lm++;
|
||||
type = aiTextureType_LIGHTMAP;
|
||||
}
|
||||
else {
|
||||
const int blend = aiBlendMode_Default;
|
||||
out->AddProperty(&blend,1,AI_MATKEY_BLEND_FUNC);
|
||||
|
||||
index = cur_diffuse++;
|
||||
type = aiTextureType_DIFFUSE;
|
||||
}
|
||||
|
||||
// setup texture
|
||||
out->AddProperty(&s,AI_MATKEY_TEXTURE(type,index));
|
||||
|
||||
// setup texture flags
|
||||
const int use_alpha = ((*it).alpha_test != Q3Shader::AT_NONE ? aiTextureFlags_UseAlpha : aiTextureFlags_IgnoreAlpha);
|
||||
out->AddProperty(&use_alpha,1,AI_MATKEY_TEXFLAGS(type,index));
|
||||
}
|
||||
// If at least one emissive texture was set, set the emissive base color to 1 to ensure
|
||||
// the texture is actually displayed.
|
||||
if (0 != cur_emissive) {
|
||||
aiColor3D one(1.f,1.f,1.f);
|
||||
out->AddProperty(&one,1,AI_MATKEY_COLOR_EMISSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
@@ -291,11 +433,14 @@ void MD3Importer::SetupProperties(const Importer* pImp)
|
||||
|
||||
// AI_CONFIG_IMPORT_MD3_SKIN_NAME
|
||||
configSkinFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SKIN_NAME,"default"));
|
||||
|
||||
// AI_CONFIG_IMPORT_MD3_SHADER_SRC
|
||||
configShaderFile = (pImp->GetPropertyString(AI_CONFIG_IMPORT_MD3_SHADER_SRC,""));
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Try to read the skin for a MD3 file
|
||||
void MD3Importer::ReadSkin(Q3Shader::SkinData& fill)
|
||||
void MD3Importer::ReadSkin(Q3Shader::SkinData& fill) const
|
||||
{
|
||||
// skip any postfixes (e.g. lower_1.md3)
|
||||
std::string::size_type s = filename.find_last_of('_');
|
||||
@@ -308,6 +453,39 @@ void MD3Importer::ReadSkin(Q3Shader::SkinData& fill)
|
||||
Q3Shader::LoadSkin(fill,skin_file,mIOHandler);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Try to read the shader for a MD3 file
|
||||
void MD3Importer::ReadShader(Q3Shader::ShaderData& fill) const
|
||||
{
|
||||
// Determine Q3 model name from given path
|
||||
std::string::size_type s = path.find_last_of('\\',path.length()-2);
|
||||
if (s == std::string::npos)
|
||||
s = path.find_last_of('/',path.length()-2);
|
||||
|
||||
const std::string model_file = path.substr(s+1,path.length()-(s+2));
|
||||
|
||||
// If no specific dir or file is given, use our default search behaviour
|
||||
if (!configShaderFile.length()) {
|
||||
if(!Q3Shader::LoadShader(fill,path + "..\\..\\..\\scripts\\" + model_file + ".shader",mIOHandler)) {
|
||||
Q3Shader::LoadShader(fill,path + "..\\..\\..\\scripts\\" + filename + ".shader",mIOHandler);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// If the given string specifies a file, load this file.
|
||||
// Otherwise it's a directory.
|
||||
std::string::size_type st = configShaderFile.find_last_of('.');
|
||||
if (st == std::string::npos) {
|
||||
|
||||
if(!Q3Shader::LoadShader(fill,configShaderFile + model_file + ".shader",mIOHandler)) {
|
||||
Q3Shader::LoadShader(fill,configShaderFile + filename + ".shader",mIOHandler);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Q3Shader::LoadShader(fill,configShaderFile,mIOHandler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Read a multi-part Q3 player model
|
||||
bool MD3Importer::ReadMultipartFile()
|
||||
@@ -416,6 +594,45 @@ error_cleanup:
|
||||
return false;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Convert a MD3 path to a proper value
|
||||
void MD3Importer::ConvertPath(const char* texture_name, const char* header_name, std::string& out) const
|
||||
{
|
||||
// If the MD3's internal path itself and the given path are using
|
||||
// the same directory, remove it completely to get right output paths.
|
||||
const char* end1 = ::strrchr(header_name,'\\');
|
||||
if (!end1)end1 = ::strrchr(header_name,'/');
|
||||
|
||||
const char* end2 = ::strrchr(texture_name,'\\');
|
||||
if (!end2)end2 = ::strrchr(texture_name,'/');
|
||||
|
||||
// HACK: If the paths starts with "models", ignore the
|
||||
// next two hierarchy levels, it specifies just the model name.
|
||||
// Ignored by Q3, it might be not equal to the real model location.
|
||||
if (end2) {
|
||||
|
||||
size_t len2;
|
||||
const size_t len1 = (size_t)(end1 - header_name);
|
||||
if (!ASSIMP_strincmp(texture_name,"models",6) && (texture_name[6] == '/' || texture_name[6] == '\\')) {
|
||||
len2 = 6; // ignore the seventh - could be slash or backslash
|
||||
|
||||
if (!header_name[0]) {
|
||||
// Use the file name only
|
||||
out = end2+1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else len2 = std::min (len1, (size_t)(end2 - texture_name ));
|
||||
if (!ASSIMP_strincmp(texture_name,header_name,len2)) {
|
||||
// Use the file name only
|
||||
out = end2+1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Use the full path
|
||||
out = texture_name;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Imports the given file into the given scene structure.
|
||||
void MD3Importer::InternReadFile( const std::string& pFile,
|
||||
@@ -504,12 +721,27 @@ void MD3Importer::InternReadFile( const std::string& pFile,
|
||||
Q3Shader::SkinData skins;
|
||||
ReadSkin(skins);
|
||||
|
||||
// And check whether we can locate a shader file for this model
|
||||
Q3Shader::ShaderData shaders;
|
||||
ReadShader(shaders);
|
||||
|
||||
// Adjust all texture paths in the shader
|
||||
const char* header_name = pcHeader->NAME;
|
||||
if (shaders.blocks.size()) {
|
||||
for (std::list< Q3Shader::ShaderDataBlock >::iterator dit = shaders.blocks.begin(); dit != shaders.blocks.end(); ++dit) {
|
||||
ConvertPath((*dit).name.c_str(),header_name,(*dit).name);
|
||||
|
||||
for (std::list< Q3Shader::ShaderMapBlock >::iterator mit = (*dit).maps.begin(); mit != (*dit).maps.end(); ++mit) {
|
||||
ConvertPath((*mit).name.c_str(),header_name,(*mit).name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read all surfaces from the file
|
||||
unsigned int iNum = pcHeader->NUM_SURFACES;
|
||||
unsigned int iNumMaterials = 0;
|
||||
unsigned int iDefaultMatIndex = 0xFFFFFFFF;
|
||||
while (iNum-- > 0)
|
||||
{
|
||||
while (iNum-- > 0) {
|
||||
|
||||
// Ensure correct endianess
|
||||
#ifdef AI_BUILD_BIG_ENDIAN
|
||||
@@ -555,7 +787,96 @@ void MD3Importer::InternReadFile( const std::string& pFile,
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure correct endianess
|
||||
// Allocate output mesh
|
||||
pScene->mMeshes[iNum] = new aiMesh();
|
||||
aiMesh* pcMesh = pScene->mMeshes[iNum];
|
||||
|
||||
std::string _texture_name;
|
||||
const char* texture_name = NULL;
|
||||
|
||||
// Check whether we have a texture record for this surface in the .skin file
|
||||
std::list< Q3Shader::SkinData::TextureEntry >::iterator it = std::find(
|
||||
skins.textures.begin(), skins.textures.end(), pcSurfaces->NAME );
|
||||
|
||||
if (it != skins.textures.end()) {
|
||||
texture_name = &*( _texture_name = (*it).second).begin();
|
||||
DefaultLogger::get()->debug("MD3: Assigning skin texture " + (*it).second + " to surface " + pcSurfaces->NAME);
|
||||
(*it).resolved = true; // mark entry as resolved
|
||||
}
|
||||
|
||||
// Get the first shader (= texture?) assigned to the surface
|
||||
if (!texture_name && pcSurfaces->NUM_SHADER) {
|
||||
texture_name = pcShaders->NAME;
|
||||
}
|
||||
|
||||
std::string convertedPath;
|
||||
if (texture_name) {
|
||||
ConvertPath(texture_name,header_name,convertedPath);
|
||||
}
|
||||
|
||||
const Q3Shader::ShaderDataBlock* shader = NULL;
|
||||
|
||||
// Now search the current shader for a record with this name (
|
||||
// excluding texture file extension)
|
||||
if (shaders.blocks.size()) {
|
||||
|
||||
std::string::size_type s = convertedPath.find_last_of('.');
|
||||
if (s == std::string::npos)
|
||||
s = convertedPath.length();
|
||||
|
||||
const std::string without_ext = convertedPath.substr(0,s);
|
||||
std::list< Q3Shader::ShaderDataBlock >::const_iterator dit = std::find(shaders.blocks.begin(),shaders.blocks.end(),without_ext);
|
||||
if (dit != shaders.blocks.end()) {
|
||||
// Hurra, wir haben einen. Tolle Sache.
|
||||
shader = &*dit;
|
||||
DefaultLogger::get()->info("Found shader record for " +without_ext );
|
||||
}
|
||||
else DefaultLogger::get()->warn("Unable to find shader record for " +without_ext );
|
||||
}
|
||||
|
||||
MaterialHelper* pcHelper = new MaterialHelper();
|
||||
|
||||
const int iMode = (int)aiShadingMode_Gouraud;
|
||||
pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
|
||||
|
||||
// Add a small ambient color value - Quake 3 seems to have one
|
||||
aiColor3D clr;
|
||||
clr.b = clr.g = clr.r = 0.05f;
|
||||
pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT);
|
||||
|
||||
clr.b = clr.g = clr.r = 1.0f;
|
||||
pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE);
|
||||
pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR);
|
||||
|
||||
// use surface name + skin_name as material name
|
||||
aiString name;
|
||||
name.Set("MD3_[" + configSkinFile + "][" + pcSurfaces->NAME + "]");
|
||||
pcHelper->AddProperty(&name,AI_MATKEY_NAME);
|
||||
|
||||
if (!shader) {
|
||||
// Setup dummy texture file name to ensure UV coordinates are kept during postprocessing
|
||||
aiString szString;
|
||||
if (convertedPath.length()) {
|
||||
szString.Set(convertedPath);
|
||||
}
|
||||
else {
|
||||
DefaultLogger::get()->warn("Texture file name has zero length. Using default name");
|
||||
szString.Set("dummy_texture.bmp");
|
||||
}
|
||||
pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0));
|
||||
|
||||
// prevent transparency by default
|
||||
int no_alpha = aiTextureFlags_IgnoreAlpha;
|
||||
pcHelper->AddProperty(&no_alpha,1,AI_MATKEY_TEXFLAGS_DIFFUSE(0));
|
||||
}
|
||||
else {
|
||||
Q3Shader::ConvertShaderToMaterial(pcHelper,*shader);
|
||||
}
|
||||
|
||||
pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper;
|
||||
pcMesh->mMaterialIndex = iNumMaterials++;
|
||||
|
||||
// Ensure correct endianess
|
||||
#ifdef AI_BUILD_BIG_ENDIAN
|
||||
|
||||
for (uint32_t i = 0; i < pcSurfaces->NUM_VERTICES;++i)
|
||||
@@ -577,9 +898,7 @@ void MD3Importer::InternReadFile( const std::string& pFile,
|
||||
|
||||
#endif
|
||||
|
||||
// Allocate the output mesh
|
||||
pScene->mMeshes[iNum] = new aiMesh();
|
||||
aiMesh* pcMesh = pScene->mMeshes[iNum];
|
||||
// Fill mesh information
|
||||
pcMesh->mPrimitiveTypes = aiPrimitiveType_TRIANGLE;
|
||||
|
||||
pcMesh->mNumVertices = pcSurfaces->NUM_TRIANGLES*3;
|
||||
@@ -600,6 +919,8 @@ void MD3Importer::InternReadFile( const std::string& pFile,
|
||||
unsigned int iTemp = iCurrent;
|
||||
for (unsigned int c = 0; c < 3;++c,++iCurrent)
|
||||
{
|
||||
pcMesh->mFaces[i].mIndices[c] = iCurrent;
|
||||
|
||||
// Read vertices
|
||||
pcMesh->mVertices[iCurrent].x = pcVertices[ pcTriangles->INDEXES[c]].X*AI_MD3_XYZ_SCALE;
|
||||
pcMesh->mVertices[iCurrent].y = pcVertices[ pcTriangles->INDEXES[c]].Y*AI_MD3_XYZ_SCALE;
|
||||
@@ -613,100 +934,14 @@ void MD3Importer::InternReadFile( const std::string& pFile,
|
||||
pcMesh->mTextureCoords[0][iCurrent].x = pcUVs[ pcTriangles->INDEXES[c]].U;
|
||||
pcMesh->mTextureCoords[0][iCurrent].y = 1.0f-pcUVs[ pcTriangles->INDEXES[c]].V;
|
||||
}
|
||||
// FIX: flip the face ordering for use with OpenGL
|
||||
pcMesh->mFaces[i].mIndices[0] = iTemp+2;
|
||||
pcMesh->mFaces[i].mIndices[1] = iTemp+1;
|
||||
pcMesh->mFaces[i].mIndices[2] = iTemp+0;
|
||||
// Flip face order if necessary
|
||||
if (!shader || shader->cull == Q3Shader::CULL_CCW) {
|
||||
pcMesh->mFaces[i].mIndices[0] = iTemp+2;
|
||||
pcMesh->mFaces[i].mIndices[1] = iTemp+1;
|
||||
pcMesh->mFaces[i].mIndices[2] = iTemp+0;
|
||||
}
|
||||
pcTriangles++;
|
||||
}
|
||||
|
||||
std::string _texture_name;
|
||||
const char* texture_name = NULL, *header_name = pcHeader->NAME;
|
||||
|
||||
// Check whether we have a texture record for this surface in the .skin file
|
||||
std::list< Q3Shader::SkinData::TextureEntry >::iterator it = std::find(
|
||||
skins.textures.begin(), skins.textures.end(), pcSurfaces->NAME );
|
||||
|
||||
if (it != skins.textures.end()) {
|
||||
texture_name = &*( _texture_name = (*it).second).begin();
|
||||
DefaultLogger::get()->debug("MD3: Assigning skin texture " + (*it).second + " to surface " + pcSurfaces->NAME);
|
||||
(*it).resolved = true; // mark entry as resolved
|
||||
}
|
||||
|
||||
// Get the first shader (= texture?) assigned to the surface
|
||||
if (!texture_name && pcSurfaces->NUM_SHADER) {
|
||||
texture_name = pcShaders->NAME;
|
||||
}
|
||||
|
||||
const char* end2 = NULL;
|
||||
if (texture_name) {
|
||||
|
||||
// If the MD3's internal path itself and the given path are using
|
||||
// the same directory, remove it completely to get right output paths.
|
||||
const char* end1 = ::strrchr(header_name,'\\');
|
||||
if (!end1)end1 = ::strrchr(header_name,'/');
|
||||
|
||||
end2 = ::strrchr(texture_name,'\\');
|
||||
if (!end2)end2 = ::strrchr(texture_name,'/');
|
||||
|
||||
// HACK: If the paths starts with "models/players", ignore the
|
||||
// next hierarchy level, it specifies just the model name.
|
||||
// Ignored by Q3, it might be not equal to the real model location.
|
||||
if (end1 && end2) {
|
||||
|
||||
size_t len2;
|
||||
const size_t len1 = (size_t)(end1 - header_name);
|
||||
if (!ASSIMP_strincmp(header_name,"models/players/",15)) {
|
||||
len2 = 15;
|
||||
}
|
||||
else len2 = std::min (len1, (size_t)(end2 - texture_name ));
|
||||
|
||||
if (!ASSIMP_strincmp(texture_name,header_name,len2)) {
|
||||
// Use the file name only
|
||||
end2++;
|
||||
}
|
||||
else {
|
||||
// Use the full path
|
||||
end2 = (const char*)texture_name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MaterialHelper* pcHelper = new MaterialHelper();
|
||||
|
||||
// Setup dummy texture file name to ensure UV coordinates are kept during postprocessing
|
||||
aiString szString;
|
||||
if (end2 && end2[0]) {
|
||||
const size_t iLen = ::strlen(end2);
|
||||
::memcpy(szString.data,end2,iLen);
|
||||
szString.data[iLen] = '\0';
|
||||
szString.length = iLen;
|
||||
}
|
||||
else {
|
||||
DefaultLogger::get()->warn("Texture file name has zero length. Using default name");
|
||||
szString.Set("dummy_texture.bmp");
|
||||
}
|
||||
pcHelper->AddProperty(&szString,AI_MATKEY_TEXTURE_DIFFUSE(0));
|
||||
|
||||
const int iMode = (int)aiShadingMode_Gouraud;
|
||||
pcHelper->AddProperty<int>(&iMode, 1, AI_MATKEY_SHADING_MODEL);
|
||||
|
||||
// Add a small ambient color value - Quake 3 seems to have one
|
||||
aiColor3D clr;
|
||||
clr.b = clr.g = clr.r = 0.05f;
|
||||
pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT);
|
||||
|
||||
clr.b = clr.g = clr.r = 1.0f;
|
||||
pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_DIFFUSE);
|
||||
pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_SPECULAR);
|
||||
|
||||
// use surface name + skin_name as material name
|
||||
aiString name;
|
||||
name.Set("MD3_[" + configSkinFile + "][" + pcSurfaces->NAME + "]");
|
||||
pcHelper->AddProperty(&name,AI_MATKEY_NAME);
|
||||
|
||||
pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper;
|
||||
pcMesh->mMaterialIndex = iNumMaterials++;
|
||||
|
||||
// Go to the next surface
|
||||
pcSurfaces = (BE_NCONST MD3::Surface*)(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_END);
|
||||
|
||||
@@ -131,6 +131,12 @@ struct ShaderMapBlock
|
||||
//! Blend and alpha test settings for texture
|
||||
BlendFunc blend_src,blend_dest;
|
||||
AlphaTestFunc alpha_test;
|
||||
|
||||
|
||||
//! For std::find()
|
||||
bool operator== (const std::string& o) const {
|
||||
return !ASSIMP_stricmp(o,name);
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -150,6 +156,12 @@ struct ShaderDataBlock
|
||||
|
||||
//! Maps defined in the shader
|
||||
std::list<ShaderMapBlock> maps;
|
||||
|
||||
|
||||
//! For std::find()
|
||||
bool operator== (const std::string& o) const {
|
||||
return !ASSIMP_stricmp(o,name);
|
||||
}
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -168,8 +180,18 @@ struct ShaderData
|
||||
* @param fill Receives output data
|
||||
* @param file File to be read.
|
||||
* @param io IOSystem to be used for reading
|
||||
* @return false if file is not accessible
|
||||
*/
|
||||
void LoadShader(ShaderData& fill, const std::string& file,IOSystem* io);
|
||||
bool LoadShader(ShaderData& fill, const std::string& file,IOSystem* io);
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** @brief Convert a Q3Shader to an aiMaterial
|
||||
*
|
||||
* @param[out] out Material structure to be filled.
|
||||
* @param[in] shader Input shader
|
||||
*/
|
||||
void ConvertShaderToMaterial(MaterialHelper* out, const ShaderDataBlock& shader);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** @brief Load a skin file
|
||||
@@ -178,8 +200,9 @@ void LoadShader(ShaderData& fill, const std::string& file,IOSystem* io);
|
||||
* @param fill Receives output data
|
||||
* @param file File to be read.
|
||||
* @param io IOSystem to be used for reading
|
||||
* @return false if file is not accessible
|
||||
*/
|
||||
void LoadSkin(SkinData& fill, const std::string& file,IOSystem* io);
|
||||
bool LoadSkin(SkinData& fill, const std::string& file,IOSystem* io);
|
||||
|
||||
} // ! namespace Q3SHader
|
||||
|
||||
@@ -243,7 +266,22 @@ protected:
|
||||
/** Try to read the skin for a MD3 file
|
||||
* @param fill Receives output information
|
||||
*/
|
||||
void ReadSkin(Q3Shader::SkinData& fill);
|
||||
void ReadSkin(Q3Shader::SkinData& fill) const;
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
/** Try to read the shader for a MD3 file
|
||||
* @param fill Receives output information
|
||||
*/
|
||||
void ReadShader(Q3Shader::ShaderData& fill) const;
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
/** Convert a texture path in a MD3 file to a proper value
|
||||
* @param[in] texture_name Path to be converted
|
||||
* @param[in] header_path Base path specified in MD3 header
|
||||
* @param[out] out Receives the converted output string
|
||||
*/
|
||||
void ConvertPath(const char* texture_name, const char* header_path,
|
||||
std::string& out) const;
|
||||
|
||||
protected:
|
||||
|
||||
@@ -256,6 +294,9 @@ protected:
|
||||
/** Configuration option: name of skin file to be read */
|
||||
std::string configSkinFile;
|
||||
|
||||
/** Configuration option: name or path of shader */
|
||||
std::string configShaderFile;
|
||||
|
||||
/** Header of the MD3 file */
|
||||
BE_NCONST MD3::Header* pcHeader;
|
||||
|
||||
|
||||
@@ -288,24 +288,16 @@ uint32_t MaterialHelper::ComputeHash(bool includeMatName /*= false*/)
|
||||
{
|
||||
aiMaterialProperty* prop;
|
||||
|
||||
// If specified, exclude the material name from the hash
|
||||
if ((prop = mProperties[i]) && (includeMatName || ::strcmp(prop->mKey.data,"$mat.name")))
|
||||
// Exclude all properties whose first character is '?' from the hash
|
||||
// See doc for aiMaterialProperty.
|
||||
if ((prop = mProperties[i]) && (includeMatName || prop->mKey.data[0] != '?'))
|
||||
{
|
||||
hash = SuperFastHash(prop->mKey.data,(unsigned int)prop->mKey.length,hash);
|
||||
hash = SuperFastHash(prop->mData,prop->mDataLength,hash);
|
||||
|
||||
// Combine the semantic and the index with the hash
|
||||
// We print them to a string to make sure the quality
|
||||
// of the hashing state isn't affected (our hashing
|
||||
// procedure was originally intended for plaintest).
|
||||
char buff[32];
|
||||
unsigned int len;
|
||||
|
||||
len = ASSIMP_itoa10(buff,prop->mSemantic);
|
||||
hash = SuperFastHash(buff,len-1,hash);
|
||||
|
||||
len = ASSIMP_itoa10(buff,prop->mIndex);
|
||||
hash = SuperFastHash(buff,len-1,hash);
|
||||
hash = SuperFastHash((const char*)&prop->mSemantic,sizeof(unsigned int),hash);
|
||||
hash = SuperFastHash((const char*)&prop->mIndex,sizeof(unsigned int),hash);
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
|
||||
@@ -131,8 +131,9 @@ public:
|
||||
* proprty and call this method again, the resulting hash value will be
|
||||
* different.
|
||||
*
|
||||
* @param includeMatName Set to 'true' to take the #AI_MATKEY_NAME property
|
||||
* into account. The default value is false.
|
||||
* @param includeMatName Set to 'true' to take all properties with
|
||||
* '?' as initial character in their name into account.
|
||||
* Currently #AI_MATKEY_NAME is the only example.
|
||||
* @return Unique hash
|
||||
*/
|
||||
uint32_t ComputeHash(bool includeMatName = false);
|
||||
|
||||
@@ -201,7 +201,7 @@ void CollectData( aiScene* pcScene, aiNode* pcNode, unsigned int iMat,
|
||||
// aiFace destructor ...
|
||||
pcMesh->mFaces[planck].mIndices = NULL;
|
||||
|
||||
// FIX: update the mPrimitiveTypes member of the mesh
|
||||
// Update the mPrimitiveTypes member of the mesh
|
||||
switch (pcMesh->mFaces[planck].mNumIndices)
|
||||
{
|
||||
case 0x1:
|
||||
@@ -249,13 +249,11 @@ void GetVFormatList( aiScene* pcScene, unsigned int iMat,
|
||||
// Compute the absolute transformation matrices of each node
|
||||
void ComputeAbsoluteTransform( aiNode* pcNode )
|
||||
{
|
||||
if (pcNode->mParent)
|
||||
{
|
||||
if (pcNode->mParent) {
|
||||
pcNode->mTransformation = pcNode->mParent->mTransformation*pcNode->mTransformation;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0;i < pcNode->mNumChildren;++i)
|
||||
{
|
||||
for (unsigned int i = 0;i < pcNode->mNumChildren;++i) {
|
||||
ComputeAbsoluteTransform(pcNode->mChildren[i]);
|
||||
}
|
||||
}
|
||||
@@ -291,17 +289,13 @@ void PretransformVertices::Execute( aiScene* pScene)
|
||||
std::vector<aiMesh*> apcOutMeshes;
|
||||
apcOutMeshes.reserve(pScene->mNumMaterials<<1u);
|
||||
std::list<unsigned int> aiVFormats;
|
||||
for (unsigned int i = 0; i < pScene->mNumMaterials;++i)
|
||||
{
|
||||
for (unsigned int i = 0; i < pScene->mNumMaterials;++i) {
|
||||
// get the list of all vertex formats for this material
|
||||
aiVFormats.clear();
|
||||
GetVFormatList(pScene,i,aiVFormats);
|
||||
aiVFormats.sort();
|
||||
aiVFormats.unique();
|
||||
for (std::list<unsigned int>::const_iterator
|
||||
j = aiVFormats.begin();
|
||||
j != aiVFormats.end();++j)
|
||||
{
|
||||
for (std::list<unsigned int>::const_iterator j = aiVFormats.begin();j != aiVFormats.end();++j) {
|
||||
unsigned int iVertices = 0;
|
||||
unsigned int iFaces = 0;
|
||||
CountVerticesAndFaces(pScene,pScene->mRootNode,i,*j,&iFaces,&iVertices);
|
||||
|
||||
@@ -95,8 +95,7 @@ void RemoveRedundantMatsProcess::Execute( aiScene* pScene)
|
||||
for (unsigned int i = 0; i < pScene->mNumMaterials;++i)
|
||||
{
|
||||
// if the material is not referenced ... remove it
|
||||
if (!abReferenced[i])
|
||||
{
|
||||
if (!abReferenced[i]) {
|
||||
++unreferenced;
|
||||
continue;
|
||||
}
|
||||
@@ -104,8 +103,7 @@ void RemoveRedundantMatsProcess::Execute( aiScene* pScene)
|
||||
uint32_t me = aiHashes[i] = ((MaterialHelper*)pScene->mMaterials[i])->ComputeHash();
|
||||
for (unsigned int a = 0; a < i;++a)
|
||||
{
|
||||
if (me == aiHashes[a])
|
||||
{
|
||||
if (me == aiHashes[a]) {
|
||||
++iCnt;
|
||||
me = 0;
|
||||
aiMappingTable[i] = aiMappingTable[a];
|
||||
@@ -113,27 +111,26 @@ void RemoveRedundantMatsProcess::Execute( aiScene* pScene)
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (me)
|
||||
{
|
||||
if (me) {
|
||||
aiMappingTable[i] = iNewNum++;
|
||||
}
|
||||
}
|
||||
if (iCnt)
|
||||
{
|
||||
if (iCnt) {
|
||||
// build an output material list
|
||||
aiMaterial** ppcMaterials = new aiMaterial*[iNewNum];
|
||||
::memset(ppcMaterials,0,sizeof(void*)*iNewNum);
|
||||
for (unsigned int p = 0; p < pScene->mNumMaterials;++p)
|
||||
{
|
||||
// if the material is not referenced ... remove it
|
||||
if (!abReferenced[p])continue;
|
||||
if (!abReferenced[p])
|
||||
continue;
|
||||
|
||||
// generate new names for all modified materials
|
||||
const unsigned int idx = aiMappingTable[p];
|
||||
if (ppcMaterials[idx])
|
||||
{
|
||||
aiString sz;
|
||||
sz.length = ::sprintf(sz.data,"aiMaterial #%i",p);
|
||||
sz.length = ::sprintf(sz.data,"JoinedMaterial_#%i",p);
|
||||
((MaterialHelper*)ppcMaterials[idx])->AddProperty(&sz,AI_MATKEY_NAME);
|
||||
}
|
||||
else ppcMaterials[idx] = pScene->mMaterials[p];
|
||||
|
||||
@@ -635,17 +635,15 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial,
|
||||
for (unsigned int i = 0; i < pMaterial->mNumProperties;++i)
|
||||
{
|
||||
aiMaterialProperty* prop = pMaterial->mProperties[i];
|
||||
if (!::strcmp(prop->mKey.data,"$tex.file") && prop->mSemantic == type)
|
||||
{
|
||||
if (!::strcmp(prop->mKey.data,"$tex.file") && prop->mSemantic == type) {
|
||||
iIndex = std::max(iIndex, (int) prop->mIndex);
|
||||
++iNumIndices;
|
||||
|
||||
if (aiPTI_String != prop->mType)
|
||||
this->ReportError("Material property %s is expected to be a string",prop->mKey.data);
|
||||
ReportError("Material property %s is expected to be a string",prop->mKey.data);
|
||||
}
|
||||
}
|
||||
if (iIndex +1 != iNumIndices)
|
||||
{
|
||||
if (iIndex +1 != iNumIndices) {
|
||||
ReportError("%s #%i is set, but there are only %i %s textures",
|
||||
szType,iIndex,iNumIndices,szType);
|
||||
}
|
||||
@@ -666,8 +664,7 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial,
|
||||
prop->mIndex, iNumIndices, szType);
|
||||
}
|
||||
|
||||
if (!::strcmp(prop->mKey.data,"$tex.mapping"))
|
||||
{
|
||||
if (!::strcmp(prop->mKey.data,"$tex.mapping")) {
|
||||
if (aiPTI_Integer != prop->mType || prop->mDataLength < sizeof(aiTextureMapping))
|
||||
{
|
||||
ReportError("Material property %s%i is expected to be an integer (size is %i)",
|
||||
@@ -675,8 +672,7 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial,
|
||||
}
|
||||
mappings[prop->mIndex] = *((aiTextureMapping*)prop->mData);
|
||||
}
|
||||
else if (!::strcmp(prop->mKey.data,"$tex.uvtrafo"))
|
||||
{
|
||||
else if (!::strcmp(prop->mKey.data,"$tex.uvtrafo")) {
|
||||
if (aiPTI_Float != prop->mType || prop->mDataLength < sizeof(aiUVTransform))
|
||||
{
|
||||
ReportError("Material property %s%i is expected to be 5 floats large (size is %i)",
|
||||
@@ -684,8 +680,7 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial,
|
||||
}
|
||||
mappings[prop->mIndex] = *((aiTextureMapping*)prop->mData);
|
||||
}
|
||||
else if (!::strcmp(prop->mKey.data,"$tex.uvwsrc"))
|
||||
{
|
||||
else if (!::strcmp(prop->mKey.data,"$tex.uvwsrc")) {
|
||||
if (aiPTI_Integer != prop->mType || sizeof(int) > prop->mDataLength)
|
||||
{
|
||||
ReportError("Material property %s%i is expected to be an integer (size is %i)",
|
||||
@@ -709,7 +704,7 @@ void ValidateDSProcess::SearchForInvalidTextures(const aiMaterial* pMaterial,
|
||||
while (mesh->HasTextureCoords(iChannels))++iChannels;
|
||||
if (iIndex >= iChannels)
|
||||
{
|
||||
ReportError("Invalid UV index: %i (key %s). Mesh %i has only %i UV channels",
|
||||
ReportWarning("Invalid UV index: %i (key %s). Mesh %i has only %i UV channels",
|
||||
iIndex,prop->mKey.data,a,iChannels);
|
||||
}
|
||||
}
|
||||
@@ -742,42 +737,37 @@ void ValidateDSProcess::Validate( const aiMaterial* pMaterial)
|
||||
for (unsigned int i = 0; i < pMaterial->mNumProperties;++i)
|
||||
{
|
||||
const aiMaterialProperty* prop = pMaterial->mProperties[i];
|
||||
if (!prop)
|
||||
{
|
||||
this->ReportError("aiMaterial::mProperties[%i] is NULL (aiMaterial::mNumProperties is %i)",
|
||||
if (!prop) {
|
||||
ReportError("aiMaterial::mProperties[%i] is NULL (aiMaterial::mNumProperties is %i)",
|
||||
i,pMaterial->mNumProperties);
|
||||
}
|
||||
if (!prop->mDataLength || !prop->mData)
|
||||
{
|
||||
this->ReportError("aiMaterial::mProperties[%i].mDataLength or "
|
||||
if (!prop->mDataLength || !prop->mData) {
|
||||
ReportError("aiMaterial::mProperties[%i].mDataLength or "
|
||||
"aiMaterial::mProperties[%i].mData is 0",i,i);
|
||||
}
|
||||
// check all predefined types
|
||||
if (aiPTI_String == prop->mType)
|
||||
{
|
||||
// FIX: strings are now stored in a less expensive way ...
|
||||
if (prop->mDataLength < sizeof(size_t) + ((const aiString*)prop->mData)->length + 1)
|
||||
{
|
||||
this->ReportError("aiMaterial::mProperties[%i].mDataLength is "
|
||||
if (prop->mDataLength < sizeof(size_t) + ((const aiString*)prop->mData)->length + 1) {
|
||||
ReportError("aiMaterial::mProperties[%i].mDataLength is "
|
||||
"too small to contain a string (%i, needed: %i)",
|
||||
i,prop->mDataLength,sizeof(aiString));
|
||||
}
|
||||
this->Validate((const aiString*)prop->mData);
|
||||
Validate((const aiString*)prop->mData);
|
||||
}
|
||||
else if (aiPTI_Float == prop->mType)
|
||||
{
|
||||
if (prop->mDataLength < sizeof(float))
|
||||
{
|
||||
this->ReportError("aiMaterial::mProperties[%i].mDataLength is "
|
||||
if (prop->mDataLength < sizeof(float)) {
|
||||
ReportError("aiMaterial::mProperties[%i].mDataLength is "
|
||||
"too small to contain a float (%i, needed: %i)",
|
||||
i,prop->mDataLength,sizeof(float));
|
||||
}
|
||||
}
|
||||
else if (aiPTI_Integer == prop->mType)
|
||||
{
|
||||
if (prop->mDataLength < sizeof(int))
|
||||
{
|
||||
this->ReportError("aiMaterial::mProperties[%i].mDataLength is "
|
||||
if (prop->mDataLength < sizeof(int)) {
|
||||
ReportError("aiMaterial::mProperties[%i].mDataLength is "
|
||||
"too small to contain an integer (%i, needed: %i)",
|
||||
i,prop->mDataLength,sizeof(int));
|
||||
}
|
||||
@@ -788,22 +778,19 @@ void ValidateDSProcess::Validate( const aiMaterial* pMaterial)
|
||||
// make some more specific tests
|
||||
float fTemp;
|
||||
int iShading;
|
||||
if (AI_SUCCESS == aiGetMaterialInteger( pMaterial,AI_MATKEY_SHADING_MODEL,&iShading))
|
||||
{
|
||||
if (AI_SUCCESS == aiGetMaterialInteger( pMaterial,AI_MATKEY_SHADING_MODEL,&iShading)) {
|
||||
switch ((aiShadingMode)iShading)
|
||||
{
|
||||
case aiShadingMode_Blinn:
|
||||
case aiShadingMode_CookTorrance:
|
||||
case aiShadingMode_Phong:
|
||||
|
||||
if (AI_SUCCESS != aiGetMaterialFloat(pMaterial,AI_MATKEY_SHININESS,&fTemp))
|
||||
{
|
||||
this->ReportWarning("A specular shading model is specified but there is no "
|
||||
if (AI_SUCCESS != aiGetMaterialFloat(pMaterial,AI_MATKEY_SHININESS,&fTemp)) {
|
||||
ReportWarning("A specular shading model is specified but there is no "
|
||||
"AI_MATKEY_SHININESS key");
|
||||
}
|
||||
if (AI_SUCCESS == aiGetMaterialFloat(pMaterial,AI_MATKEY_SHININESS_STRENGTH,&fTemp) && !fTemp)
|
||||
{
|
||||
this->ReportWarning("A specular shading model is specified but the value of the "
|
||||
if (AI_SUCCESS == aiGetMaterialFloat(pMaterial,AI_MATKEY_SHININESS_STRENGTH,&fTemp) && !fTemp) {
|
||||
ReportWarning("A specular shading model is specified but the value of the "
|
||||
"AI_MATKEY_SHININESS_STRENGTH key is 0.0");
|
||||
}
|
||||
break;
|
||||
@@ -811,13 +798,13 @@ void ValidateDSProcess::Validate( const aiMaterial* pMaterial)
|
||||
};
|
||||
}
|
||||
|
||||
if (AI_SUCCESS == aiGetMaterialFloat( pMaterial,AI_MATKEY_OPACITY,&fTemp))
|
||||
{
|
||||
if (!fTemp)
|
||||
ReportWarning("Material is fully transparent ... are you sure you REALLY want this?");
|
||||
if (AI_SUCCESS == aiGetMaterialFloat( pMaterial,AI_MATKEY_OPACITY,&fTemp) && (!fTemp || fTemp > 1.01f)) {
|
||||
ReportWarning("Invalid opacity value (must be 0 < opacity < 1.0)");
|
||||
}
|
||||
|
||||
// check whether there are invalid texture keys
|
||||
// Check whether there are invalid texture keys
|
||||
// TODO: that's a relict of the past, where texture type and index were baked
|
||||
// into the material string ... we could do that in one single pass.
|
||||
SearchForInvalidTextures(pMaterial,aiTextureType_DIFFUSE);
|
||||
SearchForInvalidTextures(pMaterial,aiTextureType_SPECULAR);
|
||||
SearchForInvalidTextures(pMaterial,aiTextureType_AMBIENT);
|
||||
@@ -826,6 +813,9 @@ void ValidateDSProcess::Validate( const aiMaterial* pMaterial)
|
||||
SearchForInvalidTextures(pMaterial,aiTextureType_SHININESS);
|
||||
SearchForInvalidTextures(pMaterial,aiTextureType_HEIGHT);
|
||||
SearchForInvalidTextures(pMaterial,aiTextureType_NORMALS);
|
||||
SearchForInvalidTextures(pMaterial,aiTextureType_DISPLACEMENT);
|
||||
SearchForInvalidTextures(pMaterial,aiTextureType_LIGHTMAP);
|
||||
SearchForInvalidTextures(pMaterial,aiTextureType_REFLECTION);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user