Пример #1
0
//***********************************************************************************************
bool CParticleWorkspace::CNode::loadPS()
{	
	nlassert(_WS);
	// manually load the PS shape (so that we can deal with exceptions)
	NL3D::CShapeStream ss;
	NLMISC::CIFile inputFile;
	// collapse name
	inputFile.open(getFullPath());
	ss.serial(inputFile);
	std::auto_ptr<NL3D::CShapeBank> sb(new NL3D::CShapeBank);
	std::string shapeName = NLMISC::CFile::getFilename(_RelativePath);
	sb->add(shapeName, ss.getShapePointer());
	NL3D::CShapeBank *oldSB = NL3D::CNELU::Scene->getShapeBank();
	NL3D::CNELU::Scene->setShapeBank(sb.get());
	NL3D::CTransformShape *trs = NL3D::CNELU::Scene->createInstance(shapeName);
	if (!trs)
	{
		NL3D::CNELU::Scene->setShapeBank(oldSB);
		return false;
	}
	NL3D::CParticleSystemModel *psm = dynamic_cast<NL3D::CParticleSystemModel *>(trs);
	if (!psm)
	{
		// Not a particle system
		NL3D::CNELU::Scene->deleteInstance(trs);
		return false;
	}
	NL3D::CNELU::Scene->setShapeBank(oldSB);		
	setup(*psm);
	unload();
	// commit new values
	_PS = psm->getPS();
	_PSM = psm;
	_ShapeBank = sb.release();
	_Modified = false;
	return true; 
}
/** Load and compute the bbox of the models that are contained in a given instance group
  * \return true if the computed bbox is valid
  */
