/*
---------------------------------------------------------------------------
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 Implementation of the 3ds importer class */

#include "AssimpPCH.h"

// internal headers
#include "3DSLoader.h"
#include "TextureTransform.h"

using namespace Assimp;
		
// ------------------------------------------------------------------------------------------------
// Begins a new parsing block
// - Reads the current chunk and validates it
// - computes its length

#define ASSIMP_3DS_BEGIN_CHUNK() \
	Discreet3DS::Chunk chunk; \
	ReadChunk(&chunk); \
	int chunkSize = chunk.Size-sizeof(Discreet3DS::Chunk); \
	int oldReadLimit = stream->GetReadLimit(); \
	stream->SetReadLimit(stream->GetCurrentPos() + chunkSize);
	

// ------------------------------------------------------------------------------------------------
// End a parsing block
// Must follow at the end of each parsing block

#define ASSIMP_3DS_END_CHUNK() \
	stream->SkipToReadLimit(); \
	stream->SetReadLimit(oldReadLimit); \
	if (stream->GetRemainingSizeToLimit() == 0)return;


// ------------------------------------------------------------------------------------------------
// Constructor to be privately used by Importer
Discreet3DSImporter::Discreet3DSImporter()
{
}

// ------------------------------------------------------------------------------------------------
// Destructor, private as well 
Discreet3DSImporter::~Discreet3DSImporter()
{
}

// ------------------------------------------------------------------------------------------------
// Returns whether the class can handle the format of the given file. 
bool Discreet3DSImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler) const
{
	// simple check of file extension is enough for the moment
	std::string::size_type pos = pFile.find_last_of('.');
	// no file extension - can't read
	if( pos == std::string::npos)
		return false;
	std::string extension = pFile.substr( pos);
	for (std::string::iterator i = extension.begin(); i != extension.end();++i)
		*i = ::tolower(*i);

	return (extension == ".3ds");
}

// ------------------------------------------------------------------------------------------------
// Setup configuration properties
void Discreet3DSImporter::SetupProperties(const Importer* pImp)
{
	configSkipPivot = pImp->GetPropertyInteger(AI_CONFIG_IMPORT_3DS_IGNORE_PIVOT,0) ? true : false;
}

// ------------------------------------------------------------------------------------------------
// Imports the given file into the given scene structure. 
void Discreet3DSImporter::InternReadFile( const std::string& pFile, 
	aiScene* pScene, IOSystem* pIOHandler)
{
	StreamReaderLE stream(pIOHandler->Open(pFile,"rb"));
	this->stream = &stream;

	// We should have at least one chunk
	if (stream.GetRemainingSize() < 16)
		throw new ImportErrorException("3DS file is either empty or corrupt: " + pFile);

	// Allocate our temporary 3DS representation
	mScene = new D3DS::Scene();

	// Initialize members
	mLastNodeIndex             = -1;
	mCurrentNode               = new D3DS::Node();
	mRootNode                  = mCurrentNode;
	mRootNode->mHierarchyPos   = -1;
	mRootNode->mHierarchyIndex = -1;
	mRootNode->mParent         = NULL;
	mMasterScale               = 1.0f;
	mBackgroundImage           = "";
	bHasBG                     = false;

	// Parse the file
	ParseMainChunk();

	// Process all meshes in the file. First check whether all
	// face indices haev valid values. The generate our 
	// internal verbose representation. Finally compute normal
	// vectors from the smoothing groups we read from the
	// file.
	for (std::vector<D3DS::Mesh>::iterator i =  mScene->mMeshes.begin(),
		 end = mScene->mMeshes.end(); i != end;++i)
	{
		CheckIndices(*i);
		MakeUnique  (*i);
		ComputeNormalsWithSmoothingsGroups<D3DS::Face>(*i);
	}

	// Apply scaling and offsets to all texture coordinates
	TextureTransform::ApplyScaleNOffset(mScene->mMaterials);

	// Replace all occurences of the default material with a
	// valid material. Generate it if no material containing
	// DEFAULT in its name has been found in the file
	ReplaceDefaultMaterial();

	// Convert the scene from our internal representation to an
	// aiScene object. This involves copying all meshes, lights
	// and cameras to the scene
	ConvertScene(pScene);

	// Generate the node graph for the scene. This is a little bit
	// tricky since we'll need to split some meshes into submeshes
	GenerateNodeGraph(pScene);

	// Now apply the master scaling factor to the scene
	ApplyMasterScale(pScene);

	// We're finished here. Everything destructs automatically
	// and the output scene should be valid.
}

// ------------------------------------------------------------------------------------------------
// Applies a master-scaling factor to the imported scene
void Discreet3DSImporter::ApplyMasterScale(aiScene* pScene)
{
	// There are some 3DS files with a zero scaling factor
	if (!mMasterScale)mMasterScale = 1.0f;
	else mMasterScale = 1.0f / mMasterScale;

	// construct an uniform scaling matrix and multiply with it
	pScene->mRootNode->mTransformation *= aiMatrix4x4( 
		mMasterScale,0.0f, 0.0f, 0.0f,
		0.0f, mMasterScale,0.0f, 0.0f,
		0.0f, 0.0f, mMasterScale,0.0f,
		0.0f, 0.0f, 0.0f, 1.0f);
}

