// 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 "stdpch.h" #include "../user_entity.h" #include "../water_map.h" #include "../interface_v3/interface_manager.h" // #include "tool.h" #include "editor.h" #include "island_collision.h" #include "verbose_clock.h" #include "../continent_manager.h" #include "../global.h" // #include "nel/misc/file.h" #include "nel/misc/polygon.h" #include "nel/misc/progress_callback.h" // #include "nel/3d/texture_file.h" extern CContinentManager ContinentMngr; using namespace NL3D; using namespace NLMISC; namespace R2 { const char *CIslandCollision::_AccessibilityTextureId = "*accessibility_texture*"; const uint CIslandCollision::MapSizeDivisor = 4; // gives accessible places for content creation as a texture class CAccessibilityTexture : public NL3D::ITexture { public: NLMISC_DECLARE_CLASS(CAccessibilityTexture); // CAccessibilityTexture(); ~CAccessibilityTexture(); CAccessibilityTexture(CIslandCollision *islandCol); // virtual void doGenerate(bool /* async */ = false) { const NLMISC::CArray2D &hm = _IslandCol->getHeightMap(); if (hm.empty()) { resize(1, 1); } else { uint width = hm.getWidth() / CIslandCollision::MapSizeDivisor; uint height = hm.getHeight() / CIslandCollision::MapSizeDivisor; width = std::max(1u, width); height = std::max(1u, height); resize(NLMISC::raiseToNextPowerOf2(width), NLMISC::raiseToNextPowerOf2(height)); CRGBA *dest = (CRGBA *) &getPixels(0)[0]; for (uint y = 0; y < height; ++y) { CRGBA *curr = dest; for (uint x = 0; x < width; ++x) { uint8 level = 0; //bool water = false; for (uint sy = 0; sy < CIslandCollision::MapSizeDivisor; ++sy) { for (uint sx = 0; sx < CIslandCollision::MapSizeDivisor; ++sx) { uint srcX = (CIslandCollision::MapSizeDivisor * x) + sx; uint srcY = (CIslandCollision::MapSizeDivisor * y) + sy; if (srcX < hm.getWidth() && srcY < hm.getHeight()) { if (hm(srcX, srcY) >= 0x7ffe) { //if (hm(srcX, srcY) == 0x7ffe) water = true; level = 255; } } } } //*curr++ = CRGBA(water ? 0 : 255, water ? 0 : 255, 255, level); *curr++ = CRGBA(255, 255, 255, level); } dest += getWidth(); } } } private: CIslandCollision *_IslandCol; }; // ********************************************************************************************************* CAccessibilityTexture::CAccessibilityTexture() : _IslandCol(NULL) { nlassert(0); } // ********************************************************************************************************* CAccessibilityTexture::CAccessibilityTexture(CIslandCollision *islandCol) : _IslandCol(islandCol) { // nlwarning new accessiblity texture created } // ********************************************************************************************************* CAccessibilityTexture::~CAccessibilityTexture() { nlwarning("Accessibility texture removed"); } // ********************************************************************************************************* CIslandCollision::CIslandCollision() { _LastPackedIslandTestPos.set(0.f, 0.f); _PackedIslandLoadingFailure = false; _EntryPointsLoadingFailure = false; _WorldToAccessibilityTexMat.scale(0.f); _AccessibilityTexture = NULL; } // ********************************************************************************************************* void CIslandCollision::releaseAccessibilityTexture() { //H_AUTO(R2_CIslandCollision_releaseAccessibilityTexture) delete _AccessibilityTexture; _AccessibilityTexture = NULL; CViewRenderer &vr = getEditor().getUI().getViewRenderer(); vr.setExternalTexture(_AccessibilityTextureId, NULL, 0, 0, 0, 0); } // ********************************************************************************************************* void CIslandCollision::release() { //H_AUTO(R2_CIslandCollision_release) _PackedIsland = NULL; _LastPackedIslandTestPos.set(0.f, 0.f); _PackedIslandLoadingFailure = false; } // ********************************************************************************************************* CIslandCollision::~CIslandCollision() { release(); } // ********************************************************************************************************* CRefCountedPackedWorld *CIslandCollision::getPackedIsland() { //H_AUTO(R2_CIslandCollision_getPackedIsland) if (_PackedIsland) return _PackedIsland; if (_PackedIslandLoadingFailure) return NULL; // get the island under the player updateCurrPackedIsland(); return _PackedIsland; } // TMP TMP inline sint clamped(sint value, sint size) { clamp(value, 0, size - 1); return value; } // ********************************************************************************************************* R2::CScenarioEntryPoints::CCompleteIsland *CIslandCollision::getCurrIslandDesc() { //H_AUTO(R2_CIslandCollision_getCurrIslandDesc) if (!getPackedIsland()) return NULL; // TMP TMP static volatile bool dumpPACSCorrepondance = false; if (dumpPACSCorrepondance) { CBitmap out; CBitmap out2; sint width = _IslandDesc.XMax - _IslandDesc.XMin; sint height = _IslandDesc.YMax - _IslandDesc.YMin; const uint zoom = 4; out.resize(width * zoom, height * zoom); out2.resize(width * zoom, height * zoom); CRGBA *dest = (CRGBA *) &out.getPixels(0)[0]; CRGBA *dest2 = (CRGBA *) &out2.getPixels(0)[0]; NLPACS::UGlobalPosition pacsPos; for (uint y = 0; y < height * zoom; ++y) { for (uint x = 0; x < width * zoom; ++x) { sint ix = (sint) (x / zoom); sint iy = (sint) (y / zoom); // get value from height map dest->R = _HeightMap(ix, iy) >= 0x7ffe ? 0 : 255; // get value from pacs CTool::TRayIntersectionType interType = CTool::getPacsType(CVector((float) x / zoom + _IslandDesc.XMin, (float) y / zoom + _IslandDesc.YMin, 0.f), 100000.f, pacsPos); dest->G = interType == CTool::ValidPacsPos ? 255 : 0; dest->B = 0; dest->A = 255; *dest2 = *dest; bool accessible = _HeightMap(clamped((sint) ((x + 0.5f) / zoom), width), iy) < 0x7ffe && _HeightMap(clamped((sint) ((x - 0.5f) / zoom), width), iy) < 0x7ffe && _HeightMap(ix, clamped((sint) ((y + 0.5f) / zoom), height)) < 0x7ffe && _HeightMap(ix, clamped((sint) ((y - 0.5f) / zoom), height)) < 0x7ffe; dest2->R = accessible ? 255 : 0; ++ dest; ++ dest2; } } try { { COFile f("pacs_test.tga"); out.writeTGA(f, 0, true); } { COFile f("pacs_test2.tga"); out2.writeTGA(f, 0, true); } } catch(...) { } } return &_IslandDesc; } // ********************************************************************************************************* const NLMISC::CArray2D &CIslandCollision::getHeightMap() { //H_AUTO(R2_CIslandCollision_getHeightMap) // force an island reloadif needed getPackedIsland(); return _HeightMap; } // ********************************************************************************************************* bool CIslandCollision::findEmptyPlace(const CVector2f &startPos, NLMISC::CVector &dest) { //H_AUTO(R2_CIslandCollision_findEmptyPlace) //static volatile bool dumpSearch = false; //CBitmap dump; getPackedIsland(); // force a rebuild of packed island if (_EntryPointsLoadingFailure) return false; enum TDir { Top = 0, Right, Bottom, Left }; sint32 currLength = 1; sint32 leftToGo = currLength; CVector2f currPos = startPos; TDir dir = Top; /*if (dumpSearch) { dump.resize(_HeightMap.getWidth(), _HeightMap.getHeight()); CRGBA *dest = (CRGBA *) &dump.getPixels()[0]; for (uint y = 0; y < _HeightMap.getHeight(); ++y) { for (uint x = 0; x < _HeightMap.getWidth(); ++x) { if (_HeightMap(x, y) <= 0x7ffe) *dest++ = CRGBA::Magenta; else (dest++)->set((uint8) (127 + _HeightMap(x, y)), (uint8) (127 + _HeightMap(x, y)), (uint8) (127 + _HeightMap(x, y)), 255); } } }*/ // if start pos is not in the current island, use the player pos instead if (!(currPos.x >= _IslandDesc.XMin && currPos.y >= _IslandDesc.YMin && currPos.x <= _IslandDesc.XMax && currPos.y <= _IslandDesc.YMax)) { currPos = CVector2f((float) UserEntity->pos().x, (float) UserEntity->pos().y); } // TMP TMP, avoid to reuse the same place twice /*static NLMISC::CArray2D used; static volatile bool doTest = false; if (used.getWidth() != _HeightMap.getWidth() || used.getHeight() != _HeightMap.getHeight()) { used.init(_HeightMap.getWidth(), _HeightMap.getHeight(), 0); }*/ for (;;) { /*if (dumpSearch) { CRGBA *dest = (CRGBA *) &dump.getPixels()[0]; dest += x + y * dump.getWidth(); *dest = CRGBA::Green; }*/ switch(dir) { case Top: currPos.y += 1.f; -- leftToGo; if (leftToGo == 0) { dir = Right; leftToGo = currLength; } break; case Right: currPos.x += 1.f; -- leftToGo; if (leftToGo == 0) { dir = Bottom; ++ currLength; leftToGo = currLength; } break; case Bottom: currPos.y -= 1.f; -- leftToGo; if (leftToGo == 0) { dir = Left; leftToGo = currLength; } break; case Left: currPos.x -= 1.f; -- leftToGo; if (leftToGo == 0) { dir = Top; ++ currLength; leftToGo = currLength; } break; } // if out of map then abort if (!(currPos.x >= _IslandDesc.XMin && currPos.y >= _IslandDesc.YMin && currPos.x <= _IslandDesc.XMax && currPos.y <= _IslandDesc.YMax)) break; if (isValidPos(currPos)) { //if (!used(x, y)) // TMP TMP { CTool::TRayIntersectionType interType = CTool::computeWorldMapIntersection(currPos.x, currPos.y, dest); if (interType == CTool::ValidPacsPos) { /*if (doTest) { used(x, y) = 1; }*/ /*if (dumpSearch) { COFile f("search.tga"); dump.writeTGA(f); }*/ return true; } } } } /*if (dumpSearch) { COFile f("search.tga"); dump.writeTGA(f); }*/ return false; } // ********************************************************************************************************* void CIslandCollision::updateCurrPackedIsland() { //H_AUTO(R2_CIslandCollision_updateCurrPackedIsland) CVectorD playerPos = UserEntity->pos(); CVector2f newPos((float) playerPos.x, (float) playerPos.y); if (fabsf(newPos.x - _LastPackedIslandTestPos.x) >= 80.f || fabsf(newPos.y - _LastPackedIslandTestPos.y) >= 80.f || (!_PackedIsland && !_PackedIslandLoadingFailure) ) { _LastPackedIslandTestPos = newPos; CScenarioEntryPoints &sep = CScenarioEntryPoints::getInstance(); loadEntryPoints(); CVectorD playerPos = UserEntity->pos(); CScenarioEntryPoints::CCompleteIsland *island = sep.getCompleteIslandFromCoords(CVector2f((float) playerPos.x, (float) playerPos.y)); if (!island) { _PackedIslandLoadingFailure = true; _PackedIsland = NULL; return; } if (_PackedIsland && _PackedIslandName == island->Island) return; // same than before, no-op reloadPackedIsland(*island); // first island or entered a new island -> reload } } // ********************************************************************************************************* CPackedWorld *CIslandCollision::reloadPackedIsland(const CScenarioEntryPoints::CCompleteIsland &islandDesc) { //H_AUTO(R2_CIslandCollision_reloadPackedIsland) _PackedIsland = NULL; _PackedIslandLoadingFailure = false; _WorldToAccessibilityTexMat.scale(0.f); _HeightMap.clear(); { CVerboseClock vbc("Reload packed island"); try { CIFile f(CPath::lookup(islandDesc.Island + ".packed_island")); CRefCountedPackedWorld::TSmartPtr newIsland(new CRefCountedPackedWorld); f.serial(*newIsland); _PackedIsland = newIsland; _PackedIslandName = islandDesc.Island; _IslandDesc = islandDesc; } catch (const Exception &) { _PackedIslandLoadingFailure = true; } if (!_PackedIslandLoadingFailure) { // additionnaly, try to load the heightmap try { CIFile f(CPath::lookup(islandDesc.Island + ".island_hm")); f.serialCheck((uint32) 'MHSI'); f.serial(_HeightMap); } catch(const Exception &e) { nlwarning(e.what()); _HeightMap.clear(); } } if (!_HeightMap.empty()) { { CVerboseClock vbc("Force water surfaces loading"); NLMISC::IProgressCallback dummyProgress; // force to load all water surfaces & add them ContinentMngr.forceUpdateStreamable(UserEntity->pos(), dummyProgress); } mergeWaterSurfaces(); } releaseAccessibilityTexture(); _AccessibilityTexture = new NL3D::CTextureUser(new CAccessibilityTexture(this)); _AccessibilityTexture->setFilterMode(UTexture::Nearest, UTexture::NearestMipMapOff); _AccessibilityTexture->setWrapS(UTexture::Clamp); _AccessibilityTexture->setWrapT(UTexture::Clamp); // make this texture accessible in the ui view renderer from a simple filename // this allow to display it in the world map uint width = std::max(_HeightMap.getWidth() / MapSizeDivisor, 1u); uint height = std::max(_HeightMap.getHeight() / MapSizeDivisor, 1u); uint realWidth = NLMISC::raiseToNextPowerOf2(width); uint realHeight = NLMISC::raiseToNextPowerOf2(height); CViewRenderer &vr = getEditor().getUI().getViewRenderer(); // create / update texture vr.setExternalTexture(_AccessibilityTextureId, _AccessibilityTexture, realWidth, realHeight, width, height); if (vr.getTextureIdFromName(_AccessibilityTextureId) == -1) { vr.createTexture(_AccessibilityTextureId, 0, 0, width, height); nlwarning("Creating accessibility texture with id %d ", (int) vr.getTextureIdFromName(_AccessibilityTextureId)); } else { vr.updateTexturePos(_AccessibilityTextureId, 0, 0, width, height); } // scale resumes to dividing by real coords, because heightmap resolution is hardcoded to 1 meter CVector scale (1.f / std::max(_HeightMap.getWidth(), 1u), 1.f / std::max(_HeightMap.getHeight(), 1u), 0.f); _WorldToAccessibilityTexMat.setScale(scale); _WorldToAccessibilityTexMat.setPos(CVector((float) - islandDesc.XMin * scale.x, (float) - islandDesc.YMin * scale.y, 0.f)); // cropped version of matrix scale.x *= (float) width / realWidth; scale.y *= (float) height / realHeight; _WorldToAccessibilityTexMatCropped.setScale(scale); _WorldToAccessibilityTexMatCropped.setPos(CVector((float) - islandDesc.XMin * scale.x, (float) - islandDesc.YMin * scale.y, 0.f)); } return _PackedIsland; } // ********************************************************************************************************* void CIslandCollision::mergeWaterSurfaces() { //H_AUTO(R2_CIslandCollision_mergeWaterSurfaces) CVerboseClock vbc("Merge water surfaces"); nlassert(_PackedIsland); std::vector tris; std::vector clippedTris; NLMISC::CPolygon2D worldPoly; NLMISC::CPolygon clippedPoly; NLMISC::CPolygon clippedPoly2D; for (std::list::iterator it = _WaterSurfs.begin(); it != _WaterSurfs.end(); ++it) { CVerboseClock vbc("Merge single water surface"); // Select a list of candidate triangles from the packed landscape // These must be inside the water surface 2D projected (convex) shape worldPoly = it->Shape; for (uint k = 0; k < worldPoly.Vertices.size(); ++k) { worldPoly.Vertices[k] = it->WorldMatrix * worldPoly.Vertices[k]; } _PackedIsland->select(worldPoly, tris); clippedTris.clear(); NLMISC::CPlane waterPlane; waterPlane.make(CVector::K, it->WorldMatrix.getPos()); // now clip by water surface to get tris that are below the water for (uint k = 0; k < tris.size(); ++k) { clippedPoly.Vertices.resize(3); clippedPoly.Vertices[0] = tris[k].V0; clippedPoly.Vertices[1] = tris[k].V1; clippedPoly.Vertices[2] = tris[k].V2; clippedPoly.clip(&waterPlane, 1); { // this part is under water, thus inaccessible // add contribution to heightmap clippedPoly2D.Vertices.resize(clippedPoly.Vertices.size()); std::copy(clippedPoly.Vertices.begin(), clippedPoly.Vertices.end(), clippedPoly2D.Vertices.begin()); addInaccessibleRegion(clippedPoly2D); } } } } // ********************************************************************************************************* void CIslandCollision::addInaccessibleRegion(const NLMISC::CPolygon2D &poly) { //H_AUTO(R2_CIslandCollision_addInaccessibleRegion) static NLMISC::CPolygon2D localPoly; localPoly = poly; for (uint k = 0; k < localPoly.Vertices.size(); ++k) { localPoly.Vertices[k].x -= (float) _IslandDesc.XMin; localPoly.Vertices[k].y -= (float) _IslandDesc.YMin; } static NLMISC::CPolygon2D::TRasterVect borders; borders.clear(); sint minY; localPoly.computeOuterBorders(borders, minY); for (sint y = minY; y < (sint) (minY + borders.size()); ++y) { if (y < 0 || y >= (sint) _HeightMap.getHeight()) continue; for (sint x = borders[y - minY].first; x <= borders[y - minY].second; ++x) { if (x < 0 || x >= (sint) _HeightMap.getWidth()) continue; { _HeightMap(x, y) = 0x7ffe; } } } } // ********************************************************************************************************* void CIslandCollision::loadEntryPoints() { //H_AUTO(R2_CIslandCollision_loadEntryPoints) if (_EntryPointsLoadingFailure) return; CScenarioEntryPoints &sep = CScenarioEntryPoints::getInstance(); if (sep.getCompleteIslands().empty()) { // load the entry points try { sep.loadCompleteIslands(); } catch (const NLMISC::EStream &e) { _EntryPointsLoadingFailure = true; nlwarning(e.what()); } } } // ********************************************************************************************************* bool CIslandCollision::isValidPoly(const NLMISC::CPolygon2D &poly) { //H_AUTO(R2_CIslandCollision_isValidPoly) if (poly.Vertices.empty()) return true; // test against heightmap R2::CScenarioEntryPoints::CCompleteIsland *islandDesc = getCurrIslandDesc(); if (!islandDesc) return true; // apply offset to polygon to avoid overflow (coords of rasterized poly can be 32000 at most) static NLMISC::CPolygon2D offsetedPoly; offsetedPoly.Vertices.resize(poly.Vertices.size()); for(uint k = 0; k < poly.Vertices.size(); ++k) { const CVector2f &src = poly.Vertices[k]; offsetedPoly.Vertices[k].set(src.x - (float) islandDesc->XMin, src.y - (float) islandDesc->YMin); static volatile bool doAssert = false; if (doAssert) { nlassert(offsetedPoly.Vertices[k].x >= -32000.f); // coordinate too big ! nlassert(offsetedPoly.Vertices[k].x < 32000.f); // coordinate too big ! nlassert(offsetedPoly.Vertices[k].y >= -32000.f); // coordinate too big ! nlassert(offsetedPoly.Vertices[k].y < 32000.f); // coordinate too big ! } // if (offsetedPoly.Vertices[k].x < -32000.f) return false; if (offsetedPoly.Vertices[k].x >= 32000.f) return false; if (offsetedPoly.Vertices[k].y < -32000.f) return false; if (offsetedPoly.Vertices[k].y >= 32000.f) return false; } static NLMISC::CPolygon2D::TRasterVect borders; sint minY; offsetedPoly.computeOuterBorders(borders, minY); const CArray2D &hm = getHeightMap(); // TMP TMP static volatile bool dumpTestBitmap = false; if (dumpTestBitmap) { CBitmap bm; bm.resize(hm.getWidth(), hm.getHeight()); CRGBA *dest = (CRGBA *) &bm.getPixels(0)[0]; for (sint y = 0; y < (sint) hm.getHeight(); ++y) { for (sint x = 0; x < (sint) hm.getWidth(); ++x) { if (hm(x, y) >= 0x7ffe) { *dest = CRGBA::Red; } else { dest->set(0, hm(x, y) & 255, 0, 255); } ++ dest; } } // for (sint y = 0; y < (sint) borders.size(); ++y) { if (y + minY < 0 || y + minY >= (sint) bm.getHeight()) continue; sint xmin = borders[y].first; sint xmax = borders[y].second; if (xmin > xmax) continue; if (xmax < 0 || xmin >= (sint) hm.getWidth()) continue; clamp(xmin, 0, (sint) (hm.getWidth() - 1)); clamp(xmax, 0, (sint) (hm.getWidth() - 1)); for (sint x = xmin; x <= xmax; ++x) { CRGBA *dest = (CRGBA *) &bm.getPixels(0)[0]; dest += x + ((y + minY)) * hm.getWidth(); dest->B = 255; } } try { COFile of("test_poly.tga"); bm.writeTGA(of); } catch(...) { } } for (sint y = 0; y < (sint) borders.size(); ++y) { if (y + minY < 0 || y + minY >= (sint) hm.getHeight()) return false; sint xmin = borders[y].first; sint xmax = borders[y].second; if (xmin > xmax) continue; if (xmax < 0 || xmin >= (sint) hm.getWidth()) return false; clamp(xmin, 0, (sint) (hm.getWidth() - 1)); clamp(xmax, 0, (sint) (hm.getWidth() - 1)); for (sint x = xmin; x <= xmax; ++x) { if (hm(x, y + minY) >= 0x7ffe) return false; } } return true; } // ********************************************************************************************************* bool CIslandCollision::isValidSegment(const NLMISC::CVector2f start, const NLMISC::CVector2f end) { //H_AUTO(R2_CIslandCollision_isValidSegment) static NLMISC::CPolygon2D extrudedSegment; extrudedSegment.Vertices.resize(4); CVector2f right, up; if (end.x != start.x || end.y != start.y) { right = 0.5f * (end - start).normed(); up.set(- right.y, right.x); } else { right.set(0.5f, 0.f); up.set(0.f, 0.5f); } extrudedSegment.Vertices[0] = start - right + up; extrudedSegment.Vertices[1] = end + right + up; extrudedSegment.Vertices[2] = end + right - up; extrudedSegment.Vertices[3] = start - right - up; return isValidPoly(extrudedSegment); } // ********************************************************************************************************* void CIslandCollision::waterSurfaceAdded(const NLMISC::CPolygon2D &shape, const NLMISC::CMatrix &worldMatrix) { //H_AUTO(R2_CIslandCollision_waterSurfaceAdded) for (std::list::iterator it = _WaterSurfs.begin(); it != _WaterSurfs.end(); ++it) { if (shape.Vertices.size() == it->Shape.Vertices.size()) { if (std::equal(shape.Vertices.begin(), shape.Vertices.end(), it->Shape.Vertices.begin())) { float oldMat[16]; float newMat[16]; it->WorldMatrix.get(oldMat); worldMatrix.get(newMat); if (std::equal(oldMat, oldMat + 16, newMat)) { nlwarning("Duplicate water surface, not inserting second"); return; // already inserted } } } } CWaterSurf ws; ws.Shape = shape; ws.WorldMatrix = worldMatrix; _WaterSurfs.push_back(ws); } // ********************************************************************************************************* bool CIslandCollision::isValidPos(const NLMISC::CVector2f &pos) { //H_AUTO(R2_CIslandCollision_isValidPos) if (!isValidSegment(pos - CVector2f(0.5f, 0.f), pos + CVector2f(0.5f, 0.f))) return false; CVector dummy; return CTool::computeWorldMapIntersection(pos.x, pos.y, dummy) == CTool::ValidPacsPos; } } // R2