// 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 (remove (igFilename.c_str ()) != 0)
{
// 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);
*/