// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/> // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. #include "nel/misc/stream.h" #include "nel/misc/file.h" #include "nel/misc/vector.h" #include "nel/misc/time_nl.h" #include "nel/misc/config_file.h" #include "nel/misc/path.h" #include "nel/georges/u_form.h" #include "nel/georges/u_form_elm.h" #include "nel/georges/u_form_loader.h" #include "nel/3d/zone.h" #include "nel/3d/zone_lighter.h" #include "nel/3d/quad_grid.h" #include "nel/3d/landscape.h" #include "nel/3d/scene_group.h" #include "nel/3d/shape.h" #include "nel/3d/transform_shape.h" #include "nel/3d/register_3d.h" #include "nel/3d/water_shape.h" #include "../zone_lib/zone_utility.h" using namespace std; using namespace NLMISC; using namespace NL3D; #define BAR_LENGTH 21 const char *progressbar[BAR_LENGTH]= { "[ ]", "[. ]", "[.. ]", "[... ]", "[.... ]", "[..... ]", "[...... ]", "[....... ]", "[........ ]", "[......... ]", "[.......... ]", "[........... ]", "[............ ]", "[............. ]", "[.............. ]", "[............... ]", "[................ ]", "[................. ]", "[.................. ]", "[................... ]", "[....................]" }; // My zone lighter class CMyZoneLighter : public CZoneLighter { // Progress bar virtual void progress (const char *message, float progress) { // Progress bar char msg[512]; uint pgId= (uint)(progress*(float)BAR_LENGTH); pgId= min(pgId, (uint)(BAR_LENGTH-1)); sprintf (msg, "\r%s: %s", message, progressbar[pgId]); uint i; for (i=(uint)strlen(msg); i<79; i++) msg[i]=' '; msg[i]=0; printf ("%s\r", msg); } }; struct CInstanceGroupRef { CInstanceGroup *IG; bool AddLight; }; //======================================================================================= // load additionnal ig from a village (ryzom specific) static void loadIGFromVillage(const NLGEORGES::UFormElm *villageItem, const std::string &continentName, uint villageIndex, std::list<CInstanceGroupRef> &instanceGroups, CConfigFile::CVar &additionalIgNames) { const NLGEORGES::UFormElm *igNamesItem; if (! (villageItem->getNodeByName (&igNamesItem, "IgList") && igNamesItem) ) { nlwarning("No list of IGs was found in the continent form %s, village #%d", continentName.c_str(), villageIndex); return; } // Get number of village uint numIgs; nlverify (igNamesItem->getArraySize (numIgs)); const NLGEORGES::UFormElm *currIg; for(uint l = 0; l < numIgs; ++l) { if (!(igNamesItem->getArrayNode (&currIg, l) && currIg)) { nlwarning("Couldn't get ig #%d in the continent form %s, in village #%d", l, continentName.c_str(), villageIndex); continue; } const NLGEORGES::UFormElm *igNameItem; currIg->getNodeByName (&igNameItem, "IgName"); std::string igName; if (!igNameItem->getValue (igName)) { nlwarning("Couldn't get ig name of ig #%d in the continent form %s, in village #%d", l, continentName.c_str(), villageIndex); continue; } if (igName.empty()) { nlwarning("Ig name of ig #%d in the continent form %s, in village #%d is an empty string", l, continentName.c_str(), villageIndex); continue; } // ensure .ig igName = CFile::getFilenameWithoutExtension(igName) + ".ig"; // verify that the ig is not already added (case of tr_water.ig in additional_igs) for(uint igAdd= 0;igAdd<(uint)additionalIgNames.size();igAdd++) { if( toLower(additionalIgNames.asString()) == toLower(igName) ) { nlwarning("Skipping Village Ig %s, cause already exist in additional ig", igName.c_str()); continue; } } // add this ig string nameLookup = CPath::lookup (igName, false, true); if (!nameLookup.empty()) { CIFile inputFile; // Try to open the file if (inputFile.open (nameLookup)) { // New ig std::auto_ptr<CInstanceGroup> group(new CInstanceGroup); try { group->serial (inputFile); } catch(const NLMISC::Exception &) { nlwarning ("Error while loading instance group %s\n", igName.c_str()); continue; } inputFile.close(); // Add to the list CInstanceGroupRef iref; iref.IG = group.release(); iref.AddLight = false; instanceGroups.push_back (iref); } else { // Error nlwarning ("Can't open instance group %s\n", igName.c_str()); } } } } //======================================================================================= // load additionnal ig from a continent (ryzom specific) static void loadIGFromContinent(NLMISC::CConfigFile ¶meter, std::list<CInstanceGroupRef> &instanceGroups, const std::vector<std::string> &zoneNameArray, CConfigFile::CVar &additionalIgNames) { try { CConfigFile::CVar &continent_name_var = parameter.getVar ("continent_name"); CConfigFile::CVar &level_design_directory = parameter.getVar ("level_design_directory"); CConfigFile::CVar &level_design_world_directory = parameter.getVar ("level_design_world_directory"); CConfigFile::CVar &level_design_dfn_directory = parameter.getVar ("level_design_dfn_directory"); CPath::addSearchPath(level_design_dfn_directory.asString(), true, false); CPath::addSearchPath(level_design_world_directory.asString(), true, false); std::string continentName = continent_name_var.asString(); if (CFile::getExtension(continentName).empty()) continentName += ".continent"; // Load the form NLGEORGES::UFormLoader *loader = NLGEORGES::UFormLoader::createLoader(); // std::string pathName = level_design_world_directory.asString() + "/" + continentName; if (pathName.empty()) { nlwarning("Can't find continent form : %s", continentName.c_str()); return; } NLGEORGES::UForm *villageForm; villageForm = loader->loadForm(pathName.c_str()); if(villageForm != NULL) { NLGEORGES::UFormElm &rootItem = villageForm->getRootNode(); // try to get the village list // Load the village list NLGEORGES::UFormElm *villagesItem; if(!(rootItem.getNodeByName (&villagesItem, "Villages") && villagesItem)) { nlwarning("No villages where found in %s", continentName.c_str()); return; } // Get number of village uint numVillage; nlverify (villagesItem->getArraySize (numVillage)); // For each village for(uint k = 0; k < numVillage; ++k) { NLGEORGES::UFormElm *currVillage; if (!(villagesItem->getArrayNode (&currVillage, k) && currVillage)) { nlwarning("Couldn't get village %d in continent %s", continentName.c_str(), k); continue; } // check that this village is in the dependency zones NLGEORGES::UFormElm *zoneNameItem; if (!currVillage->getNodeByName (&zoneNameItem, "Zone") && zoneNameItem) { nlwarning("Couldn't get zone item of village %d in continent %s", continentName.c_str(), k); continue; } std::string zoneName; if (!zoneNameItem->getValue(zoneName)) { nlwarning("Couldn't get zone name of village %d in continent %s", continentName.c_str(), k); continue; } zoneName = CFile::getFilenameWithoutExtension(zoneName); for(uint l = 0; l < zoneNameArray.size(); ++l) { if (NLMISC::nlstricmp(CFile::getFilenameWithoutExtension(zoneNameArray[l]), zoneName) == 0) { // ok, it is in the dependant zones loadIGFromVillage(currVillage, continentName, k, instanceGroups, additionalIgNames); break; } } } } else { nlwarning("Can't load continent form : %s", continentName.c_str()); } } catch (const NLMISC::EUnknownVar &e) { nlinfo(e.what()); } } //======================================================================================= int main(int argc, char* argv[]) { // Start time TTime time=CTime::getLocalTime (); // Filter addSearchPath NLMISC::createDebug(); InfoLog->addNegativeFilter ("adding the path"); WarningLog->addNegativeFilter ("continent.cfg"); // Register 3d registerSerial3d (); // Good number of args ? if (argc<5) { // Help message printf ("%s [zonein.zone] [zoneout.zone] [parameter_file] [dependancy_file] [-waterpatch bkupdir] \n", argv[0]); } else { // to patch only the tiles flags bool tileWaterPatchOnly; tileWaterPatchOnly= argc==7 && string(argv[5])=="-waterpatch"; string tileWaterPatchBkupDir; if(tileWaterPatchOnly) tileWaterPatchBkupDir = argv[6]; // Ok, read the zone CIFile inputFile; // Get extension string ext=getExt (argv[1]); string dir=getDir (argv[1]); // Open it for reading if (inputFile.open (argv[1])) { // Zone name string zoneName=toLower (string ("zone_"+getName (argv[1]))); // Load the zone try { // Read the config file CConfigFile parameter; CConfigFile dependency; // Load and parse the dependency file parameter.load (argv[3]); // ********** // *** Build the lighter descriptor // ********** CZoneLighter::CLightDesc lighterDesc; // Get bank name CConfigFile::CVar &bank_name = parameter.getVar ("bank_name"); // Load instance group ? CConfigFile::CVar &load_ig= parameter.getVar ("load_ig"); bool loadInstanceGroup = load_ig.asInt ()!=0; CConfigFile::CVar &additionnal_ig = parameter.getVar ("additionnal_ig"); // Grid size CConfigFile::CVar &quad_grid_size = parameter.getVar ("quad_grid_size"); lighterDesc.GridSize=quad_grid_size.asInt(); // Grid size CConfigFile::CVar &quad_grid_cell_size = parameter.getVar ("quad_grid_cell_size"); lighterDesc.GridCellSize=quad_grid_cell_size.asFloat(); // Heightfield cell size CConfigFile::CVar &global_illumination_cell_size = parameter.getVar ("global_illumination_cell_size"); lighterDesc.HeightfieldCellSize=global_illumination_cell_size.asFloat(); // Heightfield size CConfigFile::CVar &global_illumination_length = parameter.getVar ("global_illumination_length"); lighterDesc.HeightfieldSize=global_illumination_length.asFloat(); // Light direction CConfigFile::CVar &sun_direction = parameter.getVar ("sun_direction"); lighterDesc.SunDirection.x=sun_direction.asFloat(0); lighterDesc.SunDirection.y=sun_direction.asFloat(1); lighterDesc.SunDirection.z=sun_direction.asFloat(2); lighterDesc.SunDirection.normalize (); // Light center position CConfigFile::CVar &sun_center = parameter.getVar ("sun_center"); lighterDesc.SunCenter.x=sun_center.asFloat(0); lighterDesc.SunCenter.y=sun_center.asFloat(1); lighterDesc.SunCenter.z=sun_center.asFloat(2); // Light distance CConfigFile::CVar &sun_distance = parameter.getVar ("sun_distance"); lighterDesc.SunDistance=sun_distance.asFloat(); // Light FOV CConfigFile::CVar &sun_fov = parameter.getVar ("sun_fov"); lighterDesc.SunFOV=sun_fov.asFloat(); // Light radius CConfigFile::CVar &sun_radius = parameter.getVar ("sun_radius"); lighterDesc.SunRadius=sun_radius.asFloat(); // ZBuffer landscape size CConfigFile::CVar &zbuffer_landscape_size = parameter.getVar ("zbuffer_landscape_size"); lighterDesc.ZBufferLandscapeSize=zbuffer_landscape_size.asInt(); // ZBuffer object size CConfigFile::CVar &zbuffer_object_size = parameter.getVar ("zbuffer_object_size"); lighterDesc.ZBufferObjectSize=zbuffer_object_size.asInt(); // Soft shadow samples sqrt CConfigFile::CVar &soft_shadow_samples_sqrt = parameter.getVar ("soft_shadow_samples_sqrt"); lighterDesc.SoftShadowSamplesSqrt=soft_shadow_samples_sqrt.asInt(); // Soft shadow jitter CConfigFile::CVar &soft_shadow_jitter = parameter.getVar ("soft_shadow_jitter"); lighterDesc.SoftShadowJitter=soft_shadow_jitter.asFloat(); // Water rendering parameters CConfigFile::CVar &water_zbias = parameter.getVar ("water_shadow_bias"); lighterDesc.WaterShadowBias = water_zbias.asFloat(); CConfigFile::CVar &water_ambient = parameter.getVar ("water_ambient"); lighterDesc.WaterAmbient = water_ambient.asFloat(); CConfigFile::CVar &water_diffuse = parameter.getVar ("water_diffuse"); lighterDesc.WaterDiffuse = water_diffuse.asFloat(); CConfigFile::CVar &modulate_water_color = parameter.getVar ("modulate_water_color"); lighterDesc.ModulateWaterColor = modulate_water_color.asInt() != 0; CConfigFile::CVar &sky_contribution_for_water = parameter.getVar ("sky_contribution_for_water"); lighterDesc.SkyContributionForWater = sky_contribution_for_water.asInt() != 0; // Number of CPU CConfigFile::CVar &cpu_num = parameter.getVar ("cpu_num"); lighterDesc.NumCPU=cpu_num.asInt (); // Sun contribution CConfigFile::CVar &sun_contribution = parameter.getVar ("sun_contribution"); lighterDesc.SunContribution=sun_contribution.asInt ()!=0; // Shadows enabled ? CConfigFile::CVar &shadow = parameter.getVar ("shadow"); lighterDesc.Shadow=shadow.asInt ()!=0; // Sky contribution CConfigFile::CVar &sky_contribution = parameter.getVar ("sky_contribution"); lighterDesc.SkyContribution=sky_contribution.asInt ()!=0; // Sky contribution CConfigFile::CVar &sky_intensity = parameter.getVar ("sky_intensity"); lighterDesc.SkyIntensity=sky_intensity.asFloat (); // Vegetable Height CConfigFile::CVar &vegetable_height = parameter.getVar ("vegetable_height"); lighterDesc.VegetableHeight=vegetable_height.asFloat (); // Shadow are enabled ? if (lighterDesc.Shadow) { // Load and parse the dependency file dependency.load (argv[4]); } // Get the search pathes CConfigFile::CVar &search_pathes = parameter.getVar ("search_pathes"); uint path; for (path = 0; path < (uint)search_pathes.size(); path++) { // Add to search path CPath::addSearchPath (search_pathes.asString(path)); } // A landscape allocated with new: it is not delete because destruction take 3 secondes more! CLandscape *landscape=new CLandscape; landscape->init(); // A zone lighter CMyZoneLighter lighter; lighter.init (); // A vector of zone id vector<uint> listZoneId; // The zone CZone zone; // List of ig std::list<CInstanceGroupRef> instanceGroup; // Load zone.serial (inputFile); inputFile.close(); bool zoneIgLoaded = false; // Load ig of the zone string igName = getName (argv[1])+".ig"; string igNameLookup = CPath::lookup (igName, false, false); if (!igNameLookup.empty()) igName = igNameLookup; if (inputFile.open (igName)) { // New ig CInstanceGroup *group=new CInstanceGroup; // Serial it group->serial (inputFile); inputFile.close(); // Add to the list CInstanceGroupRef iref; iref.IG = group; iref.AddLight = true; instanceGroup.push_back (iref); zoneIgLoaded = true; } else { // Warning fprintf (stderr, "Warning: can't load instance group %s\n", igName.c_str()); zoneIgLoaded = false; } // Load the bank string bankName = bank_name.asString(); string bankNameLookup = CPath::lookup (bankName, false, false); if (!bankNameLookup.empty()) bankName = bankNameLookup; if (inputFile.open (bankName)) { try { // Load landscape->TileBank.serial (inputFile); landscape->initTileBanks(); } catch (const Exception &e) { // Error nlwarning ("ERROR error loading tile bank %s\n%s\n", bankName.c_str(), e.what()); } } else { // Error nlwarning ("ERROR can't load tile bank %s\n", bankName.c_str()); } // Add the zone landscape->addZone (zone); listZoneId.push_back (zone.getZoneId()); // Continue to build ? bool continu=true; // Try to load additionnal instance group. // Additionnal instance group if (loadInstanceGroup) { try { for (uint add=0; add<(uint)additionnal_ig.size(); add++) { // Input file CIFile inputFile; // Name of the instance group string name = additionnal_ig.asString(add); string nameLookup = CPath::lookup (name, false, false); if (!nameLookup.empty()) name = nameLookup; // Try to open the file if (inputFile.open (name)) { // New ig CInstanceGroup *group=new CInstanceGroup; // Serial it group->serial (inputFile); inputFile.close(); // Add to the list CInstanceGroupRef iref; iref.IG = group; iref.AddLight = false; instanceGroup.push_back (iref); } else { // Error nlwarning ("ERROR can't load instance group %s\n", name.c_str()); // Stop before build continu=false; } } } catch (const NLMISC::EUnknownVar &) { nlinfo("No additionnal ig's to load"); } } // *** Scan dependency file if (lighterDesc.Shadow) { CConfigFile::CVar &dependant_zones = dependency.getVar ("dependencies"); std::vector<std::string> zoneNameArray; zoneNameArray.reserve(1 + (uint)dependant_zones.size()); zoneNameArray.push_back(argv[1]); for (uint i=0; i<(uint)dependant_zones.size(); i++) { // Get zone name string zoneName=dependant_zones.asString(i); zoneNameArray.push_back(zoneName); // Load the zone CZone zoneBis; // Open it for reading if (inputFile.open (dir+zoneName+ext)) { // Read it zoneBis.serial (inputFile); inputFile.close(); // Add the zone landscape->addZone (zoneBis); listZoneId.push_back (zoneBis.getZoneId()); } else { // Error message and continue nlwarning ("ERROR can't load zone %s\n", (dir+zoneName+ext).c_str()); } // Try to load an instance group. if (loadInstanceGroup) { string name = zoneName+".ig"; string nameLookup = CPath::lookup (name, false, false); if (!nameLookup.empty()) name = nameLookup; // Name of the instance group if (inputFile.open (name)) { // New ig CInstanceGroup *group=new CInstanceGroup; // Serial it group->serial (inputFile); inputFile.close(); // Add to the list CInstanceGroupRef iref; iref.IG = group; iref.AddLight = true; instanceGroup.push_back (iref); } else { // Error message and continue nlwarning ("WARNING can't load instance group %s\n", name.c_str()); } } } if (loadInstanceGroup) { // Ryzom specific : additionnal villages from a continent form loadIGFromContinent(parameter, instanceGroup, zoneNameArray, additionnal_ig); } } // A vector of CZoneLighter::CTriangle vector<CZoneLighter::CTriangle> vectorTriangle; // ********** // *** Build triangle array // ********** landscape->checkBinds (); // Add triangles from landscape landscape->enableAutomaticLighting (false); // no need to add obstacles in case of water patch if(!tileWaterPatchOnly) lighter.addTriangles (*landscape, listZoneId, 0, vectorTriangle); // Map of shape std::map<string, IShape*> shapeMap; // For each instance group std::list<CInstanceGroupRef>::iterator ite=instanceGroup.begin(); while (ite!=instanceGroup.end()) { // Instance group CInstanceGroup *group=ite->IG; // Load and add shapes if (lighterDesc.Shadow) { // For each instance for (uint instance=0; instance<group->getNumInstance(); instance++) { // Get the instance shape name string name=group->getShapeName (instance); if (!name.empty()) { // Skip it?? use the DontCastShadowForExterior flag. See doc of this flag if(group->getInstance(instance).DontCastShadow || group->getInstance(instance).DontCastShadowForExterior) continue; // PS ? if (toLower (CFile::getExtension (name)) == "ps") continue; // Add a .shape at the end ? if (name.find('.') == std::string::npos) name += ".shape"; // Add path string nameLookup = CPath::lookup (name, false, false); if (!nameLookup.empty()) name = nameLookup; // Find the shape in the bank std::map<string, IShape*>::iterator iteMap=shapeMap.find (name); if (iteMap==shapeMap.end()) { // Input file CIFile inputFile; if (inputFile.open (name)) { // Load it CShapeStream stream; stream.serial (inputFile); // Get the pointer iteMap=shapeMap.insert (std::map<string, IShape*>::value_type (name, stream.getShapePointer ())).first; } else { // Error nlwarning ("WARNING can't load shape %s\n", name.c_str()); } } // Loaded ? if (iteMap!=shapeMap.end()) { // Build the matrix CMatrix scale; scale.identity (); scale.scale (group->getInstanceScale (instance)); CMatrix rot; rot.identity (); rot.setRot (group->getInstanceRot (instance)); CMatrix pos; pos.identity (); pos.setPos (group->getInstancePos (instance)); CMatrix mt=pos*rot*scale; // Add triangles // no need to add obstacles in case of water patch if(!tileWaterPatchOnly) lighter.addTriangles (*iteMap->second, mt, vectorTriangle); /** If it is a lightable shape and we are dealing with the ig of the main zone, * add it to the lightable shape list */ IShape *shape = iteMap->second; if (ite == instanceGroup.begin() /* are we dealing with main zone */ && zoneIgLoaded /* ig of the main zone loaded successfully (so its indeed the ig of the first zone) ? */ && CZoneLighter::isLightableShape(*shape) && !tileWaterPatchOnly ) { lighter.addLightableShape(shape, mt); } /** If it is a water shape, add it to the lighter, so that it can check * which tiles are above / below water for this zone. The result is saved in the flags of tiles. * A tile that have their flags set to VegetableDisabled won't get setupped */ if (dynamic_cast<NL3D::CWaterShape *>(shape)) { lighter.addWaterShape(static_cast<NL3D::CWaterShape *>(shape), mt); } } } } } // For each point light of the ig. No need wor tileWaterPatchOnly if (ite->AddLight && !tileWaterPatchOnly) { const std::vector<CPointLightNamed> &pointLightList= group->getPointLightList(); for (uint plId=0; plId<pointLightList.size(); plId++) { // Add it to the Ig. lighter.addStaticPointLight(pointLightList[plId]); } } // Next instance group ite++; } // If no waterpatch, and no WaterShape at all, no op bool tileWaterSkip= false; if(continu && tileWaterPatchOnly && lighter.getNumWaterShape()==0) { nlinfo("NO WATER INTERSECTION FOUND: don't patch at all"); continu= false; tileWaterSkip= true; } // Continue ? if (continu) { // ********** // *** Light! // ********** // Output zone CZone output; // normal lighting if(!tileWaterPatchOnly) { // Light the zone lighter.light (*landscape, output, zone.getZoneId(), lighterDesc, vectorTriangle, listZoneId); } else { // Load the zonel. CIFile zonelFile; // load the zonel (keep lighting) if (zonelFile.open (argv[2])) { // load the new zone try { // load it output.serial (zonelFile); } catch (const Exception& except) { // Error message nlwarning ("ERROR reading %s: %s\n", argv[2], except.what()); throw; } } else { // Error can't open the file nlwarning ("ERROR Can't open %s for reading\n", argv[1]); throw Exception("ERROR Can't open the zonel for tile water patching. abort"); } zonelFile.close(); // Bkup the zone COFile outputFile; string bkupFile= tileWaterPatchBkupDir + "/" + CFile::getFilename(argv[2]); if (outputFile.open (bkupFile)) { try { output.serial (outputFile); } catch (const Exception& except) { nlwarning ("ERROR backuping %s: %s\n", bkupFile.c_str(), except.what()); } outputFile.close(); } else nlwarning ("ERROR Can't open %s for writing\n", bkupFile.c_str()); // patch water flags lighter.computeTileFlagsOnly(*landscape, output, zone.getZoneId(), lighterDesc, listZoneId); } // Save the zone COFile outputFile; // Open it if (outputFile.open (argv[2])) { // Save the new zone try { // Save it output.serial (outputFile); } catch (const Exception& except) { // Error message nlwarning ("ERROR writing %s: %s\n", argv[2], except.what()); } } else { // Error can't open the file nlwarning ("ERROR Can't open %s for writing\n", argv[2]); } // Compute time printf ("\rCompute time: %d ms \r", (uint)(CTime::getLocalTime ()-time)); } else { if(!tileWaterSkip) // Error nlwarning ("ERROR Abort: files are missing.\n"); } } catch (const Exception& except) { // Error message nlwarning ("ERROR %s\n", except.what()); } } else { // Error can't open the file nlwarning ("ERROR Can't open %s for reading\n", argv[1]); } } // Landscape is not deleted, nor the instanceGroups, for faster quit. // Must disalbe BlockMemory checks (for pointLights). NL3D_BlockMemoryAssertOnPurge= false; // Compute time printf ("\rCompute time: %d ms \n", (uint)(CTime::getLocalTime ()-time)); // exit. return 0; }