// ------------------------------------------------------------------------------------------------
// Reads a new chunk from the file
void Discreet3DSImporter::ReadChunk(Discreet3DS::Chunk* pcOut)
{
	ai_assert(pcOut != NULL);

	pcOut->Flag = stream->GetI2();
	pcOut->Size = stream->GetI4();

	if (pcOut->Size - sizeof(Discreet3DS::Chunk) > stream->GetRemainingSize())
		throw new ImportErrorException("Chunk is too large");
	
	if (pcOut->Size - sizeof(Discreet3DS::Chunk) > stream->GetRemainingSizeToLimit())
		DefaultLogger::get()->error("3DS: Chunk overflow");
}

// ------------------------------------------------------------------------------------------------
// Skip a chunk
void Discreet3DSImporter::SkipChunk()
{
	Discreet3DS::Chunk psChunk;
	ReadChunk(&psChunk);
	
	stream->IncPtr(psChunk.Size-sizeof(Discreet3DS::Chunk));
	return;
}

// ------------------------------------------------------------------------------------------------
// Process the primary chunk of the file
void Discreet3DSImporter::ParseMainChunk()
{
	ASSIMP_3DS_BEGIN_CHUNK();

	// get chunk type
	switch (chunk.Flag)
	{
	case Discreet3DS::CHUNK_MAIN:
		ParseEditorChunk();
		break;
	};

	ASSIMP_3DS_END_CHUNK();

	// recursively continue processing this hierarchy level
	return ParseMainChunk();
}

// ------------------------------------------------------------------------------------------------
void Discreet3DSImporter::ParseEditorChunk()
{
	ASSIMP_3DS_BEGIN_CHUNK();

	// get chunk type
	switch (chunk.Flag)
	{
	case Discreet3DS::CHUNK_OBJMESH:

		ParseObjectChunk();
		break;

	// NOTE: In several documentations in the internet this
	// chunk appears at different locations
	case Discreet3DS::CHUNK_KEYFRAMER:

		ParseKeyframeChunk();
		break;

	case Discreet3DS::CHUNK_VERSION:
		{
		// print the version number
		char buff[10];
		itoa10(buff,stream->GetI2());
		DefaultLogger::get()->info(std::string("3DS file format version: ") + buff);
		}
		break;
	};
	ASSIMP_3DS_END_CHUNK();

	// recursively continue processing this hierarchy level
	return ParseEditorChunk();
}

// ------------------------------------------------------------------------------------------------
void Discreet3DSImporter::ParseObjectChunk()
{
	ASSIMP_3DS_BEGIN_CHUNK();

	// get chunk type
	switch (chunk.Flag)
	{
	case Discreet3DS::CHUNK_OBJBLOCK:
		{
		unsigned int cnt = 0;
		const char* sz = (const char*)stream->GetPtr();

		// Get the name of the geometry object
		while (stream->GetI1())++cnt;
		ParseChunk(sz,cnt);
		}
		break;

	case Discreet3DS::CHUNK_MAT_MATERIAL:

		// Add a new material to the list
		mScene->mMaterials.push_back(D3DS::Material());
		ParseMaterialChunk();
		break;

	case Discreet3DS::CHUNK_AMBCOLOR:

		// This is the ambient base color of the scene.
		// We add it to the ambient color of all materials
		ParseColorChunk(&mClrAmbient,true);
		if (is_qnan(mClrAmbient.r))
		{
			// We failed to read the ambient base color.
			// Set it to black so it won't have affect
			// the rendering 
			mClrAmbient.r = 0.0f;
			mClrAmbient.g = 0.0f;
			mClrAmbient.b = 0.0f;
		}
		break;

	case Discreet3DS::CHUNK_BIT_MAP:
		{
		// Specifies the background image. The string
		// should already be properly 0 terminated but we
		// need to be sure
		unsigned int cnt = 0;
		const char* sz = (const char*)stream->GetPtr();
		while (stream->GetI1())++cnt;

		mBackgroundImage = std::string(sz,cnt);
		}
		break;


	case Discreet3DS::CHUNK_BIT_MAP_EXISTS:
		bHasBG = true;
		break;


	case Discreet3DS::CHUNK_MASTER_SCALE:

		// Scene master scaling factor
		mMasterScale = stream->GetF4();
		break;


	};
	ASSIMP_3DS_END_CHUNK();

	// recursively continue processing this hierarchy level
	return ParseObjectChunk();
}

