// 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 "water_map.h" #include "continent_manager.h" #include "user_entity.h" #include "misc.h" #include "global.h" #include "client_cfg.h" // #include "r2/editor.h" #include "r2/island_collision.h" // #include "nel/3d/u_scene.h" #include "nel/3d/u_driver.h" // #include "nel/misc/path.h" #include "nel/misc/file.h" extern NL3D::UScene *Scene; extern NL3D::UDriver *Driver; extern CContinentManager ContinentMngr; bool DisplayWaterMap = false; using namespace NLMISC; H_AUTO_DECL(RZ_WaterMap) //********************************************************************************************************* CWaterMap::CWaterMap() { H_AUTO_USE(RZ_WaterMap) // Construct _CellSize = 0.f; _Width = 0; _Height = 0; } //********************************************************************************************************* void CWaterMap::init(const NLMISC::CVector2f &minCorner, const NLMISC::CVector2f &maxCorner, float cellSize /*=20.f*/) { H_AUTO_USE(RZ_WaterMap) release(); if (cellSize <= 0.f) { nlwarning("Invalid cell size"); return; } _MinCorner = minCorner; _MaxCorner = maxCorner; if (_MinCorner.y > _MaxCorner.y) std::swap(_MinCorner.y, _MaxCorner.y); if (_MinCorner.y > _MaxCorner.y) std::swap(_MinCorner.y, _MaxCorner.y); _CellSize = cellSize; _Width = (uint) ceilf((_MaxCorner.x - _MinCorner.x) / _CellSize); _Height = (uint) ceilf((_MaxCorner.y - _MinCorner.y) / _CellSize); _Grid.resize(_Width * _Height, 0); // init index 0 as an empty list of water surface _WaterInfoVect.push_back(CWaterInfo()); _WaterInfoVect.back().Height = 0.f; _WaterInfoVect.back().SplashEnabled = false; _WaterInfoIndexVectVect.push_back(TWaterInfoIndexVect(1, 0)); Scene->setWaterCallback(this); } //********************************************************************************************************* void CWaterMap::release() { H_AUTO_USE(RZ_WaterMap) NLMISC::contReset(_Grid); NLMISC::contReset(_WaterInfoVect); NLMISC::contReset(_WaterInfoIndexVectVect); _CellSize = 0.f; _Width = 0; _Height = 0; Scene->setWaterCallback(NULL); } //********************************************************************************************************* bool CWaterMap::getWaterHeight(const NLMISC::CVector2f &pos, float &height, bool &splashEnabled) { H_AUTO_USE(RZ_WaterMap) if (_Grid.empty()) return false; float x = (pos.x - _MinCorner.x) / _CellSize; float y = (pos.y - _MinCorner.y) / _CellSize; sint ix = (sint) x; sint iy = (sint) y; if (ix < 0 || ix >= _Width) return false; if (iy < 0 || iy >= _Height) return false; const TWaterInfoIndexVect &wiiv = _WaterInfoIndexVectVect[_Grid[ix + iy * _Width]]; if (wiiv.empty()) return false; // search if there's an intersection with one of the water surfaces for(uint k = 0; k < wiiv.size(); ++k) { const CWaterInfo &wi = _WaterInfoVect[wiiv[k]]; if (wi.Shape.Vertices.empty()) continue; // test intersection with each surface, and pick the first match if (wi.Shape.contains(NLMISC::CVector2f(x, y))) { height = wi.Height; splashEnabled = wi.SplashEnabled; return true; } } return false; } //********************************************************************************************************* void CWaterMap::waterSurfaceAdded(const NLMISC::CPolygon2D &shape, const NLMISC::CMatrix &worldMatrix, bool splashEnabled, bool usesSceneWaterEnvMap) { if (ClientCfg.R2EDEnabled) { R2::getEditor().getIslandCollision().waterSurfaceAdded(shape, worldMatrix); } H_AUTO_USE(RZ_WaterMap) if (usesSceneWaterEnvMap) ++WaterEnvMapRefCount; if (_Grid.empty()) return; if (_CellSize == 0.f) return; float height = worldMatrix.getPos().z; // transform the water shape in grid coordinates CWaterInfo wi; uint numVerts = shape.Vertices.size(); wi.Shape.Vertices.resize(numVerts); wi.SplashEnabled = splashEnabled; NLMISC::CMatrix toGridMatrix; toGridMatrix.scale(NLMISC::CVector(1.f / _CellSize, 1.f / _CellSize, 1.f)); toGridMatrix.translate(- _MinCorner); toGridMatrix = toGridMatrix * worldMatrix; for(uint k = 0; k < numVerts; ++k) { wi.Shape.Vertices[k] = toGridMatrix * shape.Vertices[k]; } // see if water surface already added for(uint k = 0; k < _WaterInfoVect.size(); ++k) { if (_WaterInfoVect[k].Height == height && _WaterInfoVect[k].Shape.Vertices.size() == shape.Vertices.size()) { if (std::equal(wi.Shape.Vertices.begin(), wi.Shape.Vertices.end(), _WaterInfoVect[k].Shape.Vertices.begin())) { // already inserted -> do nothing return; } } } // insert new CWaterInfo in the list wi.Height = height; _WaterInfoVect.push_back(wi); // build rasters NLMISC::CPolygon2D::TRasterVect rasters; sint minY; //wi.Shape.computeBordersLarge(rasters, minY); wi.Shape.computeOuterBorders(rasters, minY); if (!rasters.empty()) { sint numRasters = (sint) rasters.size(); for(sint y = 0; y < numRasters; ++y) { if ((y + minY) < 0 || (y + minY) >= _Height) continue; // outisde of map sint lastX = rasters[y].second + 1; for (sint x = rasters[y].first; x < lastX; ++x) { if (x < 0 || x >= _Width) continue; // outside of map const TWaterInfoIndexVect *currWI = &_WaterInfoIndexVectVect[_Grid[x + (y + minY) * _Width]]; // see if there's already a list of water surfaces that match the list for that grid cell // such a list should contains the surfaces already found for that grid cell + the new one sint goodList = -1; for(uint k = 0; k < _WaterInfoIndexVectVect.size(); ++k) { if (_WaterInfoIndexVectVect[k].size() == currWI->size() + 1) { if (std::equal(_WaterInfoIndexVectVect[k].begin(), _WaterInfoIndexVectVect[k].begin() + currWI->size(), currWI->begin()) && _WaterInfoIndexVectVect[k].back() == _WaterInfoVect.size() - 1) { // this list match what we want goodList = k; break; } } } if (goodList == -1) { // must create a new list _WaterInfoIndexVectVect.push_back(TWaterInfoIndexVect()); // vector has grown up, so currWI pointer becomes invalid -> rebuild it currWI = &_WaterInfoIndexVectVect[_Grid[x + (y + minY) * _Width]]; _WaterInfoIndexVectVect.back().resize(currWI->size() + 1); std::copy(currWI->begin(), currWI->end(), _WaterInfoIndexVectVect.back().begin()); _WaterInfoIndexVectVect.back().back() = _WaterInfoVect.size() - 1; goodList = _WaterInfoIndexVectVect.size() - 1; } _Grid[x + (y + minY) * _Width] = goodList; // reassign new list } } } } //********************************************************************************************************* void CWaterMap::waterSurfaceRemoved(bool usesSceneWaterEnvMap) { H_AUTO_USE(RZ_WaterMap) if (usesSceneWaterEnvMap) --WaterEnvMapRefCount; } static const NLMISC::CRGBA DebugCols[] = { NLMISC::CRGBA(255, 32, 32), NLMISC::CRGBA(32, 255, 32), NLMISC::CRGBA(255, 255, 32), NLMISC::CRGBA(32, 255, 255), NLMISC::CRGBA(255, 32, 255), NLMISC::CRGBA(255, 127, 32), NLMISC::CRGBA(255, 255, 255) }; static const uint NumDebugCols = sizeof(DebugCols) / sizeof(DebugCols[0]); //********************************************************************************************************* void CWaterMap::dump(const std::string &filename) { H_AUTO_USE(RZ_WaterMap) if (_Grid.empty()) { nlwarning("Grid not built"); return; } NLMISC::CBitmap bm; bm.resize(_Width, _Height, NLMISC::CBitmap::RGBA); NLMISC::CRGBA *pix = (NLMISC::CRGBA *) bm.getPixels(0).getPtr(); for(uint x = 0; x < _Width; ++x) { for(uint y = 0; y < _Height; ++y) { if (_Grid[x + y *_Width] == 0) { pix[x + y * _Width] = CRGBA(127, 127, 127); } else { pix[x + y * _Width] = DebugCols[_Grid[x + y *_Width] % NumDebugCols]; } } } // merge with world map if present if (ContinentMngr.cur()) { if (!ContinentMngr.cur()->WorldMap.empty()) { std::string path = CPath::lookup(ContinentMngr.cur()->WorldMap, false); if (!path.empty()) { CIFile stream; if (stream.open(path)) { CBitmap worldMap; if (worldMap.load(stream)) { worldMap.flipV(); worldMap.convertToType(CBitmap::RGBA); worldMap.resample(bm.getWidth(), bm.getHeight()); bm.blend(bm, worldMap, 127, true); } } } } } // drawDisc(bm, ((float) UserEntity->pos().x - _MinCorner.x) / _CellSize, ((float) UserEntity->pos().y - _MinCorner.y) / _CellSize, 2.f, CRGBA::Magenta); // NLMISC::COFile f; if (!f.open(filename)) { nlwarning("Can't open %s for writing", filename.c_str()); return; } bm.writeTGA(f, 24, true); f.close(); } //****************************************************************************************************************** void CWaterMap::render(const NLMISC::CVector2f &camPos, float maxDist /*=100.f*/) { H_AUTO_USE(RZ_WaterMap) if (_Grid.empty()) return; Driver->setViewMatrix(Scene->getCam().getMatrix().inverted()); NL3D::CFrustum fr; Scene->getCam().getFrustum(fr.Left, fr.Right, fr.Bottom, fr.Top, fr.Near, fr.Far); fr.Perspective = true; Driver->setFrustum(fr); Driver->setModelMatrix(NLMISC::CMatrix::Identity); float userZ = UserEntity ? (float) UserEntity->pos().z : 0.f; for (uint x = 0; x < _Width; ++x) { for (uint y = 0; y < _Height; ++y) { uint16 index = _Grid[x + _Width * y]; if (index == 0) continue; const TWaterInfoIndexVect &wii = _WaterInfoIndexVectVect[index]; // see if cell not too far NLMISC::CVector2f pos(x * _CellSize + _MinCorner.x, y * _CellSize + _MinCorner.y); if ((camPos - pos).norm() > maxDist) continue; // too far, don't display // display box for each primitive type NLMISC::CVector cornerMin(pos.x, pos.y, userZ - 5.f); NLMISC::CVector cornerMax(pos.x + _CellSize, pos.y + _CellSize, userZ + 5.f); for(uint l = 0; l < wii.size(); ++l) { // add a bias each time to see when several primitives are overlapped NLMISC::CVector bias = (float) wii[l] * NLMISC::CVector(0.01f, 0.f, 0.1f); drawBox(cornerMin + bias, cornerMax + bias, DebugCols[wii[l] % NumDebugCols]); } } } } #if !FINAL_VERSION //****************************************************************************************************************** // dump water map in a tga file NLMISC_COMMAND(dumpWaterMap, "dump water map", "") { if (args.size() != 1) return false; if (!ContinentMngr.cur()) return false; ContinentMngr.cur()->WaterMap.dump(args[0]); return true; } //****************************************************************************************************************** // display the water map NLMISC_COMMAND(displayWaterMap, "dump water map", "<0 = on / 1 = off>") { if (args.size() != 1) return false; fromString(args[0], DisplayWaterMap); return true; } #endif