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; }
// --------------------------------------------------------------------------- int main(int nNbArg, char**ppArgs) { if (!NLMISC::INelContext::isContextInitialised()) new CApplicationContext(); NL3D_BlockMemoryAssertOnPurge = false; char sCurDir[MAX_PATH]; getcwd (sCurDir, MAX_PATH); if (nNbArg != 2) { printf ("Use : ig_elevation configfile.cfg\n"); printf ("\nExample of config.cfg\n\n"); printf ("InputIGDir = \"ig_land_max\";\n"); printf ("OutputIGDir = \"ig_land_max_elev\";\n"); printf ("CellSize = 160.0;\n"); printf ("HeightMapFile1 = \"w:/database/landscape/ligo/jungle/big.tga\";\n"); printf ("ZFactor1 = 1.0;\n"); printf ("HeightMapFile2 = \"w:/database/landscape/ligo/jungle/noise.tga\";\n"); printf ("ZFactor2 = 0.5;\n"); printf ("LandFile = \"w:/matis.land\";\n"); return -1; } SExportOptions options; if (!options.load(ppArgs[1])) { return -1; } // Get all ig files in the input directory and elevate to the z of the double heightmap // Load the land CZoneRegion *ZoneRegion = loadLand(options.LandFile); CZoneLimits zl; if (ZoneRegion) { zl._ZoneMinX = ZoneRegion->getMinX() < 0 ? 0 : ZoneRegion->getMinX(); zl._ZoneMaxX = ZoneRegion->getMaxX() > 255 ? 255 : ZoneRegion->getMaxX(); zl._ZoneMinY = ZoneRegion->getMinY() > 0 ? 0 : ZoneRegion->getMinY(); zl._ZoneMaxY = ZoneRegion->getMaxY() < -255 ? -255 : ZoneRegion->getMaxY(); } else { nlwarning("A ligo .land file cannot be found"); zl._ZoneMinX = 0; zl._ZoneMaxX = 255; zl._ZoneMinY = 0; zl._ZoneMaxY = 255; } // Load the 2 height maps CBitmap *HeightMap1 = NULL; if (!options.HeightMapFile1.empty()) { HeightMap1 = new CBitmap; try { CIFile inFile; if (inFile.open(options.HeightMapFile1)) { HeightMap1->load (inFile); } else { string sTmp = string("Couldn't not open ")+string(options.HeightMapFile1) +string(" : heightmap 1 map ignored"); outString(sTmp); delete HeightMap1; HeightMap1 = NULL; } } catch (const Exception &e) { string sTmp = string("Cant load height map : ") + options.HeightMapFile1 + " : " + e.what(); outString (sTmp); delete HeightMap1; HeightMap1 = NULL; } } CBitmap *HeightMap2 = NULL; if (!options.HeightMapFile2.empty()) { HeightMap2 = new CBitmap; try { CIFile inFile; if (inFile.open(options.HeightMapFile2)) { HeightMap2->load (inFile); } else { string sTmp = string("Couldn't not open ")+string(options.HeightMapFile2) +string(" : heightmap 2 map ignored\n"); outString(sTmp); delete HeightMap2; HeightMap2 = NULL; } } catch (const Exception &e) { string sTmp = string("Cant load height map : ") + options.HeightMapFile2 + " : " + e.what() + "\n"; outString (sTmp); delete HeightMap2; HeightMap1 = NULL; } } // Get all files vector<string> vAllFiles; chdir (options.InputIGDir.c_str()); dir (".ig", vAllFiles, false); chdir (sCurDir); for (uint32 i = 0; i < vAllFiles.size(); ++i) { chdir (options.InputIGDir.c_str()); CInstanceGroup *pIG = LoadInstanceGroup (vAllFiles[i].c_str()); chdir (sCurDir); if (pIG != NULL) { bool realTimeSunContribution = pIG->getRealTimeSunContribution(); // For all instances !!! CVector vGlobalPos; CInstanceGroup::TInstanceArray IA; vector<CCluster> Clusters; vector<CPortal> Portals; vector<CPointLightNamed> PLN; pIG->retrieve (vGlobalPos, IA, Clusters, Portals, PLN); if (IA.empty() && PLN.empty() && Portals.empty() && Clusters.empty()) continue; uint k; // elevate instance for(k = 0; k < IA.size(); ++k) { CVector instancePos = vGlobalPos + IA[k].Pos; IA[k].Pos.z += getHeightMapZ(instancePos.x, instancePos.y, zl, options, HeightMap1, HeightMap2); } // lights for(k = 0; k < PLN.size(); ++k) { CVector lightPos = vGlobalPos + PLN[k].getPosition(); PLN[k].setPosition( PLN[k].getPosition() + getHeightMapZ(lightPos.x, lightPos.y, zl, options, HeightMap1, HeightMap2) * CVector::K); } // portals std::vector<CVector> portal; for(k = 0; k < Portals.size(); ++k) { Portals[k].getPoly(portal); if (portal.empty()) { nlwarning("Empty portal found"); continue; } // compute mean position of the poly CVector meanPos(0, 0, 0); uint l; for(l = 0; l < portal.size(); ++l) meanPos += portal[l]; meanPos /= (float) portal.size(); meanPos += vGlobalPos; float z = getHeightMapZ(meanPos.x, meanPos.y, zl, options, HeightMap1, HeightMap2); for(l = 0; l < portal.size(); ++l) { portal[l].z += z; } Portals[k].setPoly(portal); } // clusters std::vector<CPlane> volume; CMatrix transMatrix; for(k = 0; k < Clusters.size(); ++k) { CVector clusterPos = vGlobalPos + Clusters[k].getBBox().getCenter(); float z = getHeightMapZ(clusterPos.x, clusterPos.y, zl, options, HeightMap1, HeightMap2); transMatrix.setPos(z * CVector::K); Clusters[k].applyMatrix(transMatrix); } CInstanceGroup *pIGout = new CInstanceGroup; pIGout->build (vGlobalPos, IA, Clusters, Portals, PLN); pIGout->enableRealTimeSunContribution(realTimeSunContribution); chdir (options.OutputIGDir.c_str()); SaveInstanceGroup (vAllFiles[i].c_str(), pIGout); chdir (sCurDir); delete pIG; } } return 1; }