// ------------------------------------------------------------------------------------------------
void Discreet3DSImporter::ParseChunk(const char* name, unsigned int num)
{
	ASSIMP_3DS_BEGIN_CHUNK();

	// get chunk type
	switch (chunk.Flag)
	{
	case Discreet3DS::CHUNK_TRIMESH:
		{
		// this starts a new triangle mesh
		mScene->mMeshes.push_back(D3DS::Mesh());
		D3DS::Mesh& m = mScene->mMeshes.back();

		// Setup the name of the mesh
		m.mName = std::string(name, num);

		// Read mesh chunks
		ParseMeshChunk();
		}
		break;

	case Discreet3DS::CHUNK_LIGHT:
		{
		// This starts a new light
		aiLight* light = new aiLight();
		mScene->mLights.push_back(light);

		light->mName.Set(std::string(name, num));

		// First read the position of the light
		light->mPosition.x = stream->GetF4();
		light->mPosition.y = stream->GetF4();
		light->mPosition.z = stream->GetF4();

		// Now check for further subchunks (excluding color)
		int8_t* p = stream->GetPtr();
		ParseLightChunk();

		// Now read the color
		stream->SetPtr(p);
		ParseColorChunk(&light->mColorDiffuse,true);
		if (is_qnan(light->mColorDiffuse.r))
		{
			// it could be there is no color subchunk
			light->mColorDiffuse = aiColor3D(1.f,1.f,1.f);
		}

		// The specular light color is identical to
		// the diffuse light color. The ambient light
		// color is equal to the ambient base color of
		// the whole scene.
		light->mColorSpecular = light->mColorDiffuse;
		light->mColorAmbient  = mClrAmbient;

		if (light->mType == aiLightSource_UNDEFINED)
		{
			// It must be a point light
			light->mType = aiLightSource_POINT;
		}}
		break;

	case Discreet3DS::CHUNK_CAMERA:
		{
		// This starts a new camera
		aiCamera* camera = new aiCamera();
		mScene->mCameras.push_back(camera);

		camera->mName.Set(std::string(name, num));

		// First read the position of the camera
		camera->mPosition.x = stream->GetF4();
		camera->mPosition.y = stream->GetF4();
		camera->mPosition.z = stream->GetF4();

		// Then the camera target
		camera->mLookAt.x = stream->GetF4() - camera->mPosition.x;
		camera->mLookAt.y = stream->GetF4() - camera->mPosition.y;
		camera->mLookAt.z = stream->GetF4() - camera->mPosition.z;

		// We wouldn't need to normalize here, but we do it
		camera->mLookAt.Normalize();

		// And finally - the camera rotation angle, in
		// counter clockwise direction 
		float angle =  AI_DEG_TO_RAD( stream->GetF4() );
		aiQuaternion quat(camera->mLookAt,angle);
		camera->mUp = quat.GetMatrix() * aiVector3D(0.f,1.f,0.f);

		// Read the lense angle
		// TODO
		camera->mHorizontalFOV = AI_DEG_TO_RAD ( stream->GetF4() );
		}
		break;
	};
	ASSIMP_3DS_END_CHUNK();

	// recursively continue processing this hierarchy level
	return ParseChunk(name,num);
}

// ------------------------------------------------------------------------------------------------
void Discreet3DSImporter::ParseLightChunk()
{
	ASSIMP_3DS_BEGIN_CHUNK();
	aiLight* light = mScene->mLights.back();

	// get chunk type
	switch (chunk.Flag)
	{
	case Discreet3DS::CHUNK_SPOTLIGHT:
		{
			// Now we can be sure that the light is a spot light
			light->mType = aiLightSource_SPOT;

			// We wouldn't need to normalize here, but we do it
			light->mDirection.x = stream->GetF4() - light->mPosition.x;
			light->mDirection.y = stream->GetF4() - light->mPosition.y;
			light->mDirection.z = stream->GetF4() - light->mPosition.z;
			light->mDirection.Normalize();

			// Now the hotspot and falloff angles - in degrees
			light->mAngleInnerCone = AI_DEG_TO_RAD( stream->GetF4() );
			light->mAngleOuterCone = AI_DEG_TO_RAD( stream->GetF4() );

			// We assume linear attenuation
			light->mAttenuationLinear = 1;
		}
		break; 
	};

	ASSIMP_3DS_END_CHUNK();

	// recursively continue processing this hierarchy level
	return ParseLightChunk();
}

// ------------------------------------------------------------------------------------------------
void Discreet3DSImporter::ParseKeyframeChunk()
{
	ASSIMP_3DS_BEGIN_CHUNK();

	// get chunk type
	switch (chunk.Flag)
	{
	case Discreet3DS::CHUNK_TRACKCAMTGT:
	case Discreet3DS::CHUNK_SPOTLIGHT:
	case Discreet3DS::CHUNK_TRACKCAMERA:
	case Discreet3DS::CHUNK_TRACKINFO:
	case Discreet3DS::CHUNK_TRACKLIGHT:
	case Discreet3DS::CHUNK_TRACKLIGTGT:

		// this starts a new mesh hierarchy chunk
		ParseHierarchyChunk(chunk.Flag);
		break;
	};

	ASSIMP_3DS_END_CHUNK();

	// recursively continue processing this hierarchy level
	return ParseKeyframeChunk();
}

// ------------------------------------------------------------------------------------------------
// Little helper function for ParseHierarchyChunk
void Discreet3DSImporter::InverseNodeSearch(D3DS::Node* pcNode,D3DS::Node* pcCurrent)
{
	if (!pcCurrent)
	{
		mRootNode->push_back(pcNode);
		return;
	}
	if (pcCurrent->mHierarchyPos == pcNode->mHierarchyPos)
	{
		if(pcCurrent->mParent)pcCurrent->mParent->push_back(pcNode);
		else pcCurrent->push_back(pcNode);
		return;
	}
	return InverseNodeSearch(pcNode,pcCurrent->mParent);
}

// ------------------------------------------------------------------------------------------------
D3DS::Node* FindNode(D3DS::Node* root, const std::string& name)
{
	if (root->mName == name)return root;
	for (std::vector<D3DS::Node*>::iterator it = root->mChildren.begin();
		it != root->mChildren.end(); ++it)
	{
		D3DS::Node* nd;
		if (( nd = FindNode(*it,name)))return nd;
	}
	return NULL;
}

