// 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 = (uint)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() = (uint16)_WaterInfoVect.size() - 1;
goodList = (sint)_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