423 lines
9.7 KiB
C++
423 lines
9.7 KiB
C++
|
// 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 "prim_checker.h"
|
||
|
|
||
|
// NeL Misc includes
|
||
|
#include "nel/misc/vectord.h"
|
||
|
#include "nel/misc/path.h"
|
||
|
#include "nel/misc/i_xml.h"
|
||
|
#include "nel/misc/aabbox.h"
|
||
|
#include "nel/misc/file.h"
|
||
|
|
||
|
// NeL Ligo includes
|
||
|
#include "nel/ligo/ligo_config.h"
|
||
|
#include "nel/ligo/primitive.h"
|
||
|
|
||
|
// NeL 3d
|
||
|
#include "nel/3d/scene_group.h"
|
||
|
#include "nel/3d/transform_shape.h"
|
||
|
#include "nel/3d/water_model.h"
|
||
|
#include "nel/3d/water_shape.h"
|
||
|
#include "nel/3d/quad_grid.h"
|
||
|
|
||
|
// STL includes
|
||
|
#include <vector>
|
||
|
#include <set>
|
||
|
|
||
|
using namespace NLMISC;
|
||
|
using namespace NLLIGO;
|
||
|
using namespace NL3D;
|
||
|
using namespace std;
|
||
|
|
||
|
NLLIGO::CLigoConfig LigoConfig;
|
||
|
extern bool Verbose;
|
||
|
extern float WaterThreshold;
|
||
|
|
||
|
/*
|
||
|
* Constructor
|
||
|
*/
|
||
|
CPrimChecker::CPrimChecker()
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
* init()
|
||
|
*/
|
||
|
bool CPrimChecker::build(const string &primitivesPath, const string &igLandPath, const string &igVillagePath, const string &outputDirectory, bool forceRebuild)
|
||
|
{
|
||
|
if (Verbose)
|
||
|
nlinfo("Checking pacs.packed_prims consistency");
|
||
|
|
||
|
NLLIGO::Register();
|
||
|
|
||
|
// Init ligo
|
||
|
if (!LigoConfig.readPrimitiveClass ("world_editor_classes.xml", false))
|
||
|
{
|
||
|
// Should be in l:\leveldesign\world_edit_files
|
||
|
nlwarning ("Can't load ligo primitive config file world_editor_classes.xml");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
uint i, j;
|
||
|
string outputfname = CPath::standardizePath(outputDirectory)+"pacs.packed_prims";
|
||
|
|
||
|
_Grid.clear();
|
||
|
|
||
|
vector<string> files;
|
||
|
CPath::getPathContent(primitivesPath, true, false, true, files);
|
||
|
|
||
|
for (i=0; i<files.size(); ++i)
|
||
|
{
|
||
|
if (CFile::getExtension(files[i]) == "primitive")
|
||
|
{
|
||
|
readFile(files[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
files.clear();
|
||
|
CPath::getPathContent(igLandPath, true, false, true, files);
|
||
|
CPath::getPathContent(igVillagePath, true, false, true, files);
|
||
|
|
||
|
set<string> noWaterShapes;
|
||
|
|
||
|
for (i=0; i<files.size(); ++i)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
// load ig associated to the zone
|
||
|
string igname = files[i];
|
||
|
|
||
|
string ignamelookup = CPath::lookup(igname);
|
||
|
//nlinfo("Reading ig '%s'", ignamelookup.c_str());
|
||
|
CIFile igStream(ignamelookup);
|
||
|
CInstanceGroup ig;
|
||
|
igStream.serial(ig);
|
||
|
|
||
|
// search in group for water instance
|
||
|
for (j=0; j<ig._InstancesInfos.size(); ++j)
|
||
|
{
|
||
|
string shapeName = ig._InstancesInfos[j].Name;
|
||
|
if (CFile::getExtension (shapeName) == "")
|
||
|
shapeName += ".shape";
|
||
|
|
||
|
if (noWaterShapes.find(shapeName) != noWaterShapes.end())
|
||
|
continue;
|
||
|
|
||
|
string shapeNameLookup = CPath::lookup (shapeName, false, false);
|
||
|
if (!shapeNameLookup.empty())
|
||
|
{
|
||
|
CIFile f;
|
||
|
if (f.open (shapeNameLookup))
|
||
|
{
|
||
|
CShapeStream shape;
|
||
|
shape.serial(f);
|
||
|
|
||
|
CWaterShape *wshape = dynamic_cast<CWaterShape*>(shape.getShapePointer());
|
||
|
if (wshape == NULL)
|
||
|
{
|
||
|
noWaterShapes.insert(shapeName);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//nlinfo("Render water shape '%s'", shapeNameLookup.c_str());
|
||
|
|
||
|
CMatrix matrix;
|
||
|
ig.getInstanceMatrix(j, matrix);
|
||
|
|
||
|
CPolygon wpoly;
|
||
|
//wshape->getShapeInWorldSpace(wpoly);
|
||
|
CPolygon2D wpoly2d = wshape->getShape();
|
||
|
|
||
|
uint k;
|
||
|
for (k=0; k<wpoly2d.Vertices.size(); ++k)
|
||
|
{
|
||
|
wpoly.Vertices.push_back(matrix * wpoly2d.Vertices[k]);
|
||
|
}
|
||
|
|
||
|
float zwater = wpoly.Vertices[0].z - WaterThreshold;
|
||
|
uint16 idx = (uint16)_WaterHeight.size();
|
||
|
_WaterHeight.push_back(zwater);
|
||
|
render(wpoly, idx);
|
||
|
|
||
|
if (Verbose)
|
||
|
nlinfo("Rendered water shape '%s' in instance '%s'", CFile::getFilenameWithoutExtension(shapeName).c_str(), CFile::getFilenameWithoutExtension(igname).c_str());
|
||
|
}
|
||
|
else if (Verbose)
|
||
|
{
|
||
|
noWaterShapes.insert(shapeName);
|
||
|
nlwarning ("Can't load shape %s", shapeNameLookup.c_str());
|
||
|
}
|
||
|
}
|
||
|
else if (Verbose)
|
||
|
{
|
||
|
noWaterShapes.insert(shapeName);
|
||
|
nlwarning ("Can't find shape %s", shapeName.c_str());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
catch (Exception &e)
|
||
|
{
|
||
|
nlwarning("%s", e.what());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
COFile f;
|
||
|
if (f.open(outputfname))
|
||
|
{
|
||
|
f.serial(_Grid);
|
||
|
f.serialCont(_WaterHeight);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
nlwarning("Couldn't save pacs.packed_prims file '%s'", outputfname.c_str());
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
* load()
|
||
|
*/
|
||
|
bool CPrimChecker::load(const string &outputDirectory)
|
||
|
{
|
||
|
string outputfname = CPath::standardizePath(outputDirectory)+"pacs.packed_prims";
|
||
|
|
||
|
CIFile f;
|
||
|
if (f.open(outputfname))
|
||
|
{
|
||
|
f.serial(_Grid);
|
||
|
f.serialCont(_WaterHeight);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
nlwarning("Couldn't load pacs.packed_prims file '%s'", outputfname.c_str());
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
* readFile()
|
||
|
*/
|
||
|
void CPrimChecker::readFile(const string &filename)
|
||
|
{
|
||
|
string fullpath = CPath::lookup(filename, false);
|
||
|
|
||
|
if (fullpath.empty())
|
||
|
return;
|
||
|
|
||
|
// lookup for primitive file
|
||
|
CIFile f(fullpath);
|
||
|
CIXml xml;
|
||
|
|
||
|
CPrimitives prims;
|
||
|
|
||
|
// load xml file
|
||
|
xml.init(f);
|
||
|
if (Verbose)
|
||
|
nlinfo("Loaded prim file '%s'", filename.c_str());
|
||
|
|
||
|
// read nodes
|
||
|
if (!prims.read(xml.getRootNode(), filename.c_str(), LigoConfig))
|
||
|
{
|
||
|
nlwarning("Can't use primitive file '%s', xml parse error", filename.c_str());
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// get CPrimNode
|
||
|
CPrimNode *primRootNode = prims.RootNode;
|
||
|
|
||
|
// read recursive node
|
||
|
readPrimitive(primRootNode);
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* readPrimitive()
|
||
|
*/
|
||
|
void CPrimChecker::readPrimitive(IPrimitive *primitive)
|
||
|
{
|
||
|
string className;
|
||
|
|
||
|
// check good class and check primitive has a class name
|
||
|
if (dynamic_cast<CPrimZone*>(primitive) != NULL && primitive->getPropertyByName("class", className))
|
||
|
{
|
||
|
if (className == "pacs_include")
|
||
|
render(static_cast<CPrimZone*>(primitive), Include);
|
||
|
else if (className == "pacs_exclude")
|
||
|
render(static_cast<CPrimZone*>(primitive), Exclude);
|
||
|
else if (className == "pacs_cluster_hint")
|
||
|
render(static_cast<CPrimZone*>(primitive), ClusterHint);
|
||
|
}
|
||
|
|
||
|
// parse children
|
||
|
uint i;
|
||
|
for (i=0; i<primitive->getNumChildren(); ++i)
|
||
|
{
|
||
|
IPrimitive *child;
|
||
|
|
||
|
if (!primitive->getChild(child, i))
|
||
|
continue;
|
||
|
|
||
|
readPrimitive(child);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* render()
|
||
|
*/
|
||
|
void CPrimChecker::render(CPrimZone *zone, uint8 bits)
|
||
|
{
|
||
|
if (zone->VPoints.size() < 3)
|
||
|
return;
|
||
|
|
||
|
string name;
|
||
|
if (zone->getPropertyByName("name", name) && Verbose)
|
||
|
nlinfo("Rendering CPrimZone '%s'", name.c_str());
|
||
|
|
||
|
// get the bouding box of the CPrimZone
|
||
|
CAABBox box;
|
||
|
|
||
|
box.setCenter(zone->VPoints[0]);
|
||
|
box.setHalfSize(CVector::Null);
|
||
|
|
||
|
uint i;
|
||
|
for (i=1; i<zone->VPoints.size(); ++i)
|
||
|
box.extend(zone->VPoints[i]);
|
||
|
|
||
|
sint32 xmin, ymin, xmax, ymax;
|
||
|
|
||
|
xmin = (sint32)(floor(box.getMin().x));
|
||
|
ymin = (sint32)(floor(box.getMin().y));
|
||
|
|
||
|
xmax = (sint32)(ceil(box.getMax().x));
|
||
|
ymax = (sint32)(ceil(box.getMax().y));
|
||
|
|
||
|
// Fill grid with points that belong to the CPrimZone
|
||
|
sint32 x, y;
|
||
|
for (y=ymin; y<=ymax; ++y)
|
||
|
for (x=xmin; x<=xmax; ++x)
|
||
|
if (zone->contains(CVector((float)x, (float)y, 0.0f)))
|
||
|
_Grid.set(x, y, bits);
|
||
|
}
|
||
|
|
||
|
|
||
|
/*
|
||
|
* Render a water shape, as a CPolygon
|
||
|
*/
|
||
|
void CPrimChecker::render(const CPolygon &poly, uint16 value)
|
||
|
{
|
||
|
list<CPolygon> convex;
|
||
|
|
||
|
// divide poly in convex polys
|
||
|
if (!poly.toConvexPolygons(convex, CMatrix::Identity))
|
||
|
{
|
||
|
convex.clear();
|
||
|
CPolygon reverse = poly;
|
||
|
std::reverse(reverse.Vertices.begin(), reverse.Vertices.end());
|
||
|
if (!reverse.toConvexPolygons(convex, CMatrix::Identity))
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
list<CPolygon>::iterator it;
|
||
|
for (it=convex.begin(); it!=convex.end(); ++it)
|
||
|
{
|
||
|
CPolygon2D convex2d(*it);
|
||
|
|
||
|
CPolygon2D::TRasterVect rasterized;
|
||
|
sint ymin;
|
||
|
|
||
|
convex2d.computeBorders(rasterized, ymin);
|
||
|
|
||
|
sint dy;
|
||
|
for (dy=0; dy<(sint)rasterized.size(); ++dy)
|
||
|
{
|
||
|
sint x;
|
||
|
|
||
|
for (x=rasterized[dy].first; x<=rasterized[dy].second; ++x)
|
||
|
{
|
||
|
uint8 prevBits = _Grid.get((uint)x, (uint)(ymin+dy));
|
||
|
|
||
|
// only set if there was not a water shape there or if previous was lower
|
||
|
if ((prevBits & Water) != 0)
|
||
|
{
|
||
|
uint16 prevWS = _Grid.index((uint)x, (uint)(ymin+dy));
|
||
|
|
||
|
if (_WaterHeight[value] < _WaterHeight[prevWS])
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
_Grid.index((uint)x, (uint)(ymin+dy), value);
|
||
|
_Grid.set((uint)x, (uint)(ymin+dy), Water);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Render a CPolygon of bit value
|
||
|
*/
|
||
|
void CPrimChecker::renderBits(const CPolygon &poly, uint8 bits)
|
||
|
{
|
||
|
list<CPolygon> convex;
|
||
|
|
||
|
// divide poly in convex polys
|
||
|
if (!poly.toConvexPolygons(convex, CMatrix::Identity))
|
||
|
{
|
||
|
convex.clear();
|
||
|
CPolygon reverse = poly;
|
||
|
std::reverse(reverse.Vertices.begin(), reverse.Vertices.end());
|
||
|
if (!reverse.toConvexPolygons(convex, CMatrix::Identity))
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
list<CPolygon>::iterator it;
|
||
|
for (it=convex.begin(); it!=convex.end(); ++it)
|
||
|
{
|
||
|
CPolygon2D convex2d(*it);
|
||
|
|
||
|
CPolygon2D::TRasterVect rasterized;
|
||
|
sint ymin;
|
||
|
|
||
|
convex2d.computeBorders(rasterized, ymin);
|
||
|
|
||
|
sint dy;
|
||
|
for (dy=0; dy<(sint)rasterized.size(); ++dy)
|
||
|
{
|
||
|
sint x;
|
||
|
|
||
|
for (x=rasterized[dy].first; x<=rasterized[dy].second; ++x)
|
||
|
{
|
||
|
_Grid.set((uint)x, (uint)(ymin+dy), bits);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
|