// ------------------------------------------------------------------------------------------------
void Discreet3DSImporter::ParseHierarchyChunk(uint16_t parent)
{
	ASSIMP_3DS_BEGIN_CHUNK();

	// get chunk type
	switch (chunk.Flag)
	{
	case Discreet3DS::CHUNK_TRACKOBJNAME:

		// This is the name of the object to which the track applies
		// The chunk also defines the position of this object in the
		// hierarchy.
		{

		// First of all: get the name of the object
		unsigned int cnt = 0;
		const char* sz = (const char*)stream->GetPtr();

		while (stream->GetI1())++cnt;
		std::string name = std::string(sz,cnt);

		// Now find out whether we have this node already
		// (target animation channels are stored with a
		//  separate object ID)
		D3DS::Node* pcNode = FindNode(mRootNode,name);
		if (!pcNode)
		{
			pcNode = new D3DS::Node();
			pcNode->mName = name;
		}

		// There are two unknown values which we can safely ignore
		stream->IncPtr(4);

		// Now read the hierarchy position of the object
		uint16_t hierarchy = stream->GetI2() + 1;
		pcNode->mHierarchyPos   = hierarchy;
		pcNode->mHierarchyIndex = mLastNodeIndex;

		// And find a proper position in the graph for it
		if (mCurrentNode && mCurrentNode->mHierarchyPos == hierarchy) 
		{
			// add to the parent of the last touched node
			mCurrentNode->mParent->push_back(pcNode);
			mLastNodeIndex++;	
		}
		else if(hierarchy >= mLastNodeIndex)
		{
			// place it at the current position in the hierarchy
			mCurrentNode->push_back(pcNode);
			mLastNodeIndex = hierarchy;
		}
		else
		{
			// need to go back to the specified position in the hierarchy.
			InverseNodeSearch(pcNode,mCurrentNode);
			mLastNodeIndex++;	
		}
		// Make this node the current node
		mCurrentNode = pcNode;
		}
		break;

	case Discreet3DS::CHUNK_TRACKDUMMYOBJNAME:

		// 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");
				break;
			}

			const char* sz = (const char*)stream->GetPtr();
			while (stream->GetI1());
			mCurrentNode->mDummyName = std::string(sz);
		}
		break;

	case Discreet3DS::CHUNK_TRACKPIVOT:

		if ( Discreet3DS::CHUNK_TRACKINFO != parent) 
		{
			DefaultLogger::get()->warn("3DS: Skipping pivot subchunk for non usual object");
			break;
		}

		// Pivot = origin of rotation and scaling
		mCurrentNode->vPivot.x = stream->GetF4();
		mCurrentNode->vPivot.y = stream->GetF4();
		mCurrentNode->vPivot.z = stream->GetF4();
		break;


	case Discreet3DS::CHUNK_TRACKPOS:
		{
		stream->IncPtr(10);
		unsigned int numFrames = stream->GetI2();
		stream->IncPtr(2);

		// This could also be meant as the target position for
		// (targeted) lights and cameras
		std::vector<aiVectorKey>* l;
		if ( Discreet3DS::CHUNK_TRACKCAMTGT == parent || 
			 Discreet3DS::CHUNK_TRACKLIGTGT == parent)
		{
			l = & mCurrentNode->aTargetPositionKeys;
		}
		else l = & mCurrentNode->aPositionKeys;

		for (unsigned int i = 0; i < numFrames;++i)
		{
			unsigned int fidx = stream->GetI2();

			// Setup a new position key
			aiVectorKey v;
			v.mTime = (double)fidx;

			stream->IncPtr(4);
			v.mValue.x = stream->GetF4();
			v.mValue.y = stream->GetF4();
			v.mValue.z = stream->GetF4();

			// Check whether we do already have this keyframe
			for (std::vector<aiVectorKey>::const_iterator
				i =  l->begin();i != l->end();++i)
			{
				if ((*i).mTime == v.mTime)
				{
					DefaultLogger::get()->error("3DS: Found duplicate position keyframe");
					v.mTime = -10e10f;
					break;
				}
			}
			// Add the new keyframe to the list
			if (v.mTime != -10e10f)l->push_back(v);
		}}
		break;

	case Discreet3DS::CHUNK_TRACKROLL:
		{
		// roll keys are accepted for cameras only
		if (parent != Discreet3DS::CHUNK_TRACKCAMERA)
		{
			DefaultLogger::get()->warn("3DS: Ignoring roll track for non-camera object");
			break;
		}

		stream->IncPtr(10);
		unsigned int numFrames = stream->GetI2();
		stream->IncPtr(2);

		for (unsigned int i = 0; i < numFrames;++i)
		{
			unsigned int fidx = stream->GetI2();

			// Setup a new position key
			aiFloatKey v;
			v.first = (double)fidx;

			// This is just a single float 
			stream->IncPtr(4);
			v.second = stream->GetF4();

			// Check whether we do already have this keyframe
			for (std::vector<aiFloatKey>::const_iterator
				i =  mCurrentNode->aCameraRollKeys.begin();
				i != mCurrentNode->aCameraRollKeys.end();++i)
			{
				if ((*i).first == v.first)
				{
					DefaultLogger::get()->error("3DS: Found duplicate camera roll keyframe");
					v.first = -10e10f;
					break;
				}
			}
			// Add the new keyframe to the list
			if (v.first != -10e10f)
				mCurrentNode->aCameraRollKeys.push_back(v);
		}}
		break;

	case Discreet3DS::CHUNK_TRACKROTATE:
		{
		stream->IncPtr(10);
		unsigned int numFrames = stream->GetI2();
		stream->IncPtr(2);

		for (unsigned int i = 0; i < numFrames;++i)
		{
			unsigned int fidx = stream->GetI2();

			aiQuatKey v;
			v.mTime = (double)fidx;

			// The rotation keyframe is given as an axis-angle pair
			float rad = stream->GetF4();
			aiVector3D axis;
			axis.x = stream->GetF4();
			axis.y = stream->GetF4();
			axis.z = stream->GetF4();

			// Construct a rotation quaternion from the axis-angle pair
			v.mValue = aiQuaternion(axis,rad);

			// check whether we do already have this keyframe
			for (std::vector<aiQuatKey>::const_iterator
				i =  mCurrentNode->aRotationKeys.begin();
				i != mCurrentNode->aRotationKeys.end();++i)
			{
				if ((*i).mTime == v.mTime)
				{
					DefaultLogger::get()->error("3DS: Found duplicate rotation keyframe");
					v.mTime = -10e10f;
					break;
				}
			}
			// add the new keyframe to the list
			if (v.mTime != -10e10f)
				mCurrentNode->aRotationKeys.push_back(v);
		}}
		break;

	case Discreet3DS::CHUNK_TRACKSCALE:
		{
		unsigned int invalid = 0;

		stream->IncPtr(10);
		unsigned int numFrames = stream->GetI2();
		stream->IncPtr(2);

		for (unsigned int i = 0; i < numFrames;++i)
		{
			unsigned int fidx = stream->GetI2();

			// Setup a new key
			aiVectorKey v;
			v.mTime = (double)fidx;

			// ... and read its value
			v.mValue.x = stream->GetF4();
			v.mValue.y = stream->GetF4();
			v.mValue.z = stream->GetF4();

			// check whether we do already have this keyframe
			for (std::vector<aiVectorKey>::const_iterator
				i =  mCurrentNode->aScalingKeys.begin();
				i != mCurrentNode->aScalingKeys.end();++i)
			{
				if ((*i).mTime == v.mTime)
				{
					DefaultLogger::get()->error("3DS: Found duplicate scaling keyframe");
					v.mTime = -10e10f;
					break;
				}
			}
			// add the new keyframe
			if (v.mTime != -10e10f)
				mCurrentNode->aScalingKeys.push_back(v);

			// Check whether this is a zero scaling keyframe
			if (!v.mValue.x && !v.mValue.y && !v.mValue.z)
			{
				DefaultLogger::get()->warn("3DS: Found zero scaled axis in scaling keyframe");
				++invalid;
			}
		}
		// there are 3DS files that have only zero scalings
		if (numFrames == invalid)
		{
			DefaultLogger::get()->warn("3DS: All scaling keys are zero. Ignoring them ...");
			mCurrentNode->aScalingKeys.clear();
		}}
		break;
	};

	ASSIMP_3DS_END_CHUNK();

	// recursively continue processing this hierarchy level
	return ParseHierarchyChunk(parent);
}
// ------------------------------------------------------------------------------------------------
void Discreet3DSImporter::ParseFaceChunk()
{
	ASSIMP_3DS_BEGIN_CHUNK();

	// Get the mesh we're currently working on
	D3DS::Mesh& mMesh = mScene->mMeshes.back();

	// Get chunk type
	switch (chunk.Flag)
	{

	case Discreet3DS::CHUNK_SMOOLIST:
		{
		// This is the list of smoothing groups - a bitfield for
		// every frame. Up to 32 smoothing groups assigned to a
		// face.
		unsigned int num = chunkSize/4, m = 0;
		for (std::vector<D3DS::Face>::iterator i =  mMesh.mFaces.begin();
			 m != num;++i, ++m)
		{
			// nth bit is set for nth smoothing group
			(*i).iSmoothGroup = stream->GetI4();
		}}
		break;

	case Discreet3DS::CHUNK_FACEMAT:
		{
		// at fist an asciiz with the material name
		const char* sz = (const char*)stream->GetPtr();
		while (stream->GetI1());

		// find the index of the material
		unsigned int idx = 0xcdcdcdcd, cnt = 0;
		for (std::vector<D3DS::Material>::const_iterator
			i =  mScene->mMaterials.begin();
			i != mScene->mMaterials.end();++i,++cnt)
		{
			// compare case-independent to be sure it works
			if ((*i).mName.length() && !ASSIMP_stricmp(sz, (*i).mName.c_str()))
			{
				idx = cnt;
				break;
			}
		}
		if (0xcdcdcdcd == idx)
		{
			DefaultLogger::get()->error(std::string("3DS: Unknown material: ") + sz);

			// This material is not known. Ignore this. We will later
			// assign the default material to all faces using *this*
			// material. Use 0xcdcdcdcd as special value to indicate
			// this.
		}

		// Now continue and read all material indices
		cnt = stream->GetI2();
		for (unsigned int i = 0; i < cnt;++i)
		{
			unsigned int fidx = (uint16_t)stream->GetI2();

			// check range
			if (fidx >= mMesh.mFaceMaterials.size())
			{
				DefaultLogger::get()->error("3DS: Invalid face index in face material list");
			}
			else mMesh.mFaceMaterials[fidx] = idx;
		}}
		break;
	};
	ASSIMP_3DS_END_CHUNK();

	// recursively continue processing this hierarchy level
	return ParseFaceChunk();
}
// ------------------------------------------------------------------------------------------------
void Discreet3DSImporter::ParseMeshChunk()
{
	ASSIMP_3DS_BEGIN_CHUNK();

	// Get the mesh we're currently working on
	D3DS::Mesh& mMesh = mScene->mMeshes.back();

	// get chunk type
	switch (chunk.Flag)
	{
	case Discreet3DS::CHUNK_VERTLIST:
		{
		// This is the list of all vertices in the current mesh
		int num = stream->GetI2();
		while (num-- > 0)
		{
			aiVector3D v;
			v.x = stream->GetF4();
			v.y = stream->GetF4();
			v.z = stream->GetF4();
			mMesh.mPositions.push_back(v);
		}}
		break;
	case Discreet3DS::CHUNK_TRMATRIX:
		{
		// This is the RLEATIVE transformation matrix of the
		// current mesh. However, all vertices are pretransformed
		mMesh.mMat.a1 = stream->GetF4();
		mMesh.mMat.b1 = stream->GetF4();
		mMesh.mMat.c1 = stream->GetF4();
		mMesh.mMat.a2 = stream->GetF4();
		mMesh.mMat.b2 = stream->GetF4();
		mMesh.mMat.c2 = stream->GetF4();
		mMesh.mMat.a3 = stream->GetF4();
		mMesh.mMat.b3 = stream->GetF4();
		mMesh.mMat.c3 = stream->GetF4();
		mMesh.mMat.a4 = stream->GetF4();
		mMesh.mMat.b4 = stream->GetF4();
		mMesh.mMat.c4 = stream->GetF4();

		// Now check whether the matrix has got a negative determinant
		// If yes, we need to flip all vertices' x axis ....
		// From lib3ds, mesh.c
		if (mMesh.mMat.Determinant() < 0.0f)
		{
			// Compute the inverse of the matrix
			aiMatrix4x4 mInv = mMesh.mMat;
			mInv.Inverse();

			aiMatrix4x4 mMe = mMesh.mMat;
			mMe.a1 *= -1.0f;
			mMe.b1 *= -1.0f;
			mMe.c1 *= -1.0f;
			mMe.d1 *= -1.0f;
			mInv = mInv * mMe;

			// Now transform all vertices
			for (unsigned int i = 0; i < (unsigned int)mMesh.mPositions.size();++i)
			{
				aiVector3D a,c;
				a = mMesh.mPositions[i];
				c[0]= mInv[0][0]*a[0] + mInv[1][0]*a[1] + mInv[2][0]*a[2] + mInv[3][0];
				c[1]= mInv[0][1]*a[0] + mInv[1][1]*a[1] + mInv[2][1]*a[2] + mInv[3][1];
				c[2]= mInv[0][2]*a[0] + mInv[1][2]*a[1] + mInv[2][2]*a[2] + mInv[3][2];
				mMesh.mPositions[i] = c;
			}
		}}
		break;

	case Discreet3DS::CHUNK_MAPLIST:
		{
		// This is the list of all UV coords in the current mesh
		int num = stream->GetI2();
		while (num-- > 0)
		{
			aiVector2D v;
			v.x = stream->GetF4();
			v.y = stream->GetF4();
			mMesh.mTexCoords.push_back(v);
		}}
		break;

	case Discreet3DS::CHUNK_FACELIST:
		{
		// This is the list of all faces in the current mesh
		int num = stream->GetI2();
		while (num-- > 0)
		{
			// 3DS faces are ALWAYS triangles
			mMesh.mFaces.push_back(D3DS::Face());
			D3DS::Face& sFace = mMesh.mFaces.back();

			sFace.mIndices[0] = (uint16_t)stream->GetI2();
			sFace.mIndices[1] = (uint16_t)stream->GetI2();
			sFace.mIndices[2] = (uint16_t)stream->GetI2();

			stream->IncPtr(2); // skip edge visibility flag
		}

		// Resize the material array (0xcdcdcdcd marks the
		// default material; so if a face is not referenced
		// by a material $$DEFAULT will be assigned to it)
		mMesh.mFaceMaterials.resize(mMesh.mFaces.size(),0xcdcdcdcd);

		// Larger 3DS files could have multiple FACE chunks here
		chunkSize = stream->GetRemainingSizeToLimit();
		if (chunkSize > sizeof(Discreet3DS::Chunk))
			ParseFaceChunk();
		}
		break;
	};
	ASSIMP_3DS_END_CHUNK();

	// recursively continue processing this hierarchy level
	return ParseMeshChunk();
}

