Definitios for MDC and MDR added; MD2 loader is ready for BigEndian now, MD3 too. MD2 and MD3 keyframe option added, not yet implemented for MD3.
git-svn-id: https://assimp.svn.sourceforge.net/svnroot/assimp/trunk@101 67173fc5-114c-0410-ac8e-9d2fd5bffc1fpull/1/head
parent
6fe8c867e8
commit
c88ae2a0be
|
@ -110,6 +110,10 @@ using namespace Assimp::ASE;
|
|||
} else bLastWasEndLine = false; \
|
||||
++this->m_szFile;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
# define sprintf sprintf_s
|
||||
#endif
|
||||
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
Parser::Parser (const char* szFile)
|
||||
{
|
||||
|
@ -724,32 +728,20 @@ bool Parser::ParseString(std::string& out,const char* szName)
|
|||
{
|
||||
char szBuffer[1024];
|
||||
|
||||
#if (!defined _MSC_VER) || ( _MSC_VER < 1400)
|
||||
ai_assert(strlen(szName) < 750);
|
||||
#endif
|
||||
|
||||
// NOTE: The name could also be the texture in some cases
|
||||
// be prepared that this might occur ...
|
||||
if (!SkipSpaces(this->m_szFile,&this->m_szFile))
|
||||
{
|
||||
#if _MSC_VER >= 1400
|
||||
sprintf_s(szBuffer,"Unable to parse %s block: Unexpected EOL",szName);
|
||||
#else
|
||||
sprintf(szBuffer,"Unable to parse %s block: Unexpected EOL",szName);
|
||||
#endif
|
||||
this->LogWarning(szBuffer);
|
||||
return false;
|
||||
}
|
||||
// there must be "
|
||||
if ('\"' != *this->m_szFile)
|
||||
{
|
||||
#if _MSC_VER >= 1400
|
||||
sprintf_s(szBuffer,"Unable to parse %s block: String is expected "
|
||||
"to be enclosed in double quotation marks",szName);
|
||||
#else
|
||||
sprintf(szBuffer,"Unable to parse %s block: String is expected "
|
||||
"to be enclosed in double quotation marks",szName);
|
||||
#endif
|
||||
this->LogWarning(szBuffer);
|
||||
return false;
|
||||
}
|
||||
|
@ -759,16 +751,10 @@ bool Parser::ParseString(std::string& out,const char* szName)
|
|||
{
|
||||
if ('\"' == *sz)break;
|
||||
else if ('\0' == sz)
|
||||
{
|
||||
#if _MSC_VER >= 1400
|
||||
sprintf_s(szBuffer,"Unable to parse %s block: String is expected to be "
|
||||
"enclosed in double quotation marks but EOF was reached before a closing "
|
||||
"quotation mark was found",szName);
|
||||
#else
|
||||
{
|
||||
sprintf(szBuffer,"Unable to parse %s block: String is expected to be "
|
||||
"enclosed in double quotation marks but EOF was reached before a closing "
|
||||
"quotation mark was found",szName);
|
||||
#endif
|
||||
this->LogWarning(szBuffer);
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
/** @file Implementation of the MD2 importer class */
|
||||
#include "MD2Loader.h"
|
||||
#include "MaterialSystem.h"
|
||||
#include "ByteSwap.h"
|
||||
#include "MD2NormalTable.h" // shouldn't be included by other units
|
||||
|
||||
#include "../include/IOStream.h"
|
||||
|
@ -50,6 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
#include "../include/aiScene.h"
|
||||
#include "../include/aiAssert.h"
|
||||
#include "../include/DefaultLogger.h"
|
||||
#include "../include/assimp.hpp"
|
||||
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
|
||||
|
@ -110,6 +112,18 @@ bool MD2Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
|
|||
return true;
|
||||
}
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Setup configuration properties
|
||||
void MD2Importer::SetupProperties(const Importer* pImp)
|
||||
{
|
||||
// The AI_CONFIG_IMPORT_MD2_KEYFRAME option overrides the
|
||||
// AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option.
|
||||
if(0xffffffff == (this->configFrameID = pImp->GetProperty(
|
||||
AI_CONFIG_IMPORT_MD2_KEYFRAME,0xffffffff)))
|
||||
{
|
||||
this->configFrameID = pImp->GetProperty(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0);
|
||||
}
|
||||
}
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Validate the file header
|
||||
void MD2Importer::ValidateHeader( )
|
||||
{
|
||||
|
@ -117,9 +131,6 @@ void MD2Importer::ValidateHeader( )
|
|||
if (this->m_pcHeader->magic != AI_MD2_MAGIC_NUMBER_BE &&
|
||||
this->m_pcHeader->magic != AI_MD2_MAGIC_NUMBER_LE)
|
||||
{
|
||||
delete[] this->mBuffer;
|
||||
AI_DEBUG_INVALIDATE_PTR(this->mBuffer);
|
||||
|
||||
char szBuffer[5];
|
||||
szBuffer[0] = ((char*)&this->m_pcHeader->magic)[0];
|
||||
szBuffer[1] = ((char*)&this->m_pcHeader->magic)[1];
|
||||
|
@ -133,27 +144,22 @@ void MD2Importer::ValidateHeader( )
|
|||
|
||||
// check file format version
|
||||
if (this->m_pcHeader->version != 8)
|
||||
{
|
||||
DefaultLogger::get()->warn( "Unsupported md2 file version. Continuing happily ...");
|
||||
}
|
||||
|
||||
/* to be validated:
|
||||
int32_t offsetSkins;
|
||||
int32_t offsetTexCoords;
|
||||
int32_t offsetTriangles;
|
||||
int32_t offsetFrames;
|
||||
//int32_t offsetGlCommands;
|
||||
int32_t offsetEnd;
|
||||
*/
|
||||
// check some values whether they are valid
|
||||
if (0 == this->m_pcHeader->numFrames)
|
||||
throw new ImportErrorException( "Invalid md2 file: NUM_FRAMES is 0");
|
||||
|
||||
if (this->m_pcHeader->offsetSkins + this->m_pcHeader->numSkins * sizeof (MD2::Skin) >= this->fileSize ||
|
||||
this->m_pcHeader->offsetTexCoords + this->m_pcHeader->numTexCoords * sizeof (MD2::TexCoord) >= this->fileSize ||
|
||||
this->m_pcHeader->offsetTriangles + this->m_pcHeader->numTriangles * sizeof (MD2::Triangle) >= this->fileSize ||
|
||||
this->m_pcHeader->offsetFrames + this->m_pcHeader->numFrames * sizeof (MD2::Frame) >= this->fileSize ||
|
||||
if (this->m_pcHeader->offsetEnd > (int32_t)fileSize)
|
||||
throw new ImportErrorException( "Invalid md2 file: File is too small");
|
||||
|
||||
if (this->m_pcHeader->offsetSkins + this->m_pcHeader->numSkins * sizeof (MD2::Skin) >= this->fileSize ||
|
||||
this->m_pcHeader->offsetTexCoords + this->m_pcHeader->numTexCoords * sizeof (MD2::TexCoord) >= this->fileSize ||
|
||||
this->m_pcHeader->offsetTriangles + this->m_pcHeader->numTriangles * sizeof (MD2::Triangle) >= this->fileSize ||
|
||||
this->m_pcHeader->offsetFrames + this->m_pcHeader->numFrames * sizeof (MD2::Frame) >= this->fileSize ||
|
||||
this->m_pcHeader->offsetEnd > this->fileSize)
|
||||
{
|
||||
throw new ImportErrorException("Invalid MD2 header: some offsets are outside the file");
|
||||
AI_DEBUG_INVALIDATE_PTR(this->mBuffer);
|
||||
}
|
||||
|
||||
if (this->m_pcHeader->numSkins > AI_MD2_MAX_SKINS)
|
||||
|
@ -162,27 +168,27 @@ void MD2Importer::ValidateHeader( )
|
|||
DefaultLogger::get()->warn("The model contains more frames than Quake 2 supports");
|
||||
if (this->m_pcHeader->numVertices > AI_MD2_MAX_VERTS)
|
||||
DefaultLogger::get()->warn("The model contains more vertices than Quake 2 supports");
|
||||
|
||||
if (this->m_pcHeader->numFrames >= this->configFrameID )
|
||||
throw new ImportErrorException("The requested frame is not existing the file");
|
||||
|
||||
}
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Imports the given file into the given scene structure.
|
||||
void MD2Importer::InternReadFile(
|
||||
const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
|
||||
void MD2Importer::InternReadFile( const std::string& pFile,
|
||||
aiScene* pScene, IOSystem* pIOHandler)
|
||||
{
|
||||
boost::scoped_ptr<IOStream> file( pIOHandler->Open( pFile));
|
||||
|
||||
// Check whether we can read from the file
|
||||
if( file.get() == NULL)
|
||||
{
|
||||
throw new ImportErrorException( "Failed to open md2 file " + pFile + ".");
|
||||
}
|
||||
throw new ImportErrorException( "Failed to open MD2 file " + pFile + "");
|
||||
|
||||
// check whether the md3 file is large enough to contain
|
||||
// at least the file header
|
||||
fileSize = (unsigned int)file->FileSize();
|
||||
if( fileSize < sizeof(MD2::Header))
|
||||
{
|
||||
throw new ImportErrorException( "md2 File is too small.");
|
||||
}
|
||||
throw new ImportErrorException( "MD2 File is too small");
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -191,17 +197,30 @@ void MD2Importer::InternReadFile(
|
|||
file->Read( (void*)mBuffer, 1, fileSize);
|
||||
|
||||
this->m_pcHeader = (const MD2::Header*)this->mBuffer;
|
||||
this->ValidateHeader();
|
||||
|
||||
// check some values whether they are valid
|
||||
if (0 == this->m_pcHeader->numFrames)
|
||||
{
|
||||
throw new ImportErrorException( "Invalid md2 file: NUM_FRAMES is 0");
|
||||
}
|
||||
if (this->m_pcHeader->offsetEnd > (int32_t)fileSize)
|
||||
{
|
||||
throw new ImportErrorException( "Invalid md2 file: File is too small");
|
||||
}
|
||||
#ifdef AI_BUILD_BIG_ENDIAN
|
||||
|
||||
ByteSwap::Swap4(&m_pcHeader->frameSize);
|
||||
ByteSwap::Swap4(&m_pcHeader->magic);
|
||||
ByteSwap::Swap4(&m_pcHeader->numFrames);
|
||||
ByteSwap::Swap4(&m_pcHeader->numGlCommands);
|
||||
ByteSwap::Swap4(&m_pcHeader->numSkins);
|
||||
ByteSwap::Swap4(&m_pcHeader->numTexCoords);
|
||||
ByteSwap::Swap4(&m_pcHeader->numTriangles);
|
||||
ByteSwap::Swap4(&m_pcHeader->numVertices);
|
||||
ByteSwap::Swap4(&m_pcHeader->offsetEnd);
|
||||
ByteSwap::Swap4(&m_pcHeader->offsetFrames);
|
||||
ByteSwap::Swap4(&m_pcHeader->offsetGlCommands);
|
||||
ByteSwap::Swap4(&m_pcHeader->offsetSkins);
|
||||
ByteSwap::Swap4(&m_pcHeader->offsetTexCoords);
|
||||
ByteSwap::Swap4(&m_pcHeader->offsetTriangles);
|
||||
ByteSwap::Swap4(&m_pcHeader->skinHeight);
|
||||
ByteSwap::Swap4(&m_pcHeader->skinWidth);
|
||||
ByteSwap::Swap4(&m_pcHeader->version);
|
||||
|
||||
#endif
|
||||
|
||||
this->ValidateHeader();
|
||||
|
||||
// there won't be more than one mesh inside the file
|
||||
pScene->mNumMaterials = 1;
|
||||
|
@ -216,20 +235,43 @@ void MD2Importer::InternReadFile(
|
|||
aiMesh* pcMesh = pScene->mMeshes[0] = new aiMesh();
|
||||
|
||||
// navigate to the begin of the frame data
|
||||
const MD2::Frame* pcFrame = (const MD2::Frame*) (
|
||||
(unsigned char*)this->m_pcHeader + this->m_pcHeader->offsetFrames);
|
||||
const MD2::Frame* pcFrame = (const MD2::Frame*) ((uint8_t*)
|
||||
this->m_pcHeader + this->m_pcHeader->offsetFrames);
|
||||
pcFrame += this->configFrameID;
|
||||
|
||||
// navigate to the begin of the triangle data
|
||||
MD2::Triangle* pcTriangles = (MD2::Triangle*) (
|
||||
(unsigned char*)this->m_pcHeader + this->m_pcHeader->offsetTriangles);
|
||||
MD2::Triangle* pcTriangles = (MD2::Triangle*) ((uint8_t*)
|
||||
this->m_pcHeader + this->m_pcHeader->offsetTriangles);
|
||||
|
||||
// navigate to the begin of the tex coords data
|
||||
const MD2::TexCoord* pcTexCoords = (const MD2::TexCoord*) (
|
||||
(unsigned char*)this->m_pcHeader + this->m_pcHeader->offsetTexCoords);
|
||||
const MD2::TexCoord* pcTexCoords = (const MD2::TexCoord*) ((uint8_t*)
|
||||
this->m_pcHeader + this->m_pcHeader->offsetTexCoords);
|
||||
|
||||
// navigate to the begin of the vertex data
|
||||
const MD2::Vertex* pcVerts = (const MD2::Vertex*) (pcFrame->vertices);
|
||||
|
||||
#ifdef AI_BUILD_BIG_ENDIAN
|
||||
for (uint32_t i = 0; i< m_pcHeader->numTriangles)
|
||||
{
|
||||
for (unsigned int p = 0; p < 3;++p)
|
||||
{
|
||||
ByteSwap::Swap2(& pcTriangles[i].textureIndices[p]);
|
||||
ByteSwap::Swap2(& pcTriangles[i].vertexIndices[p]);
|
||||
}
|
||||
}
|
||||
for (uint32_t i = 0; i < m_pcHeader->offsetTexCoords;++i)
|
||||
{
|
||||
ByteSwap::Swap2(& pcTexCoords[i].s);
|
||||
ByteSwap::Swap2(& pcTexCoords[i].t);
|
||||
}
|
||||
ByteSwap::Swap4( & pcFrame->scale[0] );
|
||||
ByteSwap::Swap4( & pcFrame->scale[1] );
|
||||
ByteSwap::Swap4( & pcFrame->scale[2] );
|
||||
ByteSwap::Swap4( & pcFrame->translate[0] );
|
||||
ByteSwap::Swap4( & pcFrame->translate[1] );
|
||||
ByteSwap::Swap4( & pcFrame->translate[2] );
|
||||
#endif
|
||||
|
||||
pcMesh->mNumFaces = this->m_pcHeader->numTriangles;
|
||||
pcMesh->mFaces = new aiFace[this->m_pcHeader->numTriangles];
|
||||
|
||||
|
|
|
@ -75,6 +75,14 @@ public:
|
|||
* See BaseImporter::CanRead() for details. */
|
||||
bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
|
||||
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
/** Called prior to ReadFile().
|
||||
* The function is a request to the importer to update its configuration
|
||||
* basing on the Importer's configuration property list.
|
||||
*/
|
||||
void SetupProperties(const Importer* pImp);
|
||||
|
||||
protected:
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
@ -101,6 +109,9 @@ protected:
|
|||
|
||||
protected:
|
||||
|
||||
/** Configuration option: frame to be loaded */
|
||||
unsigned int configFrameID;
|
||||
|
||||
/** Header of the MD2 file */
|
||||
const MD2::Header* m_pcHeader;
|
||||
|
||||
|
|
|
@ -43,6 +43,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
#include "MD3Loader.h"
|
||||
#include "MaterialSystem.h"
|
||||
#include "StringComparison.h"
|
||||
#include "ByteSwap.h"
|
||||
|
||||
#include "../include/IOStream.h"
|
||||
#include "../include/IOSystem.h"
|
||||
|
@ -50,6 +51,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
#include "../include/aiScene.h"
|
||||
#include "../include/aiAssert.h"
|
||||
#include "../include/DefaultLogger.h"
|
||||
#include "../include/assimp.hpp"
|
||||
|
||||
#include <boost/scoped_ptr.hpp>
|
||||
|
||||
|
@ -90,14 +92,19 @@ bool MD3Importer::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
|
|||
// ------------------------------------------------------------------------------------------------
|
||||
void MD3Importer::ValidateHeaderOffsets()
|
||||
{
|
||||
// check magic number
|
||||
if (this->m_pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_BE &&
|
||||
this->m_pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_LE)
|
||||
throw new ImportErrorException( "Invalid MD3 file: Magic bytes not found");
|
||||
|
||||
// check file format version
|
||||
if (this->m_pcHeader->VERSION > 15)
|
||||
DefaultLogger::get()->warn( "Unsupported md3 file version. Continuing happily ...");
|
||||
DefaultLogger::get()->warn( "Unsupported MD3 file version. Continuing happily ...");
|
||||
|
||||
// check some values whether they are valid
|
||||
if (0 == this->m_pcHeader->NUM_FRAMES)
|
||||
throw new ImportErrorException( "Invalid md3 file: NUM_FRAMES is 0");
|
||||
if (0 == this->m_pcHeader->NUM_SURFACES)
|
||||
if (!this->m_pcHeader->NUM_FRAMES)
|
||||
throw new ImportErrorException( "Invalid MD3 file: NUM_FRAMES is 0");
|
||||
if (!this->m_pcHeader->NUM_SURFACES)
|
||||
throw new ImportErrorException( "Invalid md3 file: NUM_SURFACES is 0");
|
||||
|
||||
if (this->m_pcHeader->OFS_FRAMES >= this->fileSize ||
|
||||
|
@ -106,6 +113,9 @@ void MD3Importer::ValidateHeaderOffsets()
|
|||
{
|
||||
throw new ImportErrorException("Invalid MD3 header: some offsets are outside the file");
|
||||
}
|
||||
|
||||
if (this->m_pcHeader->NUM_FRAMES >= this->configFrameID )
|
||||
throw new ImportErrorException("The requested frame is not existing the file");
|
||||
}
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
void MD3Importer::ValidateSurfaceHeaderOffsets(const MD3::Surface* pcSurf)
|
||||
|
@ -131,6 +141,18 @@ void MD3Importer::ValidateSurfaceHeaderOffsets(const MD3::Surface* pcSurf)
|
|||
DefaultLogger::get()->warn("The model contains more frames than Quake 3 supports");
|
||||
}
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Setup configuration properties
|
||||
void MD3Importer::SetupProperties(const Importer* pImp)
|
||||
{
|
||||
// The AI_CONFIG_IMPORT_MD3_KEYFRAME option overrides the
|
||||
// AI_CONFIG_IMPORT_GLOBAL_KEYFRAME option.
|
||||
if(0xffffffff == (this->configFrameID = pImp->GetProperty(
|
||||
AI_CONFIG_IMPORT_MD3_KEYFRAME,0xffffffff)))
|
||||
{
|
||||
this->configFrameID = pImp->GetProperty(AI_CONFIG_IMPORT_GLOBAL_KEYFRAME,0);
|
||||
}
|
||||
}
|
||||
// ------------------------------------------------------------------------------------------------
|
||||
// Imports the given file into the given scene structure.
|
||||
void MD3Importer::InternReadFile(
|
||||
const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
|
||||
|
@ -139,17 +161,13 @@ void MD3Importer::InternReadFile(
|
|||
|
||||
// Check whether we can read from the file
|
||||
if( file.get() == NULL)
|
||||
{
|
||||
throw new ImportErrorException( "Failed to open md3 file " + pFile + ".");
|
||||
}
|
||||
throw new ImportErrorException( "Failed to open MD3 file " + pFile + ".");
|
||||
|
||||
// check whether the md3 file is large enough to contain
|
||||
// at least the file header
|
||||
fileSize = (unsigned int)file->FileSize();
|
||||
if( fileSize < sizeof(MD3::Header))
|
||||
{
|
||||
throw new ImportErrorException( ".md3 File is too small.");
|
||||
}
|
||||
throw new ImportErrorException( "MD3 File is too small.");
|
||||
|
||||
// allocate storage and copy the contents of the file to a memory buffer
|
||||
this->mBuffer = new unsigned char[fileSize];
|
||||
|
@ -160,12 +178,22 @@ void MD3Importer::InternReadFile(
|
|||
|
||||
this->m_pcHeader = (const MD3::Header*)this->mBuffer;
|
||||
|
||||
// check magic number
|
||||
if (this->m_pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_BE &&
|
||||
this->m_pcHeader->IDENT != AI_MD3_MAGIC_NUMBER_LE)
|
||||
{
|
||||
throw new ImportErrorException( "Invalid md3 file: Magic bytes not found");
|
||||
}
|
||||
#ifdef AI_BUILD_BIG_ENDIAN
|
||||
|
||||
ByteSwap::Swap4(&m_pcHeader->VERSION);
|
||||
ByteSwap::Swap4(&m_pcHeader->FLAGS);
|
||||
ByteSwap::Swap4(&m_pcHeader->IDENT);
|
||||
ByteSwap::Swap4(&m_pcHeader->NUM_FRAMES);
|
||||
ByteSwap::Swap4(&m_pcHeader->NUM_SKINS);
|
||||
ByteSwap::Swap4(&m_pcHeader->NUM_SURFACES);
|
||||
ByteSwap::Swap4(&m_pcHeader->NUM_TAGS);
|
||||
ByteSwap::Swap4(&m_pcHeader->OFS_EOF);
|
||||
ByteSwap::Swap4(&m_pcHeader->OFS_FRAMES);
|
||||
ByteSwap::Swap4(&m_pcHeader->OFS_SURFACES);
|
||||
ByteSwap::Swap4(&m_pcHeader->OFS_TAGS);
|
||||
|
||||
#endif
|
||||
|
||||
// validate the header
|
||||
this->ValidateHeaderOffsets();
|
||||
|
||||
|
@ -190,33 +218,71 @@ void MD3Importer::InternReadFile(
|
|||
unsigned int iDefaultMatIndex = 0xFFFFFFFF;
|
||||
while (iNum-- > 0)
|
||||
{
|
||||
|
||||
#ifdef AI_BUILD_BIG_ENDIAN
|
||||
|
||||
ByteSwap::Swap4(pcSurfaces->FLAGS);
|
||||
ByteSwap::Swap4(pcSurfaces->IDENT);
|
||||
ByteSwap::Swap4(pcSurfaces->NUM_FRAMES);
|
||||
ByteSwap::Swap4(pcSurfaces->NUM_SHADER);
|
||||
ByteSwap::Swap4(pcSurfaces->NUM_TRIANGLES);
|
||||
ByteSwap::Swap4(pcSurfaces->NUM_VERTICES);
|
||||
ByteSwap::Swap4(pcSurfaces->OFS_END);
|
||||
ByteSwap::Swap4(pcSurfaces->OFS_SHADERS);
|
||||
ByteSwap::Swap4(pcSurfaces->OFS_ST);
|
||||
ByteSwap::Swap4(pcSurfaces->OFS_TRIANGLES);
|
||||
ByteSwap::Swap4(pcSurfaces->OFS_XYZNORMAL);
|
||||
|
||||
#endif
|
||||
|
||||
// validate the surface
|
||||
this->ValidateSurfaceHeaderOffsets(pcSurfaces);
|
||||
|
||||
// navigate to the vertex list of the surface
|
||||
const MD3::Vertex* pcVertices = (const MD3::Vertex*)
|
||||
(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_XYZNORMAL);
|
||||
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_XYZNORMAL);
|
||||
|
||||
// navigate to the triangle list of the surface
|
||||
const MD3::Triangle* pcTriangles = (const MD3::Triangle*)
|
||||
(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_TRIANGLES);
|
||||
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_TRIANGLES);
|
||||
|
||||
// navigate to the texture coordinate list of the surface
|
||||
const MD3::TexCoord* pcUVs = (const MD3::TexCoord*)
|
||||
(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_ST);
|
||||
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_ST);
|
||||
|
||||
// navigate to the shader list of the surface
|
||||
const MD3::Shader* pcShaders = (const MD3::Shader*)
|
||||
(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_SHADERS);
|
||||
(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_SHADERS);
|
||||
|
||||
// if the submesh is empty ignore it
|
||||
if (0 == pcSurfaces->NUM_VERTICES || 0 == pcSurfaces->NUM_TRIANGLES)
|
||||
{
|
||||
pcSurfaces = (const MD3::Surface*)(((unsigned char*)pcSurfaces) + pcSurfaces->OFS_END);
|
||||
pcSurfaces = (const MD3::Surface*)(((uint8_t*)pcSurfaces) + pcSurfaces->OFS_END);
|
||||
pScene->mNumMeshes--;
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef AI_BUILD_BIG_ENDIAN
|
||||
|
||||
for (uint32_t i = 0; i < pcSurfaces->NUM_VERTICES;++i)
|
||||
{
|
||||
ByteSwap::Swap2( & pcVertices[i].NORMAL );
|
||||
ByteSwap::Swap2( & pcVertices[i].X );
|
||||
ByteSwap::Swap2( & pcVertices[i].Y );
|
||||
ByteSwap::Swap2( & pcVertices[i].Z );
|
||||
|
||||
ByteSwap::Swap4( & pcUVs[i].U );
|
||||
ByteSwap::Swap4( & pcUVs[i].U );
|
||||
}
|
||||
for (uint32_t i = 0; i < pcSurfaces->NUM_TRIANGLES;++i)
|
||||
{
|
||||
ByteSwap::Swap4(pcTriangles[i].INDEXES[0]);
|
||||
ByteSwap::Swap4(pcTriangles[i].INDEXES[1]);
|
||||
ByteSwap::Swap4(pcTriangles[i].INDEXES[2]);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// allocate the output mesh
|
||||
pScene->mMeshes[iNum] = new aiMesh();
|
||||
aiMesh* pcMesh = pScene->mMeshes[iNum];
|
||||
|
@ -358,7 +424,7 @@ void MD3Importer::InternReadFile(
|
|||
pcHelper->AddProperty(&szName,AI_MATKEY_NAME);
|
||||
|
||||
pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper;
|
||||
pcMesh->mMaterialIndex = iNumMaterials++;
|
||||
iDefaultMatIndex = pcMesh->mMaterialIndex = iNumMaterials++;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -384,7 +450,7 @@ void MD3Importer::InternReadFile(
|
|||
pcHelper->AddProperty<aiColor3D>(&clr, 1,AI_MATKEY_COLOR_AMBIENT);
|
||||
|
||||
pScene->mMaterials[iNumMaterials] = (aiMaterial*)pcHelper;
|
||||
pcMesh->mMaterialIndex = iNumMaterials++;
|
||||
iDefaultMatIndex = pcMesh->mMaterialIndex = iNumMaterials++;
|
||||
}
|
||||
}
|
||||
// go to the next surface
|
||||
|
|
|
@ -78,6 +78,14 @@ public:
|
|||
* See BaseImporter::CanRead() for details. */
|
||||
bool CanRead( const std::string& pFile, IOSystem* pIOHandler) const;
|
||||
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
/** Called prior to ReadFile().
|
||||
* The function is a request to the importer to update its configuration
|
||||
* basing on the Importer's configuration property list.
|
||||
*/
|
||||
void SetupProperties(const Importer* pImp);
|
||||
|
||||
protected:
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
@ -105,6 +113,9 @@ protected:
|
|||
|
||||
protected:
|
||||
|
||||
/** Configuration option: frame to be loaded */
|
||||
unsigned int configFrameID;
|
||||
|
||||
/** Header of the MD3 file */
|
||||
const MD3::Header* m_pcHeader;
|
||||
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
Open Asset Import Library (ASSIMP)
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2006-2008, ASSIMP Development Team
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the
|
||||
following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* Neither the name of the ASSIMP team, nor the names of its
|
||||
contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior
|
||||
written permission of the ASSIMP Development Team.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/** @file Defines the helper data structures for importing MDC files
|
||||
|
||||
**********************************************************************
|
||||
File format specification:
|
||||
http://themdcfile.planetwolfenstein.gamespy.com/MDC_File_Format.pdf
|
||||
**********************************************************************
|
||||
|
||||
*/
|
||||
#ifndef AI_MDCFILEHELPER_H_INC
|
||||
#define AI_MDCFILEHELPER_H_INC
|
||||
|
||||
#include "../include/aiTypes.h"
|
||||
#include "../include/aiMesh.h"
|
||||
#include "../include/aiAnim.h"
|
||||
|
||||
#if defined(_MSC_VER) || defined(__BORLANDC__) || defined (__BCPLUSPLUS__)
|
||||
# pragma pack(push,1)
|
||||
# define PACK_STRUCT
|
||||
#elif defined( __GNUC__ )
|
||||
# define PACK_STRUCT __attribute__((packed))
|
||||
#else
|
||||
# error Compiler not supported
|
||||
#endif
|
||||
|
||||
|
||||
namespace Assimp {
|
||||
namespace MDC {
|
||||
|
||||
#define AI_MDC_MAGIC_NUMBER_BE 'CPDI'
|
||||
#define AI_MDC_MAGIC_NUMBER_LE 'IDPC'
|
||||
|
||||
// common limitations
|
||||
#define AI_MDC_VERSION 2
|
||||
#define AI_MDC_MAXQPATH 64
|
||||
#define AI_MDC_MAX_BONES 128
|
||||
|
||||
#define AI_MDC_CVERT_BIAS 127.0f
|
||||
#define AI_MDC_DELTA_SCALING 4.0f
|
||||
#define AI_MDC_BASE_SCALING (1.0f / 64.0f)
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Data structure for a MDC file's main header
|
||||
*/
|
||||
struct Header
|
||||
{
|
||||
uint32_t ulIdent ;
|
||||
uint32_t ulVersion ;
|
||||
char ucName [ AI_MDC_MAXQPATH ] ;
|
||||
uint32_t ulFlags ;
|
||||
uint32_t ulNumFrames ;
|
||||
uint32_t ulNumTags ;
|
||||
uint32_t ulNumSurfaces ;
|
||||
uint32_t ulNumSkins ;
|
||||
uint32_t ulOffsetBorderFrames ;
|
||||
uint32_t ulOffsetTagNames ;
|
||||
uint32_t ulOffsetTagFrames ;
|
||||
uint32_t ulOffsetSurfaces ;
|
||||
uint32_t ulOffsetEnd ;
|
||||
} PACK_STRUCT ;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Data structure for a MDC file's surface header
|
||||
*/
|
||||
struct Surface
|
||||
{
|
||||
uint32_t ulIdent ;
|
||||
char ucName [ AI_MDC_MAXQPATH ] ;
|
||||
uint32_t ulFlags ;
|
||||
uint32_t ulNumCompFrames ;
|
||||
uint32_t ulNumBaseFrames ;
|
||||
uint32_t ulNumShaders ;
|
||||
uint32_t ulNumVertices ;
|
||||
uint32_t ulNumTriangles ;
|
||||
uint32_t ulOffsetTriangles ;
|
||||
uint32_t ulOffsetShaders ;
|
||||
uint32_t ulOffsetTexCoords ;
|
||||
uint32_t ulOffsetBaseVerts ;
|
||||
uint32_t ulOffsetCompVerts ;
|
||||
uint32_t ulOffsetFrameBaseFrames ;
|
||||
uint32_t ulOffsetFrameCompFrames ;
|
||||
uint32_t ulOffsetEnd ;
|
||||
} PACK_STRUCT;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Data structure for a MDC frame
|
||||
*/
|
||||
struct Frame
|
||||
{
|
||||
//! bounding box minimum coords
|
||||
aiVector3D bboxMin ;
|
||||
|
||||
//! bounding box maximum coords
|
||||
aiVector3D bboxMax ;
|
||||
|
||||
//! local origin of the frame
|
||||
aiVector3D localOrigin ;
|
||||
|
||||
//! radius of the BB
|
||||
float radius ;
|
||||
|
||||
//! Name of the frame
|
||||
char name [ 16 ] ;
|
||||
} PACK_STRUCT;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Data structure for a MDC triangle
|
||||
*/
|
||||
struct Triangle
|
||||
{
|
||||
uint32_t aiIndices[3];
|
||||
} PACK_STRUCT;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Data structure for a MDC texture coordinate
|
||||
*/
|
||||
struct TexturCoord
|
||||
{
|
||||
float u,v;
|
||||
} PACK_STRUCT;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Data structure for a MDC base vertex
|
||||
*/
|
||||
struct BaseVertex
|
||||
{
|
||||
int16_t x,y,z;
|
||||
uint16_t normal;
|
||||
} PACK_STRUCT;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Data structure for a MDC compressed vertex
|
||||
*/
|
||||
struct CompressedVertex
|
||||
{
|
||||
uint8_t xd,yd,zd,nd;
|
||||
} PACK_STRUCT;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Data structure for a MDC shader
|
||||
*/
|
||||
struct Shader
|
||||
{
|
||||
char ucName [ AI_MDC_MAXQPATH ] ;
|
||||
uint32_t ulPath;
|
||||
|
||||
} PACK_STRUCT;
|
||||
|
||||
// reset packing to the original value
|
||||
#if defined(_MSC_VER) || defined(__BORLANDC__) || defined (__BCPLUSPLUS__)
|
||||
# pragma pack( pop )
|
||||
#endif
|
||||
#undef PACK_STRUCT
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** Build a floating point vertex from the compressed data in MDC files
|
||||
*/
|
||||
void BuildVertex(const Frame& frame,
|
||||
const BaseVertex& bvert,
|
||||
const CompressedVertex& cvert,
|
||||
aiVector3D& vXYZOut,
|
||||
aiVector3D& vNorOut);
|
||||
}}
|
||||
|
||||
#endif // !! AI_MDCFILEHELPER_H_INC
|
|
@ -0,0 +1,237 @@
|
|||
/*
|
||||
Open Asset Import Library (ASSIMP)
|
||||
----------------------------------------------------------------------
|
||||
|
||||
Copyright (c) 2006-2008, ASSIMP Development Team
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use of this software in source and binary forms,
|
||||
with or without modification, are permitted provided that the
|
||||
following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the
|
||||
following disclaimer in the documentation and/or other
|
||||
materials provided with the distribution.
|
||||
|
||||
* Neither the name of the ASSIMP team, nor the names of its
|
||||
contributors may be used to endorse or promote products
|
||||
derived from this software without specific prior
|
||||
written permission of the ASSIMP Development Team.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
----------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/** @file Defines the helper data structures for importing MDR files */
|
||||
#ifndef AI_MDRFILEHELPER_H_INC
|
||||
#define AI_MDRFILEHELPER_H_INC
|
||||
|
||||
#include "../include/aiTypes.h"
|
||||
#include "../include/aiMesh.h"
|
||||
#include "../include/aiAnim.h"
|
||||
|
||||
#if defined(_MSC_VER) || defined(__BORLANDC__) || defined (__BCPLUSPLUS__)
|
||||
# pragma pack(push,1)
|
||||
# define PACK_STRUCT
|
||||
#elif defined( __GNUC__ )
|
||||
# define PACK_STRUCT __attribute__((packed))
|
||||
#else
|
||||
# error Compiler not supported
|
||||
#endif
|
||||
|
||||
|
||||
namespace Assimp {
|
||||
namespace MDR {
|
||||
|
||||
#define AI_MDR_MAGIC_NUMBER_BE 'RDM5'
|
||||
#define AI_MDR_MAGIC_NUMBER_LE '5MDR'
|
||||
|
||||
// common limitations
|
||||
#define AI_MDR_VERSION 2
|
||||
#define AI_MDR_MAXQPATH 64
|
||||
#define AI_MDR_MAX_BONES 128
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Data structure for a vertex weight in a MDR file
|
||||
*/
|
||||
struct Weight
|
||||
{
|
||||
//! these are indexes into the boneReferences
|
||||
//! not the global per-frame bone list
|
||||
uint32_t boneIndex;
|
||||
|
||||
//! weight of this bone
|
||||
float boneWeight;
|
||||
|
||||
//! offset of this bone
|
||||
aiVector3D offset;
|
||||
} PACK_STRUCT;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Data structure for a vertex in a MDR file
|
||||
*/
|
||||
struct Vertex
|
||||
{
|
||||
aiVector3D normal;
|
||||
aiVector2D texCoords;
|
||||
uint32_t numWeights;
|
||||
Weight weights[1]; // variable sized
|
||||
} PACK_STRUCT;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Data structure for a triangle in a MDR file
|
||||
*/
|
||||
struct Triangle
|
||||
{
|
||||
uint32_t indexes[3];
|
||||
} PACK_STRUCT;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Data structure for a surface in a MDR file
|
||||
*/
|
||||
struct Surface
|
||||
{
|
||||
uint32_t ident;
|
||||
|
||||
char name[AI_MDR_MAXQPATH]; // polyset name
|
||||
char shader[AI_MDR_MAXQPATH];
|
||||
uint32_t shaderIndex;
|
||||
|
||||
int32_t ofsHeader; // this will be a negative number
|
||||
|
||||
uint32_t numVerts;
|
||||
uint32_t ofsVerts;
|
||||
|
||||
uint32_t numTriangles;
|
||||
uint32_t ofsTriangles;
|
||||
|
||||
// Bone references are a set of ints representing all the bones
|
||||
// present in any vertex weights for this surface. This is
|
||||
// needed because a model may have surfaces that need to be
|
||||
// drawn at different sort times, and we don't want to have
|
||||
// to re-interpolate all the bones for each surface.
|
||||
uint32_t numBoneReferences;
|
||||
uint32_t ofsBoneReferences;
|
||||
|
||||
uint32_t ofsEnd; // next surface follows
|
||||
} PACK_STRUCT;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Data structure for a bone in a MDR file
|
||||
*/
|
||||
struct Bone
|
||||
{
|
||||
float matrix[3][4];
|
||||
} PACK_STRUCT;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Data structure for a frame in a MDR file
|
||||
*/
|
||||
struct Frame {
|
||||
aiVector3D bounds[2]; // bounds of all surfaces of all LOD's for this frame
|
||||
aiVector3D localOrigin; // midpoint of bounds, used for sphere cull
|
||||
float radius; // dist from localOrigin to corner
|
||||
char name[16];
|
||||
Bone bones[1]; // [numBones]
|
||||
} PACK_STRUCT;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Data structure for a compressed bone in a MDR file
|
||||
*/
|
||||
struct CompBone
|
||||
{
|
||||
unsigned char Comp[24]; // MC_COMP_BYTES is in MatComp.h, but don't want to couple
|
||||
} PACK_STRUCT;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Data structure for a compressed frame in a MDR file
|
||||
*/
|
||||
struct CompFrame
|
||||
{
|
||||
aiVector3D bounds[2]; // bounds of all surfaces of all LOD's for this frame
|
||||
aiVector3D localOrigin; // midpoint of bounds, used for sphere cull
|
||||
float radius; // dist from localOrigin to corner
|
||||
CompBone bones[1]; // [numBones]
|
||||
} PACK_STRUCT;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Data structure for a LOD in a MDR file
|
||||
*/
|
||||
struct LOD
|
||||
{
|
||||
uint32_t numSurfaces;
|
||||
uint32_t ofsSurfaces; // first surface, others follow
|
||||
uint32_t ofsEnd; // next lod follows
|
||||
} ;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Data structure for a tag (= attachment) in a MDR file
|
||||
*/
|
||||
struct Tag
|
||||
{
|
||||
uint32_t boneIndex;
|
||||
char name[32];
|
||||
} PACK_STRUCT;
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
/** \brief Header data structure for a MDR file
|
||||
*/
|
||||
struct Header
|
||||
{
|
||||
uint32_t ident;
|
||||
uint32_t version;
|
||||
|
||||
char name[AI_MDR_MAXQPATH];
|
||||
|
||||
// frames and bones are shared by all levels of detail
|
||||
int32_t numFrames;
|
||||
uint32_t numBones;
|
||||
uint32_t ofsFrames;
|
||||
|
||||
// each level of detail has completely separate sets of surfaces
|
||||
uint32_t numLODs;
|
||||
uint32_t ofsLODs;
|
||||
|
||||
uint32_t numTags;
|
||||
uint32_t ofsTags;
|
||||
|
||||
uint32_t ofsEnd;
|
||||
} PACK_STRUCT;
|
||||
|
||||
|
||||
// reset packing to the original value
|
||||
#if defined(_MSC_VER) || defined(__BORLANDC__) || defined (__BCPLUSPLUS__)
|
||||
# pragma pack( pop )
|
||||
#endif
|
||||
#undef PACK_STRUCT
|
||||
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
#endif // !! AI_MDRFILEHELPER_H_INC
|
Loading…
Reference in New Issue