// 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 . //------------------------------------------------------------------------------------------------- // includes //------------------------------------------------------------------------------------------------- // Nel misc #include "nel/misc/types_nl.h" #include "nel/misc/command.h" #include "nel/misc/path.h" #include "nel/misc/sstring.h" #include "nel/misc/file.h" // Game share #include "server_share/bmp4image.h" // AI share #include "ai_share/world_map.h" //------------------------------------------------------------------------------------------------- // using namespaces... //------------------------------------------------------------------------------------------------- using namespace std; using namespace NLMISC; using namespace RYAI_MAP_CRUNCH; //------------------------------------------------------------------------------------------------- // External variables... //------------------------------------------------------------------------------------------------- extern string OutputPath; //------------------------------------------------------------------------------------------------- // Local type definitions //------------------------------------------------------------------------------------------------- typedef uint16 TBufferEntry; typedef std::vector TBuffer; typedef std::vector TOffsetsVector; //------------------------------------------------------------------------------------------------- // class CProximityZone //------------------------------------------------------------------------------------------------- class CProximityZone { public: typedef std::vector TOffsets; // ctor // scanWidth and scanHeight define the dimentions of the buffer that this zone is being extracted from // xOffset and yOffset are for re-mapping coordinates from buffer-relative to absolute ryzom world coordinates CProximityZone(uint32 scanWidth=0,uint32 scanHeight=0,sint32 xOffset=0, sint32 yOffset=0); // add an 'accessible position' to a zone (offset is y*scanWidth+x) bool add(uint32 offset); // zone dimention accessors (note that zone dimentions line up with 160x160 world zones) // note that this means that the zone bounds may extend outside the scan area uint32 getZoneWidth() const; uint32 getZoneHeight() const; sint32 getZoneXMin() const; sint32 getZoneYMin() const; uint32 getZoneXMax() const; uint32 getZoneYMax() const; // read accessors for the ryzom world coordinate offsets sint32 getXOffset() const; sint32 getYOffset() const; // read accessors for the bounding limits that define the area occupied by the accessible points uint32 getBoundXMin() const; uint32 getBoundYMin() const; uint32 getBoundXMax() const; uint32 getBoundYMax() const; // read accessor for the _Offsets vector // this is a vector of offsets into the scan area. It needs to be remapped to zone offsets // via the remapOffset() routine in order to be used to index into a zone buffer const TOffsets& getOffsets() const; // remap a scan buffer offset to a zone offset by decomposing into x and y parts and // subtracting getZoneXMin() and getZoneYMin() uint32 remapOffset(uint32 bufferOffset) const; private: // parameters setup at construction time, giving info on the context that we're in uint32 _ScanWidth; uint32 _ScanHeight; sint32 _XOffset; sint32 _YOffset; uint32 _MaxOffset; // the vector of points that are part of this zone TOffsets _Offsets; // the min and max coords of the points that are part of this zone uint32 _XMax; uint32 _XMin; uint32 _YMax; uint32 _YMin; }; //------------------------------------------------------------------------------------------------- // class CProximityMapBuffer //------------------------------------------------------------------------------------------------- class CProximityMapBuffer { public: typedef std::vector TZones; // load a cwmap2 file and setup this object from its contents // the 'name' parameter is the full file name of the file to load with path and extension void load(const std::string& name); // scan the buffer to generate the set of non-connecting zones that it contains void calculateZones(TZones& zones); // generate the proximity map for a given zone void generateZoneProximityMap(const CProximityZone& zone,TBuffer& zoneBuffer); // read accessors... const TBuffer& getBuffer() const; uint32 getScanHeight() const; uint32 getScanWidth() const; private: // private routine used by generateZoneProximityMap() to setup the zoneBuffer with the accessible points set void _prepareBufferForZoneProximityMap(const CProximityZone& zone,TBuffer& zoneBuffer,TOffsetsVector& accessiblePoints); private: // the width and heilght of the scan zone (ie the dimentions of the buffer) uint32 _ScanWidth; uint32 _ScanHeight; // vector representing 2d array of points [_ScanHeight][_ScanWidth] TBuffer _Buffer; // buffer coordinate to world coordinate offsets... sint32 _XOffset; sint32 _YOffset; }; //------------------------------------------------------------------------------------------------- // Handy utility routines //------------------------------------------------------------------------------------------------- static void writeProximityBufferToTgaFile(const std::string& fileName,const TBuffer& buffer,uint32 scanWidth,uint32 scanHeight) { uint imageWidth = (scanWidth+15)&~15; uint imageHeight = (scanHeight); CTGAImageGrey tgaImage; tgaImage.setup((uint16)imageWidth, (uint16)imageHeight, fileName, 0, 0); for (uint32 y=0;y255*5)?255:value/5); } tgaImage.writeLine(); } } static void processProximityBuffer(const TBuffer& inputBuffer, uint32 lineLength, TBuffer& resultBuffer) { // a couple of constants to control the range over which our degressive filter is to be applied const uint32 smallValue= 2*5; const uint32 bigValue= 15*5; // determine numer of lines in the buffer... uint32 numLines= (uint32)inputBuffer.size()/ lineLength; // clear out the result buffer and reset all values to 5*255, remembering that this is the correct value for the image edges resultBuffer.clear(); resultBuffer.resize(inputBuffer.size(),(TBufferEntry)5*255); for (uint32 y=1;ybigValue) value=5*255; else value= (uint32)(((1.0-cos(3.14159265359*(float(value-smallValue)/(float)(bigValue-smallValue))))/2.0)*float(5*255)); // store the value into the result buffer resultBuffer[offset]= (TBufferEntry)value; } } } //------------------------------------------------------------------------------------------------- // methods CProximityZone //------------------------------------------------------------------------------------------------- CProximityZone::CProximityZone(uint32 scanWidth,uint32 scanHeight,sint32 xOffset, sint32 yOffset) { _ScanWidth = scanWidth; _ScanHeight = scanHeight; _XOffset = xOffset; _YOffset = yOffset; _MaxOffset = scanWidth * scanHeight -1; _XMin = ~0u; _YMin = ~0u; _XMax = 0; _YMax = 0; } bool CProximityZone::add(uint32 offset) { // make sure the requested point is in the zone if (offset>_MaxOffset) return false; // calculate the x and y coordinates of the point uint32 y= offset/ _ScanWidth; uint32 x= offset% _ScanWidth; // update the bounding coordinates for this zone if (x<_XMin) _XMin= x; if (x>_XMax) _XMax= x; if (y<_YMin) _YMin= y; if (y>_YMax) _YMax= y; // add the point to the vector of points _Offsets.push_back(offset); return true; } const CProximityZone::TOffsets& CProximityZone::getOffsets() const { return _Offsets; } uint32 CProximityZone::getZoneWidth() const { return getZoneXMax()- getZoneXMin() +1; } uint32 CProximityZone::getZoneHeight() const { return getZoneYMax()- getZoneYMin() +1; } sint32 CProximityZone::getZoneXMin() const { return (_XMin+_XOffset)/160*160-_XOffset; } sint32 CProximityZone::getZoneYMin() const { return (_YMin+_YOffset)/160*160-_YOffset; } uint32 CProximityZone::getZoneXMax() const { return (((_XMax+_XOffset)/160+1)*160)-_XOffset-1; } uint32 CProximityZone::getZoneYMax() const { return (((_YMax+_YOffset)/160+1)*160)-_YOffset-1; } sint32 CProximityZone::getXOffset() const { return _XOffset+getZoneXMin(); } sint32 CProximityZone::getYOffset() const { return _YOffset+getZoneYMin(); } uint32 CProximityZone::getBoundXMin() const { return _XMin-getZoneXMin(); } uint32 CProximityZone::getBoundYMin() const { return _YMin-getZoneYMin(); } uint32 CProximityZone::getBoundXMax() const { return _XMax-getZoneXMin(); } uint32 CProximityZone::getBoundYMax() const { return _YMax-getZoneYMin(); } uint32 CProximityZone::remapOffset(uint32 bufferOffset) const { // decompose input coordinates into x and y parts uint32 bufferX= bufferOffset% _ScanWidth; uint32 bufferY= bufferOffset/ _ScanWidth; // remap the offset from a _Buffer-relative offset to a zone-relative offset return bufferX-getZoneXMin()+ (bufferY-getZoneYMin())*getZoneWidth(); } //------------------------------------------------------------------------------------------------- // methods CProximityMapBuffer //------------------------------------------------------------------------------------------------- void CProximityMapBuffer::load(const std::string& name) { // load the AI collision map file CWorldMap worldMap; CIFile f(name); f.serial(worldMap); // lookup the map bounds CMapPosition min, max; worldMap.getBounds(min, max); // calculate a handful of constants relating to the bounds of the image... _ScanWidth = max.x()-min.x(); _ScanHeight = max.y()-min.y(); _XOffset= min.x(); _YOffset= max.y(); // redimension buffer to correct size _Buffer.resize(_ScanWidth*_ScanHeight); // setup a position variable to mark the start point of each line CMapPosition scanpos(min.x(),min.y()); // iterate over the scan area looking for points that are accessible for (uint32 y=0; y<_ScanHeight; ++y, scanpos = scanpos.getStepN()) { CMapPosition pos(scanpos); // scan a line of the map for (uint32 x=0; x<_ScanWidth; ++x, pos = pos.getStepE()) { bool isAccessible= false; // if the cell pointer is NULL it means that the 16x16 cell in question is inaccessible if (worldMap.getRootCellCst(pos) != NULL) { // run through the surfaces in the cell looking for a match for this position (may be as many as 3 surfaces per cell max) for (uint32 ns=0; ns<3; ++ns) { isAccessible |= worldMap.getSafeWorldPosition(pos, CSlot(ns)).isValid(); } } // setup the next pixel in the output buffers... _Buffer[y*_ScanWidth+x]= (isAccessible? 0: (TBufferEntry)~0u); } } } void CProximityMapBuffer::calculateZones(TZones& zones) { // clear out the result buffer before starting work zones.clear(); // setup a container to hold the accessible points within this buffer typedef std::set TAccessiblePoints; TAccessiblePoints accessiblePoints; // start by building the set of all accessible points for (uint32 i=0;i<_Buffer.size();++i) { if (_Buffer[i]==0) accessiblePoints.insert(i); } // while there are still points remaining in the set we must have another zone to process while (!accessiblePoints.empty()) { // append a new zone to the zones vector and get a refference to it zones.push_back( CProximityZone(_ScanWidth,_ScanHeight,_XOffset,_YOffset) ); CProximityZone& theZone= zones.back(); // setup a todo list representing points that are part of the surface that we are dealing with // that haven't yet been treated to check for neighbours, etc std::vector todo; // get hold of the first point in the accessilbe points set and push it onto the todo list todo.push_back(*accessiblePoints.begin()); accessiblePoints.erase(todo.back()); // while we have more points to deal with ... while (!todo.empty()) { // pop the next point off the todo list uint32 thePoint= todo.back(); todo.pop_back(); // add the point to the zone theZone.add(thePoint); // a little macro for the code to perform for each movement test... #define TEST_MOVE(xoffs,yoffs)\ {\ TAccessiblePoints::iterator it= accessiblePoints.find(thePoint+xoffs+_ScanWidth*yoffs);\ if (it!=accessiblePoints.end())\ {\ todo.push_back(*it);\ accessiblePoints.erase(it);\ }\ } // N, S, W, E moves TEST_MOVE( 0, 1); TEST_MOVE( 0,-1); TEST_MOVE( 1, 0); TEST_MOVE(-1, 0); // NW, NE, WS, SE moves TEST_MOVE( 1, 1); TEST_MOVE(-1, 1); TEST_MOVE( 1,-1); TEST_MOVE(-1,-1); #undef TEST_MOVE } } nlinfo("Found %u zones",zones.size()); } void CProximityMapBuffer::_prepareBufferForZoneProximityMap(const CProximityZone& zone,TBuffer& zoneBuffer,TOffsetsVector& accessiblePoints) { // the length of runs that we consider too short to deal with... const uint32 shortRunLength=5; // redimention and initialise the zone buffer uint32 zoneWidth= zone.getZoneWidth(); uint32 zoneHeight= zone.getZoneHeight(); zoneBuffer.clear(); zoneBuffer.resize(zoneWidth*zoneHeight,(TBufferEntry)~0u); // setup the buffer's accessible points and prime vects[0] with the set of accessible points in the zone buffer for (uint32 i=0;istartOffset && zoneBuffer[endOffset]!=0; endOffset-= zoneWidth) {} for (uint32 offset=startOffset, marker=startOffset;offset<=endOffset;offset+=zoneWidth) { // see if this is an accessible position if (zoneBuffer[offset]==0) { // look to see whether this position follows a short run of inaccessible positions sint32 inaccessibleRunLength= (offset-marker)/zoneWidth-1; if (inaccessibleRunLength>0 && inaccessibleRunLength<=shortRunLength) { // flag all of the points in this run as belonging to a short run in y for (uint32 j=marker+zoneWidth;jstartOffset && zoneBuffer[endOffset]!=0; --endOffset) {} for (uint32 offset=startOffset, marker=startOffset;offset<=endOffset;++offset) { // see if this is an accessible position if (zoneBuffer[offset]==0) { // look to see whether this position follows a short run of inaccessible positions sint32 inaccessibleRunLength= offset-marker-1; if (inaccessibleRunLength>0 && inaccessibleRunLength<=shortRunLength) { // flag all of the points in this run as belonging to a short run in y for (uint32 j=marker+1;j(newDist))\ {\ zoneBuffer[newVal]=(newDist);\ vects[(newDist)&15].push_back(newVal);\ ++entriesToTreat;\ }\ }\ } // N, S, W, E moves TEST_MOVE( 0, 1,dist+5); TEST_MOVE( 0,-1,dist+5); TEST_MOVE( 1, 0,dist+5); TEST_MOVE(-1, 0,dist+5); // NW, NE, WS, SE moves TEST_MOVE( 1, 1,dist+7); TEST_MOVE(-1, 1,dist+7); TEST_MOVE( 1,-1,dist+7); TEST_MOVE(-1,-1,dist+7); // NNW, NNE, SSW, SSE moves TEST_MOVE( 1, 2,dist+11); TEST_MOVE(-1, 2,dist+11); TEST_MOVE( 1,-2,dist+11); TEST_MOVE(-1,-2,dist+11); // WNW, WSW, ENE, ESE moves TEST_MOVE( 2, 1,dist+11); TEST_MOVE(-2, 1,dist+11); TEST_MOVE( 2,-1,dist+11); TEST_MOVE(-2,-1,dist+11); #undef TEST_MOVE } // clear out the vector entriesToTreat-= (uint32)vect.size(); vect.clear(); } } const TBuffer& CProximityMapBuffer::getBuffer() const { return _Buffer; } uint32 CProximityMapBuffer::getScanHeight() const { return _ScanHeight; } uint32 CProximityMapBuffer::getScanWidth() const { return _ScanWidth; } //------------------------------------------------------------------------------------------------- // NLMISC_COMMAND pacsBuildProximityMap //------------------------------------------------------------------------------------------------- NLMISC_COMMAND(pacsBuildProximityMap,"build a set of proximity maps from a cwmap2 file","") { // deal with command arguments nlinfo("Building proximity map..."); if(args.size()<1) return false; const std::string& name= args[0]; // load the collision map file and generate our buffer from it CProximityMapBuffer buffer; string ext = CFile::getExtension(name); if (ext == "") ext = "cwmap2"; string fileName= OutputPath+CFile::getFilenameWithoutExtension(name)+"."+ext; nlinfo("Building proximity map: Loading cwmap2 file: %s",fileName.c_str()); buffer.load(fileName); // generate a tga file from the buffer contents nlinfo("Building proximity map: Writing accessibilty file: %s",(OutputPath+name+"_accessible.tga").c_str()); writeProximityBufferToTgaFile(OutputPath+name+"_accessible.tga",buffer.getBuffer(),buffer.getScanWidth(),buffer.getScanHeight()); // divide space up into non-connecting zones nlinfo("Building proximity map: Dividing accessible space into zones"); CProximityMapBuffer::TZones zones; buffer.calculateZones(zones); // setup a string to contain the proximity zone list (in csv format) NLMISC::CSString proximityZoneList="idx,x_offset,y_offset,width,height,xmin,ymin,xmax,ymax\n"; // generate proximity info for inaccessible points in each zone for (uint32 i=0;i