// ------------------------------------------------------------------------------------------------
void Discreet3DSImporter::ParseMaterialChunk()
{
	ASSIMP_3DS_BEGIN_CHUNK();

	// get chunk type
	switch (chunk.Flag)
	{
	case Discreet3DS::CHUNK_MAT_MATNAME:

		{
		// The material name string is already zero-terminated, but
		// we need to be sure ...
		const char* sz = (const char*)stream->GetPtr();
		unsigned int cnt = 0;
		while (stream->GetI1())++cnt;

		if (!cnt)
		{
			// This may not be, we use the default name instead
			DefaultLogger::get()->error("3DS: Empty material name");
		}
		else mScene->mMaterials.back().mName = std::string(sz,cnt);
		}
		break;

	case Discreet3DS::CHUNK_MAT_DIFFUSE:
		{
		// This is the diffuse material color
		aiColor3D* pc = &mScene->mMaterials.back().mDiffuse;
		ParseColorChunk(pc);
		if (is_qnan(pc->r))
		{
			// color chunk is invalid. Simply ignore it
			DefaultLogger::get()->error("Unable to read DIFFUSE chunk");
			pc->r = pc->g = pc->b = 1.0f;
		}}
		break;

	case Discreet3DS::CHUNK_MAT_SPECULAR:
		{
		// This is the specular material color
		aiColor3D* pc = &mScene->mMaterials.back().mSpecular;
		ParseColorChunk(pc);
		if (is_qnan(pc->r))
		{
			// color chunk is invalid. Simply ignore it
			DefaultLogger::get()->error("Unable to read SPECULAR chunk");
			pc->r = pc->g = pc->b = 1.0f;
		}}
		break;

	case Discreet3DS::CHUNK_MAT_AMBIENT:
		{
		// This is the ambient material color
		aiColor3D* pc = &mScene->mMaterials.back().mAmbient;
		ParseColorChunk(pc);
		if (is_qnan(pc->r))
		{
			// color chunk is invalid. Simply ignore it
			DefaultLogger::get()->error("Unable to read AMBIENT chunk");
			pc->r = pc->g = pc->b = 0.0f;
		}}
		break;

	case Discreet3DS::CHUNK_MAT_SELF_ILLUM:
		{
		// This is the emissive material color
		aiColor3D* pc = &mScene->mMaterials.back().mEmissive;
		ParseColorChunk(pc);
		if (is_qnan(pc->r))
		{
			// color chunk is invalid. Simply ignore it
			DefaultLogger::get()->error("Unable to read EMISSIVE chunk");
			pc->r = pc->g = pc->b = 0.0f;
		}}
		break;

	case Discreet3DS::CHUNK_MAT_TRANSPARENCY:
		{
		// This is the material's transparency
		float* pcf = &mScene->mMaterials.back().mTransparency;
		*pcf = ParsePercentageChunk();

		// NOTE: transparency, not opacity
		if (is_qnan(*pcf))*pcf = 1.0f;
		else *pcf = 1.0f - *pcf * (float)0xFFFF / 100.0f;
		}
		break;

	case Discreet3DS::CHUNK_MAT_SHADING:
		// This is the material shading mode
		mScene->mMaterials.back().mShading = (D3DS::Discreet3DS::shadetype3ds)stream->GetI2();
		break;

	case Discreet3DS::CHUNK_MAT_TWO_SIDE:
		// This is the two-sided flag
		mScene->mMaterials.back().mTwoSided = true;
		break;

	case Discreet3DS::CHUNK_MAT_SHININESS:
		{ // This is the shininess of the material
		float* pcf = &mScene->mMaterials.back().mSpecularExponent;
		*pcf = ParsePercentageChunk();
		if (is_qnan(*pcf))*pcf = 0.0f;
		else *pcf *= (float)0xFFFF;
		}
		break;

	case Discreet3DS::CHUNK_MAT_SHININESS_PERCENT:
		{ // This is the shininess strength of the material
		float* pcf = &mScene->mMaterials.back().mShininessStrength;
		*pcf = ParsePercentageChunk();
		if (is_qnan(*pcf))*pcf = 0.0f;
		else *pcf *= (float)0xffff / 100.0f;
		}
		break;

	case Discreet3DS::CHUNK_MAT_SELF_ILPCT:
		{ // This is the self illumination strength of the material
		// TODO: need to multiply with emissive base color?
		float* pcf = &mScene->mMaterials.back().sTexEmissive.mTextureBlend;
		*pcf = ParsePercentageChunk();
		if (is_qnan(*pcf))*pcf = 0.0f;
		else *pcf = *pcf * (float)0xFFFF / 100.0f;
		}
		break;

	// Parse texture chunks
	case Discreet3DS::CHUNK_MAT_TEXTURE:
		// Diffuse texture
		ParseTextureChunk(&mScene->mMaterials.back().sTexDiffuse);
		break;
	case Discreet3DS::CHUNK_MAT_BUMPMAP:
		// Height map
		ParseTextureChunk(&mScene->mMaterials.back().sTexBump);
		break;
	case Discreet3DS::CHUNK_MAT_OPACMAP:
		// Opacity texture
		ParseTextureChunk(&mScene->mMaterials.back().sTexOpacity);
		break;
	case Discreet3DS::CHUNK_MAT_MAT_SHINMAP:
		// Shininess map
		ParseTextureChunk(&mScene->mMaterials.back().sTexShininess);
		break;
	case Discreet3DS::CHUNK_MAT_SPECMAP:
		// Specular map
		ParseTextureChunk(&mScene->mMaterials.back().sTexSpecular);
		break;
	case Discreet3DS::CHUNK_MAT_SELFIMAP:
		// Self-illumination (emissive) map
		ParseTextureChunk(&mScene->mMaterials.back().sTexEmissive);
		break;
	case Discreet3DS::CHUNK_MAT_REFLMAP:

		// Reflection map - no support in Assimp
		DefaultLogger::get()->warn("3DS: Found reflection map in file. This is not supported");

		break;
	};
	ASSIMP_3DS_END_CHUNK();

	// recursively continue processing this hierarchy level
	return ParseMaterialChunk();
}
// ------------------------------------------------------------------------------------------------
void Discreet3DSImporter::ParseTextureChunk(D3DS::Texture* pcOut)
{
	ASSIMP_3DS_BEGIN_CHUNK();

	// get chunk type
	switch (chunk.Flag)
	{
	case Discreet3DS::CHUNK_MAPFILE:
		{
		// The material name string is already zero-terminated, but
		// we need to be sure ...
		const char* sz = (const char*)stream->GetPtr();
		unsigned int cnt = 0;
		while (stream->GetI1())++cnt;
		pcOut->mMapName = std::string(sz,cnt);
		}
		break;


	case Discreet3DS::CHUNK_PERCENTF:
		// Manually parse the blend factor
		pcOut->mTextureBlend = stream->GetF4();
		break;

	case Discreet3DS::CHUNK_PERCENTW:
		// Manually parse the blend factor
		pcOut->mTextureBlend = (float)((uint16_t)stream->GetI2()) / 100.0f;
		break;

	case Discreet3DS::CHUNK_MAT_MAP_USCALE:
		// Texture coordinate scaling in the U direction
		pcOut->mScaleU = stream->GetF4();
		if (0.0f == pcOut->mScaleU)
		{
			DefaultLogger::get()->warn("Texture coordinate scaling in the "
				"x direction is zero. Assuming this should be 1.0 ... ");
			pcOut->mScaleU = 1.0f;
		}
		break;
	case Discreet3DS::CHUNK_MAT_MAP_VSCALE:
		// Texture coordinate scaling in the V direction
		pcOut->mScaleV = stream->GetF4();
		if (0.0f == pcOut->mScaleV)
		{
			DefaultLogger::get()->warn("Texture coordinate scaling in the "
				"y direction is zero. Assuming this should be 1.0 ... ");
			pcOut->mScaleV = 1.0f;
		}
		break;

	case Discreet3DS::CHUNK_MAT_MAP_UOFFSET:
		// Texture coordinate offset in the U direction
		pcOut->mOffsetU = stream->GetF4();
		break;

	case Discreet3DS::CHUNK_MAT_MAP_VOFFSET:
		// Texture coordinate offset in the V direction
		pcOut->mOffsetV = stream->GetF4();
		break;

	case Discreet3DS::CHUNK_MAT_MAP_ANG:
		// Texture coordinate rotation, CCW in radians
		pcOut->mRotation = stream->GetF4();
		break;

	case Discreet3DS::CHUNK_MAT_MAP_TILING:
		{
		uint16_t iFlags = stream->GetI2();

		// check whether the mirror flag is set
		if (iFlags & 0x2u)
		{
			pcOut->mMapMode = aiTextureMapMode_Mirror;
		}
		// assume that "decal" means clamping ...
		else if (iFlags & 0x10u && iFlags & 0x1u)
		{
			pcOut->mMapMode = aiTextureMapMode_Clamp;
		}}
		break;
	};

	ASSIMP_3DS_END_CHUNK();

	// recursively continue processing this hierarchy level
	return ParseTextureChunk(pcOut);
}

