// Ryzom - MMORPG Framework // 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 . #include #include #include #include "nel/misc/config_file.h" #include "nel/misc/file.h" #include "nel/misc/i_xml.h" #include "nel/misc/path.h" #include "nel/misc/progress_callback.h" #include "nel/ligo/primitive.h" #include "nel/ligo/ligo_config.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/landscape.h" #include "nel/3d/scene_group.h" #ifdef NL_OS_WINDOWS #include #endif // NL_OS_WINDOWS // *************************************************************************** /* Documentation This tool generates ig from primitive files - Load the landscape zone. Load all the *.zonew found in the InLandscapeDir and add them in a landscape - For each primitive files found in PrimDirs - Look for points with the "prim" primitive class - Get the good height on the landscape - Get the .plant georges file associed with the point - Add an entry in the good ig file - Set the shape filename - Set the plant name - Set the final position (x and y come from the primitive and z from height test with the landscape, the rotation comes from the primitive) - Set the ig date - The date is the most recent date between the .zonew .plant .primitive files associed with the ig. - Snap to ground only modified or created ig - Save the modified or created ig files */ // *************************************************************************** using namespace std; using namespace NLMISC; using namespace NL3D; using namespace NLGEORGES; using namespace NLLIGO; // *************************************************************************** // Utility functions // *************************************************************************** #define SELECTION_EPSILON 0.1f #define BAR_LENGTH 21 const char *progressbar[BAR_LENGTH]= { "[ ]", "[. ]", "[.. ]", "[... ]", "[.... ]", "[..... ]", "[...... ]", "[....... ]", "[........ ]", "[......... ]", "[.......... ]", "[........... ]", "[............ ]", "[............. ]", "[.............. ]", "[............... ]", "[................ ]", "[................. ]", "[.................. ]", "[................... ]", "[....................]" }; // *************************************************************************** // Progress bar class CMyCallback : public IProgressCallback { public: void progress (float progress) { // Delta time, update max all the 300 ms static sint64 time = CTime::getLocalTime (); sint64 currentTime = CTime::getLocalTime (); if ((currentTime - time) > 300) { // Crop the progress bar value progress = getCropedValue (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", DisplayString.c_str (), progressbar[pgId]); uint i; for (i=(uint)strlen(msg); i<79; i++) msg[i]=' '; msg[i]=0; printf ("%s", msg); printf ("\r"); time = currentTime; } } }; // *************************************************************************** sint getXFromZoneName (const string &ZoneName) { string xStr, yStr; uint32 i = 0; while (ZoneName[i] != '_') { yStr += ZoneName[i]; ++i; if (i == ZoneName.size()) return -1; } ++i; while (i < ZoneName.size()) { xStr += ZoneName[i]; ++i; } return ((xStr[0] - 'A')*26 + (xStr[1] - 'A')); } // *************************************************************************** bool getZoneCoordByName(const char * name, uint16& x, uint16& y) { uint i; std::string zoneName(name); // y std::string::size_type ind1 = zoneName.find("_"); if(ind1 == std::string::npos) { nlwarning("bad file name"); return false; } std::string ystr = zoneName.substr(0,ind1); for(i=0; idisplayRaw(sText.c_str()); } // *************************************************************************** string getPrimitiveName (const IPrimitive &primitive) { string name; primitive.getPropertyByName ("name", name); return name; } // *************************************************************************** uint16 getZoneId (sint x, sint y) { return (uint16)((((-y)-1)<<8) + x); } // *************************************************************************** void getLettersFromNum(uint16 num, std::string& code) { if(num>26*26) { nlwarning("zone index too high"); return; } code.resize(0); uint16 remainder = num%26; code += 'A' + num/26; code += 'A' + remainder; } // *************************************************************************** void getZoneNameByCoord(sint16 x, sint16 y, std::string& zoneName) { if ((y>0) || (y<-255) || (x<0) || (x>255)) return; zoneName = toString(-y) + "_"; zoneName += ('A' + (x/26)); zoneName += ('A' + (x%26)); } // *************************************************************************** bool triangleIntersect2DGround (const CTriangle &tri, const CVector &pos0) { const CVector &p0= tri.V0; const CVector &p1= tri.V1; const CVector &p2= tri.V2; // Test if the face enclose the pos in X/Y plane. // NB: compute and using a BBox to do a rapid test is not a very good idea, since it will // add an overhead which is NOT negligeable compared to the following test. float a,b,c; // 2D cartesian coefficients of line in plane X/Y. // Line p0-p1. a= -(p1.y-p0.y); b= (p1.x-p0.x); c= -(p0.x*a + p0.y*b); if( (a*pos0.x + b*pos0.y + c) < 0) return false; // Line p1-p2. a= -(p2.y-p1.y); b= (p2.x-p1.x); c= -(p1.x*a + p1.y*b); if( (a*pos0.x + b*pos0.y + c) < 0) return false; // Line p2-p0. a= -(p0.y-p2.y); b= (p0.x-p2.x); c= -(p2.x*a + p2.y*b); if( (a*pos0.x + b*pos0.y + c) < 0) return false; return true; } // *************************************************************************** // CExportOptions // *************************************************************************** struct CExportOptions { std::string InLandscapeDir; // Directory where to get .zonew files std::string OutIGDir; // Directory where to put IG std::string LandBankFile; // The .smallbank file associated with the landscape std::string LandFarBankFile; // The .farbank file float CellSize; // Typically 160.0 std::string LandTileNoiseDir; // Directory where to get displacement map std::vector PrimDirs; // Directory to parse for .flora and .prim associated // This is here we get continent.cfg file std::string FormDir; // Directory to get georges dfn std::string WorldEditorFiles; CExportOptions (); bool loadcf (NLMISC::CConfigFile &cf); }; // *************************************************************************** CExportOptions::CExportOptions () { CellSize = 160.0f; } // *************************************************************************** bool CExportOptions::loadcf (CConfigFile &cf) { // Out CConfigFile::CVar &cvOutIGDir = cf.getVar("OutIGDir"); OutIGDir = cvOutIGDir.asString(); // In CConfigFile::CVar &cvInLandscapeDir = cf.getVar("ZoneWDir"); InLandscapeDir = cvInLandscapeDir.asString(); CConfigFile::CVar &cvLandBankFile = cf.getVar("SmallBank"); LandBankFile = cvLandBankFile.asString(); CConfigFile::CVar &cvLandFarBankFile = cf.getVar("FarBank"); LandFarBankFile = cvLandFarBankFile.asString(); CConfigFile::CVar &cvLandTileNoiseDir = cf.getVar("DisplaceDir"); LandTileNoiseDir = cvLandTileNoiseDir.asString(); CConfigFile::CVar &cvCellSize = cf.getVar("CellSize"); CellSize = cvCellSize.asFloat(); CConfigFile::CVar &cvPrimDir = cf.getVar("PrimDirs"); uint i; PrimDirs.resize (cvPrimDir.size()); for (i=0; i<(uint)cvPrimDir.size(); i++) PrimDirs[i] = cvPrimDir.asString(i); CConfigFile::CVar &cvFormDir = cf.getVar("FormDir"); FormDir = cvFormDir.asString(); CConfigFile::CVar &cvWorldEditorFiles = cf.getVar("WorldEditorFiles"); WorldEditorFiles = cvWorldEditorFiles.asString(); return true; } // *************************************************************************** // Array of ig class CIgContainer { public: // An ig class CIG { public: // Additionnal parameters class CAdditionnalParam { public: CAdditionnalParam (uint snapLayer, const string &primitiveName, const string &primitiveFile, bool snap) : SnapLayer (snapLayer), PrimitiveName (primitiveName), PrimitiveFile (primitiveFile) { Snap = snap; }; // Snap over the landscape bool Snap; // Snap layer uint SnapLayer; // Primitive name string PrimitiveName; // Primitive file string PrimitiveFile; }; // Default ctor CIG () { Date = 0; } // Update date if new date is more recent void updateDate (uint32 newDate) { if (newDate > Date) Date = newDate; } // The ig CInstanceGroup::TInstanceArray Instances; // Additionnal information vector AdditionnalInfo; // The ig date uint32 Date; }; // Init the container void init (sint minx, sint maxx, sint miny, sint maxy, const char *zoneDir) { // Save the values Minx = minx; Miny = miny; Maxx = maxx+1; Maxy = maxy+1; Width = maxx - minx + 1; // Resize the array IGS.clear (); IGS.resize (Width*(maxy-miny+1)); // Directory string dir = CPath::standardizePath (zoneDir, true); // For each zone for (sint y=miny; y<=maxy; y++) for (sint x=minx; x<=maxx; x++) { // The zone name string zoneFilename; getZoneNameByCoord ((sint16)x, (sint16)y, zoneFilename); zoneFilename = dir + zoneFilename + ".zonew"; // Get the date if (CFile::fileExists (zoneFilename)) get (x, y).Date = CFile::getFileModificationDate (zoneFilename); } } // Get the ig CIG &get (sint x, sint y) { return IGS[(x-Minx)+(y-Miny)*Width]; } // Size and position uint Width; sint Minx; sint Miny; sint Maxx; sint Maxy; // The ig vector vector IGS; }; // *************************************************************************** // Array of ig class CFormContainer { private: // The map value struct CValue { CValue (CSmartPtr ptr, uint32 date) : Ptr (ptr), Date (date) {}; // The form pointer CSmartPtr Ptr; // Its date uint32 Date; }; public: // Default ctor CFormContainer () { _FormLoader = UFormLoader::createLoader (); } // Dtor ~CFormContainer () { UFormLoader::releaseLoader (_FormLoader); } // The form container const UForm *loadForm (const char *formName, uint32 &formDate) { // The form UForm *form = NULL; formDate = 0; // In the map ? string formShortName = NLMISC::toLower(CFile::getFilename (formName)); map::iterator ite = _FormMap.find (formShortName); if (ite == _FormMap.end ()) { // Look for this plant file string path = CPath::lookup (formName, false, false, false); if (!path.empty ()) { // Load it ! form = _FormLoader->loadForm (path.c_str ()); if (form) { // Get dependencies set dependencies; form->getDependencies (dependencies); // Get dependencies dates formDate = 0; set::const_iterator ite = dependencies.begin (); while (ite != dependencies.end ()) { // Get the path name string path = CPath::lookup (*ite, false, false, false); if (!path.empty ()) { // Get the file date uint32 date = CFile::getFileModificationDate (path); // Update date if (date > formDate) formDate = date; } // Next dependency ite++; } // Add it _FormMap.insert (map::value_type (formShortName, CValue (form, formDate))); } else { // Error in the log nlwarning ("Error : Can't load the form (%s)", path.c_str ()); } } } else { form = ite->second.Ptr; formDate = ite->second.Date; } // Return the form or NULL return form; } private: // The form loader UFormLoader *_FormLoader; // The form map map _FormMap; }; // *************************************************************************** void addPointPrimitive (CLandscape &landscape, const char *primFilename, uint32 primFileDate, const IPrimitive &primitive, CIgContainer &igs, const CExportOptions &options, CFormContainer &formContainer, IProgressCallback &callback) { // Is this primitive a point ? const CPrimPoint *point = dynamic_cast(&primitive); if (point) { // Get the class name string className; if (point->getPropertyByName ("class", className)) { // Is it a plant ? if (className == "prim") { // Get its plant name string plantFilename; if (point->getPropertyByName ("form", plantFilename)) { // Add an extension if (NLMISC::toLower(CFile::getExtension (plantFilename)) != "plant") plantFilename += ".plant"; // Load this form uint32 formDate; const UForm *form = formContainer.loadForm (plantFilename.c_str (), formDate); if (form) { // Get the parameters string shape; if (form->getRootNode ().getValueByName (shape, "3D.Shape")) { // Get the position CVector position = point->Point; // Get the scale string scaleText; float scale = 1; if (point->getPropertyByName ("scale", scaleText)) NLMISC::fromString(scaleText, scale); // Get zone coordinates sint x = (sint)floor (position.x / options.CellSize); sint y = (sint)floor (position.y / options.CellSize); if ( (x >= igs.Minx) && (x < igs.Maxx) && (y >= igs.Miny) && (y < igs.Maxy) ) { // Get its layer string text; uint layer = 0; if (point->getPropertyByName ("depth", text)) { layer = atoi (text.c_str ()); } // Not snap yet position.z = 0; // Snap flag bool snap = true; if (point->getPropertyByName ("snap", text)) snap = text != "false"; // Get height if (!snap && point->getPropertyByName ("height", text)) NLMISC::fromString(text, position.z); // *** Add the instance // Create it CInstanceGroup::CInstance instance; instance.Pos = position; instance.Rot = CQuat(CVector::K, point->Angle); instance.Scale = CVector (scale, scale, scale); instance.nParent = -1; instance.Name = shape; instance.InstanceName = NLMISC::toLower(CFile::getFilename (plantFilename)); // Get the instance group ref CIgContainer::CIG &instances = igs.get (x, y); instances.Instances.push_back (instance); instances.AdditionnalInfo.push_back (CIgContainer::CIG::CAdditionnalParam (layer, getPrimitiveName (primitive), primFilename, snap)); // Update the date with the primitive filename instances.updateDate (primFileDate); // Update the date with the plant filename instances.updateDate (formDate); // Update the date with the zone filename string zoneFilename; getZoneNameByCoord (x, y, zoneFilename); zoneFilename = CPath::standardizePath (options.InLandscapeDir, true) + zoneFilename + ".zonew"; // todo hulud needed ? instances.updateDate (zoneFilename); } } else { // Error in the log nlwarning ("Error : Can't get a shape name in the form (%s) for the primitive (%s) in the file (%s)", plantFilename.c_str (), getPrimitiveName (primitive).c_str (), primFilename); } } else { // Error in the log nlwarning ("Error : can't load the file (%s) used by the primitive (%s) in the file (%s).", plantFilename.c_str (), getPrimitiveName (primitive).c_str (), primFilename); } } else { // Error in the log nlwarning ("Error : in file (%s), the primitive (%s) has no plant file.", primFilename, getPrimitiveName (primitive).c_str ()); } } } } // Look in children uint numChildren = primitive.getNumChildren (); for (uint i=0; iaddNegativeFilter ("addSearchPath"); // Register ligo NLLIGO::Register (); if (argc != 2) { printf ("Use : prim_export configfile.cfg\n"); printf ("\nExample of config.cfg\n\n"); printf ("\n// Export Options\n"); printf ("OutIGDir = \"c:/temp/outIG\";\n"); printf ("ZoneWDir = \"c:/temp/inZoneW\";\n"); printf ("SmallBank = \"//amiga/3d/database/landscape/_texture_tiles/jungle/jungle.bank\";\n"); printf ("FarBank = \"//amiga/3d/database/landscape/_texture_tiles/jungle/jungle.farbank\";\n"); printf ("DisplaceDir = \"//amiga/3d/database/landscape/_texture_tiles/displace\";\n"); printf ("CellSize = 160.0;\n"); printf ("PrimDirs = {\"//server/leveldesign/world/fyros\"};\n"); return -1; } try { // Load the config file CExportOptions options; string sTmp = string("loading cfg file : ") + string(argv[1]) + "\n"; // outString(sTmp); { CConfigFile cf; cf.load (argv[1]); if (!options.loadcf(cf)) { sTmp = "Error : options not loaded from config file\n"; outString (sTmp); return -1; } } // *** Add pathes in the search path for georges forms CPath::addSearchPath (options.FormDir, true, true); CPath::addSearchPath (options.WorldEditorFiles, true, true); // Ligo config CLigoConfig config; string path = CPath::lookup ("world_editor_classes.xml", false, false, false); if (path.empty()) nlwarning ("Error : File world_editor_classes.xml not found"); else config.readPrimitiveClass (path.c_str(), false); // *** Load the landscape // Init the landscape CLandscape landscape; landscape.init (); // Get file list vector files; CPath::getPathContent (options.InLandscapeDir, false, false, true, files); // Landscape bounding box sint minx = 0x7fffffff; sint miny = 0x7fffffff; sint maxx = 0x80000000; sint maxy = 0x80000000; // The callback CMyCallback callback; // For each zone files if (files.empty()) { nlwarning ("Error : no zonew files found. Abort."); } else { uint i; for (i=0; imaxx) maxx = x; if (y>maxy) maxy = y; } else { // Error in the log nlwarning ("Error : can't open the file (%s) for reading", files[i].c_str ()); } } catch(const Exception &e) { // Error in the log nlwarning ("Error loading zone file (%s) : %s", files[i].c_str (), e.what ()); } } } // *** Create the igs CIgContainer igs; igs.init (minx, maxx, miny, maxy, options.InLandscapeDir.c_str ()); // *** Create a form container CFormContainer formContainer; // *** For each primitive files // Get the primitive files liste files.clear (); for (i=0; igetZoneBB (); // The bbox used to select triangles CAABBox bbox; CVector &position = instance.Instances[i].Pos; bbox.setCenter (CVector (position.x + SELECTION_EPSILON, position.y + SELECTION_EPSILON, zoneBBox.getMax ().z + SELECTION_EPSILON)); bbox.extend (CVector (position.x - SELECTION_EPSILON, position.y - SELECTION_EPSILON, zoneBBox.getMin ().z - SELECTION_EPSILON)); // Select some triangles vector triangles; landscape.buildTrianglesInBBox (bbox, triangles, 0); // Ray trace triangles set selectedHeight; uint j; for (j=0; j= setSize) { // Error in the log nlwarning ("Error : Layer %d used by the primitive (%s) in the file (%s) doesn't exist. Select layer %d instead.", layer, instance.AdditionnalInfo[i].PrimitiveName.c_str (), instance.AdditionnalInfo[i].PrimitiveFile.c_str (), setSize-1); // New layer layer = setSize-1; } // Invert the layer number layer = setSize - layer - 1; set::iterator ite = selectedHeight.begin (); while (ite != selectedHeight.end ()) { // Good layer ? if (currentLayer == layer) break; // Next layer currentLayer++; ite++; } // Should be found nlassert (ite != selectedHeight.end ()); // Get the final height position.z = *ite; } else { // Error in the log nlwarning ("Error : No landscape under the primitive (%s) in the file (%s).", instance.AdditionnalInfo[i].PrimitiveName.c_str (), instance.AdditionnalInfo[i].PrimitiveFile.c_str ()); } } } } else { // Error in the log nlwarning ("Error : No landscape for the zone (%s)", CFile::getFilename (igFilename).c_str ()); } // Build an instance group CInstanceGroup ig; CVector vGlobalPos = CVector::Null; vector Portals; vector Clusters; ig.build (vGlobalPos, instance.Instances, Portals, Clusters); // *** Save the ig file try { // The file COFile outFile; if (outFile.open (igFilename)) { ig.serial (outFile); // Done outString ("OK " + CFile::getFilename (igFilename) + " \n"); } else { // Error in the log nlwarning ("Error : can't open the file (%s) for writing.", igFilename.c_str ()); } } catch (const Exception &e) { // Error in the log nlwarning ("Error writing the file (%s) : %s", igFilename.c_str (), e.what ()); } } } else { // File exist ? if (CFile::fileExists (igFilename)) { // Done outString ("REMOVE " + CFile::getFilename (igFilename) + " \n"); // Remove it if (!CFile::deleteFile(igFilename)) { // Error in the log nlwarning ("Error : Can't remove the file (%s)", igFilename.c_str ()); } } } } } } } catch (const Exception &e) { string sTmp = string("ERROR : ") + e.what(); outString (sTmp); } return 1; } /* // *** Snap to the ground // Get zone coordinates sint x = (sint)floor (position.x / options.CellSize); sint y = (sint)floor (position.y / options.CellSize); */