static void computeIGBBox(const NL3D::CInstanceGroup &ig, CLightingBBox &result, TShapeMap &shapeMap)
{
	result = CLightingBBox(); // starts with void result
	bool firstBBox = true;	
	/// now, compute the union of all bboxs
	for (CInstanceGroup::TInstanceArray::const_iterator it = ig._InstancesInfos.begin(); it != ig._InstancesInfos.end(); ++it)
	{		
		CLightingBBox currBBox;
		
		bool validBBox = false;
		/// get the bbox from file or from map
		if (shapeMap.count(it->Name)) // already loaded ?
		{
			currBBox = shapeMap[it->Name];
			validBBox = true;
		}
		else // must load the shape to get its bbox
		{		
			std::string shapePathName;
			std::string toLoad = it->Name;
			if (getExt(toLoad).empty()) toLoad += ".shape";
			shapePathName = NLMISC::CPath::lookup(toLoad, false, false);

			if (shapePathName.empty())
			{
				nlwarning("Unable to find shape %s", it->Name.c_str());				
			}
			else
			{
				CIFile shapeInputFile;
				
				if (shapeInputFile.open (shapePathName.c_str()))
				{					
					NL3D::CShapeStream shapeStream;
					try
					{
						shapeStream.serial (shapeInputFile);
						// NB Nico :
						// Deal with water shape -> their 'Receiving' box is set to 'void'
						// this prevent the case where a huge surface of water will cause the zone it is attached to (the 'Zone'
						// field in the villages sheets) to load all the zones that the water surface cover. (This caused
						// an 'out of memory error' in the zone lighter due to too many zone being loaded)
						
						// FIXME : test for water case hardcoded for now						
						CWaterShape *ws = dynamic_cast<CWaterShape *>(shapeStream.getShapePointer());
						if (ws)
						{
							CAABBox bbox;
							shapeStream.getShapePointer()->getAABBox(bbox);
							currBBox.OccludingBox = CPotentialBBox(bbox); // occluding box is used, though the water shape
																		 // doesn't cast shadow -> the tiles flag ('above', 'intersect', 'below water')
																		 // are updated inside the zone_lighter
							currBBox.ReceivingBox.IsVoid = true; // no lighted by the zone lighter !!!
							currBBox.removeVoid();
							shapeMap[it->Name] = currBBox;							
						}
						else
						{

							CAABBox bbox;
							shapeStream.getShapePointer()->getAABBox(bbox);
							currBBox.OccludingBox = CPotentialBBox(bbox);
							currBBox.ReceivingBox = CPotentialBBox(bbox);
							currBBox.removeVoid();
							shapeMap[it->Name] = currBBox;
						}
						validBBox = true;
					}
					
					catch (NLMISC::Exception &e)
					{
						nlwarning("Error while loading shape %s. \n\t Reason : %s ", it->Name.c_str(), e.what());
					}				
				}
				else
				{
					nlwarning("Unable to open shape file %s to get its bbox", it->Name.c_str());
				}
			}
		}


		if (validBBox)
		{
			/// build the model matrix
			NLMISC::CMatrix mat;
			mat.scale(it->Scale);
			NLMISC::CMatrix rotMat;
			rotMat.setRot(it->Rot);
			mat = rotMat * mat;
			mat.setPos(it->Pos);

			/// transform the bbox
			currBBox.transform(mat);
			currBBox.removeVoid();			
			if (firstBBox)
			{
				result = currBBox;
				firstBBox = false;
			}
			else // add to previous one
			{						
				result.makeUnion(currBBox);
			}			
		}		
	}	
}
Пример #3
0
CInstanceGroup*	CExportNel::buildInstanceGroup(const vector<INode*>& vectNode, vector<INode*>& resultInstanceNode, TimeValue tvTime)
{
	// Extract from the node the name, the transformations and the parent

	CInstanceGroup::TInstanceArray aIGArray;
	uint32 i, nNumIG;
	uint32 j,k,m;

	aIGArray.empty ();
	resultInstanceNode.empty ();
	aIGArray.resize (vectNode.size());
	resultInstanceNode.resize (vectNode.size());

	int nNbInstance = 0;
	for (i = 0; i < vectNode.size(); ++i)
	{
		INode *pNode = vectNode[i];

		int nAccelType = CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_ACCEL, 32);

		if ((nAccelType&3) == 0) // If not an accelerator
		if (!RPO::isZone (*pNode, tvTime))
		if (CExportNel::isMesh (*pNode, tvTime) || CExportNel::isDummy(*pNode, tvTime))
		{
			++nNbInstance;
		}
	}

	// Check integrity of the hierarchy and set the parents
	std::vector<INode*>::const_iterator it = vectNode.begin();
	nNumIG = 0;
	for (i = 0; i < (sint)vectNode.size(); ++i, ++it)
	{
		INode *pNode = *it;

		int nAccelType = CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_ACCEL, 32);

		if ((nAccelType&3) == 0) // If not an accelerator
		if (!RPO::isZone( *pNode, tvTime ))
		if (CExportNel::isMesh( *pNode, tvTime ) || CExportNel::isDummy(*pNode, tvTime))
		{
			aIGArray[nNumIG].DontAddToScene = CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_DONT_ADD_TO_SCENE, 0)?true:false;
			aIGArray[nNumIG].InstanceName = CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_INSTANCE_NAME, "");
			resultInstanceNode[nNumIG] = pNode;
			if (aIGArray[nNumIG].InstanceName == "") // no instance name was set, takes the node name instead
			{
				aIGArray[nNumIG].InstanceName = pNode->GetName();
			}

			// Visible? always true, but if special flag for camera collision
			sint	appDataCameraCol= CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_CAMERA_COLLISION_MESH_GENERATION, 0);
			aIGArray[nNumIG].Visible= appDataCameraCol!=3;


			INode *pParent = pNode->GetParentNode();

			// Set the DontCastShadow flag.
			aIGArray[nNumIG].DontCastShadow= pNode->CastShadows()==0;

			// Set the Special DontCastShadow flag.
			aIGArray[nNumIG].DontCastShadowForInterior= CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_LIGHT_DONT_CAST_SHADOW_INTERIOR, BST_UNCHECKED)?true:false;
			aIGArray[nNumIG].DontCastShadowForExterior= CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_LIGHT_DONT_CAST_SHADOW_EXTERIOR, BST_UNCHECKED)?true:false;

			// Is the pNode has the root node for parent ?
			if( pParent->IsRootNode() == 0 )
			{
				// Look if the parent is in the selection
				int nNumIG2 = 0;
				for (j = 0; j < vectNode.size(); ++j)
				{
					INode *pNode2 = vectNode[j];

					int nAccelType2 = CExportNel::getScriptAppData (pNode2, NEL3D_APPDATA_ACCEL, 32);
					if ((nAccelType2&3) == 0) // If not an accelerator
					if (!RPO::isZone( *pNode2, tvTime ))
					if (CExportNel::isMesh( *pNode2, tvTime ))
					{
						if (pNode2 == pParent)
							break;
						++nNumIG2;
					}
				}
				if (nNumIG2 == nNbInstance)
				{
					// The parent is not selected ! link to root
					aIGArray[nNumIG].nParent = -1;
				}
				else
				{
					aIGArray[nNumIG].nParent = nNumIG2;
				}
			}
			else
			{
				aIGArray[nNumIG].nParent = -1;
			}
			++nNumIG;
		}
	}
	aIGArray.resize( nNumIG );
	resultInstanceNode.resize( nNumIG );

	// Build the array of node
	vGlobalPos = CVector(0,0,0);
	nNumIG = 0;
	it = vectNode.begin();
	for (i = 0; i < (sint)vectNode.size(); ++i, ++it)
	{
		INode *pNode = *it;

		int nAccelType = CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_ACCEL, 32);

		if ((nAccelType&3) == 0) // If not an accelerator
		if (!RPO::isZone (*pNode, tvTime))
		if (CExportNel::isMesh (*pNode, tvTime) || CExportNel::isDummy(*pNode, tvTime))
		{
			CVector vScaleTemp;
			CQuat qRotTemp;
			CVector vPosTemp;

			// Get Nel Name for the object.
			aIGArray[nNumIG].Name= CExportNel::getNelObjectName(*pNode);

			//Get the local transformation matrix
			Matrix3 nodeTM = pNode->GetNodeTM(0);
			INode *pParent = pNode->GetParentNode();
			Matrix3 parentTM = pParent->GetNodeTM(0);
			Matrix3 localTM	= nodeTM*Inverse(parentTM);

			// Extract transformations
			CExportNel::decompMatrix (vScaleTemp, qRotTemp, vPosTemp, localTM);
			aIGArray[nNumIG].Rot   = qRotTemp;
			aIGArray[nNumIG].Pos   = vPosTemp;
			aIGArray[nNumIG].Scale = vScaleTemp;
			vGlobalPos += vPosTemp;
			++nNumIG;
		}
	}
	// todo Make this work (precision):
	/*
	vGlobalPos = vGlobalPos / nNumIG;
	for (i = 0; i < nNumIG; ++i)
		aIGArray[i].Pos -= vGlobalPos;
	*/

	vGlobalPos = CVector(0,0,0); // Temporary !!!

	// Accelerator Portal/Cluster part
	//=================

	// Creation of all the clusters
	vector<CCluster> vClusters;
	it = vectNode.begin();
	for (i = 0; i < (sint)vectNode.size(); ++i, ++it)
	{
		INode *pNode = *it;

		int nAccelType = CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_ACCEL, NEL3D_APPDATA_ACCEL_DEFAULT);
		bool bFatherVisible = nAccelType&NEL3D_APPDATA_ACCEL_FATHER_VISIBLE?true:false;
		bool bVisibleFromFather = nAccelType&NEL3D_APPDATA_ACCEL_VISIBLE_FROM_FATHER?true:false;
		bool bAudibleLikeVisible = (nAccelType&NEL3D_APPDATA_ACCEL_AUDIBLE_NOT_LIKE_VISIBLE)?false:true;
		bool bFatherAudible = bAudibleLikeVisible ? bFatherVisible : nAccelType&NEL3D_APPDATA_ACCEL_FATHER_AUDIBLE?true:false;
		bool bAudibleFromFather = bAudibleLikeVisible ? bVisibleFromFather : nAccelType&NEL3D_APPDATA_ACCEL_AUDIBLE_FROM_FATHER?true:false;

		if ((nAccelType&NEL3D_APPDATA_ACCEL_TYPE) == NEL3D_APPDATA_ACCEL_CLUSTER) // If cluster
		if (!RPO::isZone (*pNode, tvTime))
		if (CExportNel::isMesh(*pNode, tvTime))
		{
			CCluster clusterTemp;
			std::string temp;

			temp = CExportNel::getScriptAppData(pNode, NEL3D_APPDATA_SOUND_GROUP, "no sound");
			clusterTemp.setSoundGroup(temp != "no sound" ? temp : "");
			temp = CExportNel::getScriptAppData(pNode, NEL3D_APPDATA_ENV_FX, "no fx");
			clusterTemp.setEnvironmentFx(temp != "no fx" ? temp : "");

			CMesh::CMeshBuild *pMB;
			CMeshBase::CMeshBaseBuild *pMBB;
			pMB = createMeshBuild (*pNode, tvTime, pMBB);

			convertToWorldCoordinate( pMB, pMBB );

			for (j = 0; j < pMB->Faces.size(); ++j)
			{
				if (!clusterTemp.makeVolume (pMB->Vertices[pMB->Faces[j].Corner[0].Vertex],
											 pMB->Vertices[pMB->Faces[j].Corner[1].Vertex],
											 pMB->Vertices[pMB->Faces[j].Corner[2].Vertex]) )
				{
					// ERROR : The volume is not convex !!!
					char tam[256];
					sprintf(tam,"ERROR: The cluster %s is not convex.",vectNode[i]->GetName());
					//MessageBox(NULL,tam,"Error",MB_OK|MB_ICONERROR);
					nlwarning(tam);
				}
			}

			clusterTemp.FatherVisible = bFatherVisible;
			clusterTemp.VisibleFromFather = bVisibleFromFather;
			clusterTemp.FatherAudible = bFatherAudible;
			clusterTemp.AudibleFromFather = bAudibleFromFather;
			clusterTemp.Name = pNode->GetName();

			vClusters.push_back (clusterTemp);
			delete pMB;
			delete pMBB;
		}
	}

	// Creation of all the portals
	vector<CPortal> vPortals;
	it = vectNode.begin();
	for (i = 0; i < (sint)vectNode.size(); ++i, ++it)
	{
		INode *pNode = *it;

		int nAccelType = CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_ACCEL, 32);

		if ((nAccelType&3) == 1) // If Portal
		if (!RPO::isZone (*pNode, tvTime))
		if (CExportNel::isMesh(*pNode, tvTime))
		{
			CPortal portalTemp;
			std::string temp;

			temp = CExportNel::getScriptAppData(pNode, NEL3D_APPDATA_OCC_MODEL, "no occlusion");
			portalTemp.setOcclusionModel(temp != "no occlusion" ? temp : "");
			temp = CExportNel::getScriptAppData(pNode, NEL3D_APPDATA_OPEN_OCC_MODEL, "no occlusion");
			portalTemp.setOpenOcclusionModel(temp != "no occlusion" ? temp : "");

			CMesh::CMeshBuild *pMB;
			CMeshBase::CMeshBaseBuild *pMBB;
			pMB = createMeshBuild (*pNode, tvTime, pMBB);

			convertToWorldCoordinate( pMB, pMBB );

			vector<sint32> poly;
			vector<bool> facechecked;
			facechecked.resize (pMB->Faces.size());
			for (j = 0; j < pMB->Faces.size(); ++j)
				facechecked[j] = false;

			poly.push_back(pMB->Faces[0].Corner[0].Vertex);
			poly.push_back(pMB->Faces[0].Corner[1].Vertex);
			poly.push_back(pMB->Faces[0].Corner[2].Vertex);
			facechecked[0] = true;
			for (j = 0; j < pMB->Faces.size(); ++j)
			if (!facechecked[j])
			{
				bool found = false;

				for(k = 0; k < 3; ++k)
				{
					for(m = 0; m < poly.size(); ++m)
					{
						if ((pMB->Faces[j].Corner[k].Vertex == poly[m]) &&
							(pMB->Faces[j].Corner[(k+1)%3].Vertex == poly[(m+1)%poly.size()]))
						{
							found = true;
							break;
						}
						if ((pMB->Faces[j].Corner[(k+1)%3].Vertex == poly[m]) &&
							(pMB->Faces[j].Corner[k].Vertex == poly[(m+1)%poly.size()]))
						{
							found = true;
							break;
						}
					}
					if (found)
						break;
				}
				if (found)
				{
					// insert an empty space in poly between m and m+1
					poly.resize (poly.size()+1);
					for (uint32 a = poly.size()-2; a > m; --a)
						poly[a+1] = poly[a];
					poly[m+1] = pMB->Faces[j].Corner[(k+2)%3].Vertex;
					facechecked[j] = true;
					j = 0;
				}
			}
			vector<CVector> polyv;
			polyv.resize (poly.size());
			for (j = 0; j < poly.size(); ++j)
				polyv[j] = pMB->Vertices[poly[j]];
			
			if (!portalTemp.setPoly (polyv))
			{
				// ERROR : Poly not convex, or set of vertices not plane
				char tam[256];
				sprintf(tam,"ERROR: The portal %s is not convex.",vectNode[i]->GetName());
				//MessageBox(NULL,tam,"Error",MB_OK|MB_ICONERROR);
				nlwarning(tam);
			}

			if (nAccelType&16) // is dynamic portal ?
			{
				string InstanceName = CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_INSTANCE_NAME, "");
				if (!InstanceName.empty())
					portalTemp.setName (InstanceName);
				else
					portalTemp.setName (string(pNode->GetName()));
			}

			// Check if portal has 2 cluster
			int nNbCluster = 0;
			for (j = 0; j < vClusters.size(); ++j)
			{
				bool bPortalInCluster = true;
				for (k = 0; k < polyv.size(); ++k)
					if (!vClusters[j].isIn (polyv[k]) )
					{
						bPortalInCluster = false;
						break;
					}
				if (bPortalInCluster)
					++nNbCluster;
			}
			if (nNbCluster != 2)
			{
				// ERROR
				char tam[256];
				sprintf(tam,"ERROR: The portal %s has not 2 clusters but %d",vectNode[i]->GetName(), nNbCluster);
				//MessageBox(NULL,tam,"Error",MB_OK|MB_ICONERROR);
				nlwarning(tam);
			}


			vPortals.push_back (portalTemp);
			delete pMB;
			delete pMBB;
		}
	}

	// Link instance to clusters (an instance has a list of clusters)
	nNumIG = 0;
	it = vectNode.begin();
	for (i = 0; i < (sint)vectNode.size(); ++i, ++it)
	{
		INode *pNode = *it;

		int nAccelType = CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_ACCEL, 32);

		if ((nAccelType&3) == 0) // If not an accelerator
		if (!RPO::isZone (*pNode, tvTime))
		if (CExportNel::isMesh (*pNode, tvTime) || CExportNel::isDummy(*pNode, tvTime))
		{
			if (nAccelType&32) // Is the flag clusterize set ?
			{
				// Test against all clusters

				// The list of vertices used to test against cluster
				std::vector<NLMISC::CVector> *testVertices;
				std::vector<NLMISC::CVector>       FXVertices;  // Used only if the obj is a fx. It contains the corners of the bbox.
				bool  buildMeshBBox = true;

				/** If it is a mesh, we build its bbox and transform in world
				  * If it is a FX, we read its bbox from its shape
				  * If we can't read it, we use the bbox of the fx helper in max
				  */
				Object *obj = pNode->EvalWorldState(tvTime).obj;
				// Check if there is an object
				if (obj)
				{
					Class_ID  clid = obj->ClassID();
					// is the object a particle system ?					
					if (clid.PartA() == NEL_PARTICLE_SYSTEM_CLASS_ID)
					{
						// build the shape from the file name
						std::string objName = CExportNel::getNelObjectName(*pNode); 						
						if (!objName.empty())
						{											
							NL3D::CShapeStream ss;
							NLMISC::CIFile iF;
							if (iF.open(objName.c_str()))
							{
								try
								{								
									iF.serial(ss);
									NL3D::CParticleSystemShape *pss = dynamic_cast<NL3D::CParticleSystemShape *>(ss.getShapePointer());
									if (!pss)
									{
										nlwarning("ERROR: Node %s shape is not a FX", CExportNel::getName(*pNode).c_str());
									}
									else
									{									
										NLMISC::CAABBox bbox;
										pss->getAABBox(bbox);
										// transform in world
										Matrix3 xForm = pNode->GetNodeTM(tvTime);
										NLMISC::CMatrix nelXForm;
										CExportNel::convertMatrix(nelXForm, xForm);									
										bbox = NLMISC::CAABBox::transformAABBox(nelXForm, bbox);
										// store vertices of the bbox in the list
										FXVertices.reserve(8);
										for(uint k = 0; k < 8; ++k)
										{
											FXVertices.push_back(CVector(((k & 1) ? 1 : -1) * bbox.getHalfSize().x + bbox.getCenter().x,
																		 ((k & 2) ? 1 : -1) * bbox.getHalfSize().y + bbox.getCenter().y,
																		 ((k & 4) ? 1 : -1) * bbox.getHalfSize().z + bbox.getCenter().z));
										}
										//
										testVertices = &FXVertices;
										buildMeshBBox = false;
									}
									delete ss.getShapePointer();
								}
								catch (NLMISC::Exception &e)
								{
									nlwarning(e.what());									
								}
							}							
							if (buildMeshBBox)
							{
								nlwarning("ERROR: Can't get bbox of a particle system from its shape, using helper bbox instead");
							}
						}
					}
				}

				CMesh::CMeshBuild *pMB = NULL;
				CMeshBase::CMeshBaseBuild *pMBB = NULL;

				if (buildMeshBBox)
				{				
					pMB = createMeshBuild (*pNode, tvTime, pMBB);
					convertToWorldCoordinate( pMB, pMBB );
					testVertices = &pMB->Vertices;
				}

				for(k = 0; k < vClusters.size(); ++k)
				{
					bool bMeshInCluster = false;

					for(j = 0; j < testVertices->size(); ++j)
					{
						if (vClusters[k].isIn ((*testVertices)[j]))
						{
							bMeshInCluster = true;
							break;
						}
					}

					if (bMeshInCluster)
					{
						aIGArray[nNumIG].Clusters.push_back (k);
					}
				}
				
				// debug purpose : to remove
				if (vClusters.size() > 0)
				if (aIGArray[nNumIG].Clusters.size() == 0)
				{
					char tam[256];
					sprintf(tam,"ERROR: Object %s is not attached to any cluster\nbut his flag clusterize is set", pNode->GetName());
					//MessageBox(NULL, tam, "Warning", MB_OK);
					nlwarning(tam);
				}
				// debug purpose : to remove

				delete pMB;
				delete pMBB;
			}
			
			++nNumIG;
		}
		// debug purpose : to remove
		/*
		if ((nAccelType&3) == 0) // If not an accelerator
		if (!(nAccelType&32))
		{
			char tam[256];
			sprintf(tam,"Object %s is not clusterized", pNode->GetName());
			MessageBox(NULL, tam, "Info", MB_OK);
		}
		*/
		// debug purpose : to remove

	}


	// PointLight part
	//=================
	bool	sunLightEnabled= false;
	sint	nNumPointLight = 0;
	vector<CPointLightNamed>	pointLights;
	pointLights.resize(vectNode.size());
	// For all nodes
	for (i = 0; i < (sint)vectNode.size(); ++i)
	{
		INode *pNode = vectNode[i];

		SLightBuild		sLightBuild;

		// If it is a Max Light.
		if ( sLightBuild.canConvertFromMaxLight(pNode, tvTime) )
		{
			// And if this light is checked to realtime export
			int		nRTExport= CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_EXPORT_REALTIME_LIGHT, BST_CHECKED);
			if(nRTExport == BST_CHECKED)
			{
				// get Max Light info.
				sLightBuild.convertFromMaxLight(pNode, tvTime);

				// Skip if LightDir
				if(sLightBuild.Type != SLightBuild::LightDir)
				{
					// Fill PointLight Info.
					NL3D::CPointLightNamed	&plNamed= pointLights[nNumPointLight];

					// Position
					plNamed.setPosition(sLightBuild.Position);
					// Attenuation
					plNamed.setupAttenuation(sLightBuild.rRadiusMin, sLightBuild.rRadiusMax);
					// Colors
					// Ensure A=255 for localAmbient to work.
					NLMISC::CRGBA	ambient= sLightBuild.Ambient;
					ambient.A= 255;
					plNamed.setDefaultAmbient(ambient);
					plNamed.setAmbient(ambient);
					plNamed.setDefaultDiffuse(sLightBuild.Diffuse);
					plNamed.setDiffuse(sLightBuild.Diffuse);
					plNamed.setDefaultSpecular(sLightBuild.Specular);
					plNamed.setSpecular(sLightBuild.Specular);

					// GroupName.
					plNamed.AnimatedLight = sLightBuild.AnimatedLight;
					plNamed.LightGroup = sLightBuild.LightGroup;

					// Which light type??
					if(sLightBuild.bAmbientOnly || sLightBuild.Type== SLightBuild::LightAmbient)
					{
						plNamed.setType(CPointLight::AmbientLight);
						// Special ambient info
						int		nRTAmbAdd= CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_REALTIME_AMBIENT_ADD_SUN, BST_UNCHECKED);
						plNamed.setAddAmbientWithSun(nRTAmbAdd==BST_CHECKED);
					}
					else if(sLightBuild.Type== SLightBuild::LightPoint)
					{
						plNamed.setType(CPointLight::PointLight);
					}
					else if(sLightBuild.Type== SLightBuild::LightSpot)
					{
						plNamed.setType(CPointLight::SpotLight);
						// Export Spot infos.
						plNamed.setupSpotDirection(sLightBuild.Direction);
						plNamed.setupSpotAngle(sLightBuild.rHotspot, sLightBuild.rFallof);
					}
					else
					{
						// What???
						nlstop;
					}


					// inc Size
					++nNumPointLight;
				}
			}

			// if this light is a directionnal and checked to export as Sun Light
			int		nExportSun= CExportNel::getScriptAppData (pNode, NEL3D_APPDATA_EXPORT_AS_SUN_LIGHT, BST_UNCHECKED);
			if(nExportSun== BST_CHECKED)
			{
				// get Max Light info.
				sLightBuild.convertFromMaxLight(pNode, tvTime);

				// Skip if not dirLight.
				if(sLightBuild.Type == SLightBuild::LightDir)
					sunLightEnabled= true;
			}
		}
	}
	// Good size
	pointLights.resize(nNumPointLight);


	// Build the ig
	//=================

	CInstanceGroup* pIG = new CInstanceGroup;

	// Link portals and clusters and create meta cluster if one
	pIG->build (vGlobalPos,  aIGArray, vClusters, vPortals, pointLights);

	// IG touched by sun ??
	pIG->enableRealTimeSunContribution(sunLightEnabled);

	return pIG;
}