void ControllerExporter::determineReferencedJoints(ExportNode* exportNode, SkinController* skinController)
	{
		ISkinInterface* skinInterface = getISkinInterface( exportNode, skinController);

		int jointCount = skinInterface->getNumBones();

		ExportNodeSet referencedJoints;

		for (int i = 0; i <  jointCount; ++i)
		{
			// there should not be any null bone.
			// the ISkin::GetBone, not GetBoneFlat, function is called here.
			INode* boneNode = skinInterface->getBone(i);
			assert(boneNode);

			ExportNode* jointExportNode = mExportSceneGraph->getExportNode(boneNode);
			assert(jointExportNode);
			jointExportNode->setIsInVisualScene( true );

			referencedJoints.insert(jointExportNode);
		}

		determineSkeletonRoots(referencedJoints, exportNode->getControllerList());
		skinInterface->releaseMe();
	}
    //---------------------------------------------------------------
    ExportNode * ExportSceneGraph::create ( INode *iNode, ExportNode* parent, bool& isInVisualScene)
    {

        ExportNode * exportNode = new ExportNode ( iNode, parent );

		isInVisualScene = isNodeInVisualScene(iNode);

        int numberOfChildren = iNode->NumberOfChildren();

        for ( int i = 0; i < numberOfChildren; ++i )
        {
            INode * child = iNode->GetChildNode ( i );
			bool isChildInVisualScene;
            ExportNode * childExportNode = create ( child, exportNode, isChildInVisualScene );

			exportNode->add( childExportNode );

            if ( isChildInVisualScene )
               isInVisualScene = true;
        }

		exportNode->setId ( mNodeIdList.addId ( NativeString(iNode->GetName()) ) );
		mINodeExportNodeMap[iNode] = exportNode;
		exportNode->createControllerList();
		//createBones(exportNode);
		exportNode->setIsInVisualScene(isInVisualScene);

		return exportNode;
    }
	//---------------------------------------------------------------
	void ExportSceneGraph::findReferencedObjects( ExportNode* exportNode )
	{
		if ( exportNode->hasControllers() )
		{
			ControllerList* controllerList = exportNode->getControllerList();

			size_t controllerCount = controllerList->getControllerCount();

			for ( size_t j = 0; j < controllerCount; ++j)
			{
				Controller* controller = controllerList->getController(j);

				if ( controller->getType() != Controller::MORPH )
					continue;

				MorphController* morphController = (MorphController*)controller;

				MorphR3* morpher = morphController->getMorph();

				size_t channelBankCount = morpher->chanBank.size();
				for ( size_t i = 0; i<channelBankCount; ++i)
				{
					morphChannel& channel = morpher->chanBank[i];

					if (!channel.mActive || channel.mNumPoints == 0) 
						continue;

					INode* targetINode = channel.mConnection;

					if ( !targetINode )
					{
						MorphControllerHelperGeometry morphControllerHelperGeometry;
						morphControllerHelperGeometry.exportNode = exportNode;
						morphControllerHelperGeometry.morphController = morphController;
						morphControllerHelperGeometry.controllerId = ControllerExporter::getControllerId(*exportNode, controllerCount - j, controllerList->getController(j)->getType());
						morphControllerHelperGeometry.channelBankindex = i;
						mMorphControllerHelperGeometryList.push_back(morphControllerHelperGeometry);
					}
					else
					{
						ExportNode* targetExportNode = getExportNode(targetINode);
						targetExportNode->setIsReferenced(true);
					}
				}

			}
		}

		size_t numberOfChildren = exportNode->getNumberOfChildren();

		for ( size_t i = 0; i < numberOfChildren; ++i )
			findReferencedObjects(exportNode->getChild(i));

	}
	//---------------------------------------------------------------
	String LightExporter::getLightId( const ExportNode& exportNode )
	{
		return exportNode.getId() + LIGHT_ID_SUFFIX;
	}
	//---------------------------------------------------------------
	COLLADASW::String GeometriesExporter::getGeometryId( const ExportNode& exportNode )
	{
		return COLLADASW::LibraryGeometries::GEOMETRY_ID_PRAEFIX + exportNode.getId() ;
	}
	//---------------------------------------------------------------
	void ControllerExporter::exportMorphController( ExportNode* exportNode, MorphController* morphController, const String& controllerId, const String& morphSource )
	{
		MorphR3* morpher = morphController->getMorph();


		FloatList listOfWeights;
		StringList listOfTargetIds;

		String weightsId = controllerId + WEIGHTS_SOURCE_ID_SUFFIX;

		size_t channelBankCount = morpher->chanBank.size();
		for ( size_t i = 0; i<channelBankCount; ++i)
		{
			morphChannel& channel = morpher->chanBank[i];
			
			if (!channel.mActive || channel.mNumPoints == 0) 
				continue;

			INode* targetINode = channel.mConnection;

			listOfWeights.push_back(ConversionFunctors::fromPercent(channel.cblock->GetFloat(morphChannel::cblock_weight_index, mDocumentExporter->getOptions().getAnimationStart())));

			Control* weightController = channel.cblock->GetController(morphChannel::cblock_weight_index);
			mDocumentExporter->getAnimationExporter()->addAnimatedFloat(weightController, weightsId, EMPTY_STRING, (int)i, true, &ConversionFunctors::fromPercent );

			if ( !targetINode )
			{
				MorphControllerHelperGeometry morphControllerHelperGeometry;
				morphControllerHelperGeometry.exportNode = exportNode;
				morphControllerHelperGeometry.controllerId = controllerId;
				morphControllerHelperGeometry.morphController = morphController;
				morphControllerHelperGeometry.channelBankindex = i;
				
				String targetId = ExportSceneGraph::getMorphControllerHelperId(morphControllerHelperGeometry);
				listOfTargetIds.push_back(targetId);
			}
			else
			{
				ExportNode* targetExportNode = mExportSceneGraph->getExportNode(targetINode);
				assert(targetExportNode);

				ExportNode* geometryExportNode = mDocumentExporter->getExportedObjectExportNode(ObjectIdentifier(targetExportNode->getInitialPose()));
				assert( geometryExportNode );
//				listOfTargetIds.push_back(geometryExportNode);
				listOfTargetIds.push_back(GeometriesExporter::getGeometryId(*geometryExportNode));
			}
		}

		openMorph(controllerId, EMPTY_STRING, morphSource);

		//export weights source
		String targetId = controllerId + TARGETS_SOURCE_ID_SUFFIX;
		COLLADASW::IdRefSource targetsSource(mSW);
		targetsSource.setId(targetId);
		targetsSource.setArrayId(targetId + ARRAY_ID_SUFFIX);
		targetsSource.setAccessorStride(1);
		targetsSource.getParameterNameList().push_back("MORPH_TARGET");
		targetsSource.setAccessorCount((unsigned long)listOfTargetIds.size());
		targetsSource.prepareToAppendValues();

		for ( StringList::const_iterator it = listOfTargetIds.begin(); it != listOfTargetIds.end(); ++it)
			targetsSource.appendValues(*it);

		targetsSource.finish();


		//export weights source
		COLLADASW::FloatSource weightsSource(mSW);
		weightsSource.setId(weightsId);
		weightsSource.setArrayId(weightsId + ARRAY_ID_SUFFIX);
		weightsSource.setAccessorStride(1);
		weightsSource.getParameterNameList().push_back("MORPH_WEIGHT");
		weightsSource.setAccessorCount((unsigned long)listOfWeights.size());
		weightsSource.prepareToAppendValues();
		
		for ( FloatList::const_iterator it = listOfWeights.begin(); it != listOfWeights.end(); ++it)
			weightsSource.appendValues(*it);
		
		weightsSource.finish();


		COLLADASW::TargetsElement targets(mSW);
		targets.getInputList().push_back(COLLADASW::Input(COLLADASW::InputSemantic::MORPH_TARGET, "#" + targetId));
		targets.getInputList().push_back(COLLADASW::Input(COLLADASW::InputSemantic::MORPH_WEIGHT, "#" + weightsId));
		targets.add();

		closeMorph();

	}
	//---------------------------------------------------------------
	void ControllerExporter::exportSkinController( ExportNode* exportNode, SkinController* skinController, const String& controllerId, const String& skinSource )
	{
		INode* iNode = exportNode->getINode();

		if ( !skinController )
			return;

		// We cannot use skin->GetContextInterface to get ISkinContextData if we are exporting an XRef, since we cannot access
		// the INode the object belongs to in the referenced file. To solve this problem, we temporarily create an INode, that references
		// the object and delete it immediately. 
		ISkinInterface* skinInterface = getISkinInterface( exportNode, skinController );

		if ( !skinInterface )
			return;

		openSkin(controllerId, skinSource);
	
		Matrix3 bindShapeTransformationMatrix;
		skinInterface->getSkinInitTM(bindShapeTransformationMatrix, true);	
		double  bindShapeTransformationArray[4][4];
		VisualSceneExporter::matrix3ToDouble4x4(bindShapeTransformationArray, bindShapeTransformationMatrix);

		addBindShapeTransform(bindShapeTransformationArray);

		int jointCount = skinInterface->getNumBones();
		INodeList boneINodes;


		// Export joints source
		String jointsId = controllerId + JOINTS_SOURCE_ID_SUFFIX;
		COLLADASW::NameSource jointSource(mSW);
		jointSource.setId(jointsId);
		jointSource.setArrayId(jointsId + ARRAY_ID_SUFFIX);
		jointSource.setAccessorStride(1);
		jointSource.getParameterNameList().push_back("JOINT");
		jointSource.setAccessorCount(jointCount);
		jointSource.prepareToAppendValues();

		for (int i = 0; i <  jointCount; ++i)
		{
			// there should not be any null bone.
			// the ISkin::GetBone, not GetBoneFlat, function is called here.
			INode* boneNode = skinInterface->getBone(i);
			assert(boneNode);
			boneINodes.push_back(boneNode);

			ExportNode* jointExportNode = mExportSceneGraph->getExportNode(boneNode);
			assert(jointExportNode);

			if ( !jointExportNode->hasSid() )
				jointExportNode->setSid(mExportSceneGraph->createJointSid());

			jointExportNode->setIsJoint();

			jointSource.appendValues(jointExportNode->getSid());
		}
		jointSource.finish();

		determineReferencedJoints(exportNode, skinController);

		//export inverse bind matrix source
		String inverseBindMatrixId = controllerId + BIND_POSES_SOURCE_ID_SUFFIX;
		COLLADASW::Float4x4Source inverseBindMatrixSource(mSW);
		inverseBindMatrixSource.setId(inverseBindMatrixId);
		inverseBindMatrixSource.setArrayId(inverseBindMatrixId + ARRAY_ID_SUFFIX);
		inverseBindMatrixSource.setAccessorStride(16);
		inverseBindMatrixSource.getParameterNameList().push_back("TRANSFORM");
		inverseBindMatrixSource.setAccessorCount(jointCount);
		inverseBindMatrixSource.prepareToAppendValues();

		for (int i = 0; i <  jointCount; ++i)
		{
			INode* boneNode = boneINodes[i];

			Matrix3 bindPose;
			if ( !skinInterface->getBoneInitTM(boneNode, bindPose) )
			{
				bindPose = VisualSceneExporter::getWorldTransform( boneNode, mDocumentExporter->getOptions().getAnimationStart() );
			}
			bindPose.Invert();

			double bindPoseArray[4][4];
			VisualSceneExporter::matrix3ToDouble4x4(bindPoseArray, bindPose);
			inverseBindMatrixSource.appendValues(bindPoseArray);
		}
		inverseBindMatrixSource.finish();

		int vertexCount = skinInterface->getNumVertices();
		
		//count weights, excluding the ones equals one
		int weightsCount = 1;
		for (int i = 0; i < vertexCount; ++i)
		{
			int jointCount = skinInterface->getNumAssignedBones(i);
			for (int p = 0; p < jointCount; ++p)
			{
				float weight = skinInterface->getBoneWeight(i, p);
				if ( !COLLADASW::MathUtils::equals(weight, 1.0f) )
					weightsCount++;
			}
		}

		//export weights source
		String weightsId = controllerId + WEIGHTS_SOURCE_ID_SUFFIX;
		COLLADASW::FloatSource weightsSource(mSW);
		weightsSource.setId(weightsId);
		weightsSource.setArrayId(weightsId + ARRAY_ID_SUFFIX);
		weightsSource.setAccessorStride(1);
		weightsSource.getParameterNameList().push_back("WEIGHT");
		weightsSource.setAccessorCount(weightsCount);
		weightsSource.prepareToAppendValues();

		weightsSource.appendValues(1.0);
		for (int i = 0; i < vertexCount; ++i)
		{
			int jointCount = skinInterface->getNumAssignedBones(i);
			for (int p = 0; p < jointCount; ++p)
			{
				float weight = skinInterface->getBoneWeight(i, p);
				if ( !COLLADASW::MathUtils::equals(weight, 1.0f) )
					weightsSource.appendValues(weight);
			}
		}
		weightsSource.finish();

		COLLADASW::JointsElement joints(mSW);
		joints.getInputList().push_back(COLLADASW::Input(COLLADASW::InputSemantic::JOINT, "#" + jointsId));
		joints.getInputList().push_back(COLLADASW::Input(COLLADASW::InputSemantic::BINDMATRIX, "#" + inverseBindMatrixId));
		joints.add();

		COLLADASW::VertexWeightsElement vertexWeights(mSW);
		COLLADASW::Input weightInput(COLLADASW::InputSemantic::WEIGHT, "#" + weightsId);
		vertexWeights.getInputList().push_back(COLLADASW::Input(COLLADASW::InputSemantic::JOINT, "#" + jointsId, 0));
		vertexWeights.getInputList().push_back(COLLADASW::Input(COLLADASW::InputSemantic::WEIGHT, "#" + weightsId, 1));
		vertexWeights.setCount(vertexCount);

		vertexWeights.prepareToAppendVCountValues();

		for (int i = 0; i < vertexCount; ++i)
			vertexWeights.appendValues(skinInterface->getNumAssignedBones(i));

		vertexWeights.CloseVCountAndOpenVElement();

		int currentIndex = 1;
		for (int i = 0; i < vertexCount; ++i)
		{
			int jointCount = skinInterface->getNumAssignedBones(i);
			for (int p = 0; p < jointCount; ++p)
			{
				vertexWeights.appendValues(skinInterface->getAssignedBone(i, p));
				float weight = skinInterface->getBoneWeight(i, p);
				if ( !COLLADASW::MathUtils::equals(weight, 1.0f) )
				{
					vertexWeights.appendValues(currentIndex++);
				}
				else
				{
					vertexWeights.appendValues(0);
				}
			}
		}
		vertexWeights.finish();
		closeSkin();
		skinInterface->releaseMe();
	}
	//---------------------------------------------------------------
	void ControllerList::resolveControllers( const ExportNode& exportNode )
	{
		Object* object = exportNode.getINode()->GetObjectRef();

		if ( !object )
			return;

		if ( exportNode.getIsXRefObject() )
			object = XRefFunctions::getXRefItemSource(object);

		if ( !object )
			return;

		SClass_ID		superClassId;
		IDerivedObject* derivedObject;
		Object *currentObject = object;

		//int modIdx = 0;
		superClassId = object->SuperClassID();
		if (superClassId == GEN_DERIVOB_CLASS_ID || superClassId == DERIVOB_CLASS_ID || superClassId == WSM_DERIVOB_CLASS_ID)
		{
			derivedObject = (IDerivedObject*)object;
			while (superClassId == GEN_DERIVOB_CLASS_ID || superClassId == DERIVOB_CLASS_ID || superClassId == WSM_DERIVOB_CLASS_ID)
			{
				for (int modifierIndex = 0; modifierIndex < derivedObject->NumModifiers(); ++modifierIndex)
				{
					Modifier* modifier = derivedObject->GetModifier(modifierIndex);
					//no iskin support for xrefs
					// TODO needs further investigation...

					if ( SkinController::isSkinController(modifier) /*&& !exportNode.getIsXRefObject() */) 
					{
						SkinController * skinController = new SkinController(derivedObject, modifierIndex, modifier->IsEnabled() != false);
						mControllers.push_back(skinController);
					}
					else if ( MorphController::isMorphController(modifier) )
					{
						MorphController * morphController = new MorphController(derivedObject, modifierIndex, modifier->IsEnabled() != false);
						mControllers.push_back(morphController);
					}
					else
						return;
				}
				derivedObject = (IDerivedObject*) derivedObject->GetObjRef();
				currentObject = (Object*) derivedObject;
				superClassId = derivedObject->SuperClassID();
			}
		}

/*		int bct = currentObject->NumPipeBranches(FALSE);
		if (bct > 0)
		{
			for (int bi = 0; bi < bct; bi++)
			{
				Object* bobj = currentObject->GetPipeBranch(bi,FALSE);
				Resolve(bobj);
				return;
			}
		}
*/
	}
	//---------------------------------------------------------------
	String CameraExporter::getCameraId( const ExportNode& exportNode )
	{
		return exportNode.getId() + CAMERA_ID_SUFFIX;
	}
	//---------------------------------------------------------------
	void CameraExporter::exportCamera( ExportNode* exportNode )
	{
		if ( !exportNode->getIsInVisualScene() )
			return;
		
		String cameraId = getCameraId(*exportNode);

		INode* iNode = exportNode->getINode();

		CameraObject* camera = (CameraObject*)iNode->GetObjectRef();

		INode* targetNode =  ( camera->ClassID().PartA() == LOOKAT_CAM_CLASS_ID) ? iNode->GetTarget() : 0;

		if ( camera )
		{
			if (  mDocumentExporter->isExportedObject(ObjectIdentifier(camera)) )
				return;

			mDocumentExporter->insertExportedObject(ObjectIdentifier(camera), exportNode);


			// Retrieve the camera parameters block
			IParamBlock* parameters = (IParamBlock*) camera->GetReference(MaxCamera::PBLOCK_REF);

			COLLADASW::BaseOptic * optics = 0; 
			if ( camera->IsOrtho() )
			{
				optics = new COLLADASW::OrthographicOptic(COLLADASW::LibraryCameras::mSW);

				// Calculate the target distance for FOV calculations
				float targetDistance;
				if ( targetNode )
				{
					Point3 targetTrans = targetNode->GetNodeTM(mDocumentExporter->getOptions().getAnimationStart()).GetTrans();
					Point3 cameraTrans = iNode->GetNodeTM(mDocumentExporter->getOptions().getAnimationStart()).GetTrans();
					targetDistance = (targetTrans - cameraTrans).Length();
				}
				else
				{
					targetDistance = camera->GetTDist(mDocumentExporter->getOptions().getAnimationStart());
				}
				ConversionInverseOrthoFOVFunctor conversionInverseOrthoFOVFunctor(targetDistance);

				if ( AnimationExporter::isAnimated(parameters, MaxCamera::FOV) )
				{
					optics->setXMag(conversionInverseOrthoFOVFunctor(parameters->GetFloat(MaxCamera::FOV)), XMAG_SID);
					mAnimationExporter->addAnimatedParameter(parameters, MaxCamera::FOV, cameraId, XMAG_SID, 0, true, &conversionInverseOrthoFOVFunctor);
				}
				else
				{
					optics->setXMag(conversionInverseOrthoFOVFunctor(parameters->GetFloat(MaxCamera::FOV)));	
				}
			}
			else
			{
				optics = new COLLADASW::PerspectiveOptic(COLLADASW::LibraryCameras::mSW);
				if ( AnimationExporter::isAnimated(parameters, MaxCamera::FOV) )
				{
					optics->setXFov(COLLADASW::MathUtils::radToDegF(parameters->GetFloat(MaxCamera::FOV)), XFOV_SID);
					mAnimationExporter->addAnimatedParameter(parameters, MaxCamera::FOV, cameraId, XFOV_SID, 0, true, &ConversionFunctors::radToDeg);
				}
				else
				{
					optics->setXFov(COLLADASW::MathUtils::radToDegF(parameters->GetFloat(MaxCamera::FOV)));	
				}
			}

			bool hasAnimatedZNear = mAnimationExporter->addAnimatedParameter(parameters, MaxCamera::NEAR_CLIP, cameraId, optics->getZNearDefaultSid(), 0);
			optics->setZNear(parameters->GetFloat(MaxCamera::NEAR_CLIP), hasAnimatedZNear);

			bool hasAnimatedZFar = mAnimationExporter->addAnimatedParameter(parameters, MaxCamera::FAR_CLIP, cameraId, optics->getZFarDefaultSid(), 0);
			optics->setZFar(parameters->GetFloat(MaxCamera::FAR_CLIP), hasAnimatedZFar);


#ifdef UNICODE
			String exportNodeName = COLLADABU::StringUtils::wideString2utf8String(exportNode->getINode()->GetName());
			COLLADASW::Camera colladaCamera(COLLADASW::LibraryCameras::mSW, optics, cameraId, COLLADASW::Utils::checkNCName(exportNodeName));
#else
			COLLADASW::Camera colladaCamera(COLLADASW::LibraryCameras::mSW, optics, cameraId, COLLADASW::Utils::checkNCName(exportNode->getINode()->GetName()));
#endif
			setExtraTechnique(&colladaCamera);


			// Retrieve the camera target
			if ( targetNode )
			{
				ExportNode* targetExportNode = mExportSceneGraph->getExportNode(targetNode);
				addExtraParameter(EXTRA_PARAMETER_TARGET, "#" + targetExportNode->getId());
			}

			if (camera->GetMultiPassEffectEnabled(0, FOREVER))
			{
				IMultiPassCameraEffect *multiPassCameraEffect = camera->GetIMultiPassCameraEffect();
				if (multiPassCameraEffect)
				{
					Class_ID id = multiPassCameraEffect->ClassID();

					// the camera could have both effects, but not in Max
					if (id == FMULTI_PASS_MOTION_BLUR_CLASS_ID)
					{
						IParamBlock2 *parameters = multiPassCameraEffect->GetParamBlock(0);
						if (parameters )
						{
							addParamBlockAnimatedExtraParameters(MOTION_BLUR_ELEMENT, MOTION_BLUR_PARAMETERS, MOTION_BLUR_PARAMETER_COUNT, parameters, cameraId);
						}
					}
					else if (id == FMULTI_PASS_DOF_CLASS_ID)
					{
						IParamBlock2 *parameters = multiPassCameraEffect->GetParamBlock(0);
						if (parameters )
						{
							addParamBlockAnimatedExtraParameters(DEPTH_OF_FIELD_ELEMENT, DEPTH_OF_FIELD_PARAMETERS, DEPTH_OF_FIELD_PARAMETER_COUNT, parameters, cameraId);
							addExtraParameter(TARGETDISTANCE_PARAMETER, camera->GetTDist(0));
						}
					}
				}
			}
			addCamera(colladaCamera);
		
			delete optics;
		}

	}