// 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 (const 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); } } } }