// ------------------------------------------------------------------------------------------------
// Read a percentage chunk
float Discreet3DSImporter::ParsePercentageChunk()
{
	Discreet3DS::Chunk chunk;
	ReadChunk(&chunk);

	if (Discreet3DS::CHUNK_PERCENTF == chunk.Flag)
	{
		return stream->GetF4();
	}
	else if (Discreet3DS::CHUNK_PERCENTW == chunk.Flag)
	{
		return (float)((uint16_t)stream->GetI2()) / (float)0xFFFF;
	}
	return std::numeric_limits<float>::quiet_NaN();
}

// ------------------------------------------------------------------------------------------------
// Read a color chunk. If a percentage chunk is found instead, it will be converted
// to a grayscale color value
void Discreet3DSImporter::ParseColorChunk(aiColor3D* p_pcOut,
	bool p_bAcceptPercent)
{
	ai_assert(p_pcOut != NULL);

	// error return value
	static const aiColor3D clrError = aiColor3D(std::numeric_limits<float>::quiet_NaN(),
		std::numeric_limits<float>::quiet_NaN(),
		std::numeric_limits<float>::quiet_NaN());

	Discreet3DS::Chunk chunk;
	ReadChunk(&chunk);
	const unsigned int diff = chunk.Size - sizeof(Discreet3DS::Chunk);

	bool bGamma = false;

	// Get the type of the chunk
	switch(chunk.Flag)
	{
	case Discreet3DS::CHUNK_LINRGBF:
		bGamma = true;

	case Discreet3DS::CHUNK_RGBF:
		if (sizeof(float) * 3 > diff)
		{
			*p_pcOut = clrError;
			return;
		}
		p_pcOut->r = stream->GetF4();
		p_pcOut->g = stream->GetF4();
		p_pcOut->b = stream->GetF4();
		break;

	case Discreet3DS::CHUNK_LINRGBB:
		bGamma = true;
	case Discreet3DS::CHUNK_RGBB:
		if (sizeof(char) * 3 > diff)
		{
			*p_pcOut = clrError;
			return;
		}
		p_pcOut->r = (float)(uint8_t)stream->GetI1() / 255.0f;
		p_pcOut->g = (float)(uint8_t)stream->GetI1() / 255.0f;
		p_pcOut->b = (float)(uint8_t)stream->GetI1() / 255.0f;
		break;

	// Percentage chunks: accepted to be compatible with various
	// .3ds files with very curious content
	case Discreet3DS::CHUNK_PERCENTF:
		if (p_bAcceptPercent && 4 <= diff)
		{
			p_pcOut->r = stream->GetF4();
			p_pcOut->g = p_pcOut->b = p_pcOut->r;
			break;
		}
		*p_pcOut = clrError;
		return;

	case Discreet3DS::CHUNK_PERCENTW:
		if (p_bAcceptPercent && 1 <= diff)
		{
			p_pcOut->r = (float)(uint8_t)stream->GetI1() / 255.0f;
			p_pcOut->g = p_pcOut->b = p_pcOut->r;
			break;
		}
		*p_pcOut = clrError;
		return;

	default:

		// skip unknown chunks, hope this won't cause any problems.
		return ParseColorChunk(p_pcOut,p_bAcceptPercent);
	};

	// Do a gamma correction ... I'm not sure whether this is correct
	// or not but I'm too tired now to think of it
	if (bGamma)
	{
		p_pcOut->r = powf(p_pcOut->r, 1.0f / 2.2f);
		p_pcOut->g = powf(p_pcOut->g, 1.0f / 2.2f);
		p_pcOut->b = powf(p_pcOut->b, 1.0f / 2.2f);
	}
	return;
}