// 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 "displayer_visual_activity_sequence.h"
#include "displayer_visual_group.h"
#include "r2_config.h"
//
#include "../interface_v3/ctrl_quad.h"
#include "../interface_v3/interface_manager.h"
#include "../interface_v3/view_renderer.h"
using namespace NLMISC;
namespace R2
{
//*********************************************************************************************************
CDisplayerVisualActivitySequence::CDisplayerVisualActivitySequence()
{
_Touched = true;
_Active = false;
_AddedToWorldMap = false;
_DecalColor = CRGBA(0, 0, 0, 0);
}
//*********************************************************************************************************
CDisplayerVisualActivitySequence::~CDisplayerVisualActivitySequence()
{
clear();
_WPCache.clear();
}
//*********************************************************************************************************
void CDisplayerVisualActivitySequence::clear(bool wantRemoveFromWorldMap)
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_clear)
for(uint k = 0; k < _ObserverHandles.size(); ++k)
{
getEditor().removeInstanceObserver(_ObserverHandles[k]);
}
_ObserverHandles.clear();
_Decals.clear();
_FootSteps.clear();
_WPCache.clear();
if (wantRemoveFromWorldMap)
{
removeFromWorldMap();
}
_TraversedPrimInfos.clear();
}
//*********************************************************************************************************
void CDisplayerVisualActivitySequence::onAttrModified(const std::string &attrName,sint32 /* attrIndex */)
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_onAttrModified)
if (attrName == "Components")
{
touch();
}
}
//*********************************************************************************************************
void CDisplayerVisualActivitySequence::touch()
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_touch)
_Touched = true;
}
//*********************************************************************************************************
CDisplayerVisual *CDisplayerVisualActivitySequence::getParentDV() const
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_getParentDV)
CInstance *currParent = getDisplayedInstance()->getParent();
CDisplayerVisual *prevDV = NULL;
while (currParent)
{
prevDV = currParent->getDisplayerVisual();
if (prevDV) break;
currParent = currParent->getParent();
}
return prevDV;
}
//*********************************************************************************************************
void CDisplayerVisualActivitySequence::update()
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_update)
if (!_Active) return;
if (!_Touched) return;
_Touched = false;
CObjectTable *activities = getProps().toTable("Components");
if (!activities)
{
clear();
return;
}
// get first world object parent to get start position
nlassert(getDisplayedInstance());
CInstance *prevZone = NULL;
CDisplayerVisual *prevDV = getParentDV();
if (!prevDV)
{
clear();
return;
}
//
clear(false);
//
for(uint k = 0; k < activities->getSize(); ++k)
{
// search next zone of activity
CObjectTable *activity = activities->getValue(k)->toTable();
if (!activity) continue;
std::string activityStr = getString(activity, "Activity");
if (activityStr == "Stand Still" || activityStr == "Inactive") continue;
//
CInstance *nextZone = NULL;
std::string zoneId = getString(activity, "ActivityZoneId");
//
if (!zoneId.empty())
{
_ObserverHandles.push_back(getEditor().addInstanceObserver(zoneId, this));
nextZone = getEditor().getInstanceFromId(zoneId);
}
if (!nextZone) break;
CDisplayerVisual *nextDV = nextZone->getDisplayerVisual();
if (!nextDV) break;
_TraversedPrimInfos.push_back(CTraversedPrimInfo());
_TraversedPrimInfos.back().PrimDisplay = nextDV;
_TraversedPrimInfos.back().Visible = nextDV->getActualDisplayMode() != DisplayModeHidden;
CWorldPosCache wpc;
wpc.DV = nextDV;
wpc.WorldPos2f = nextDV->getWorldPos2f();
_WPCache.push_back(wpc);
if (nextDV->getActualDisplayMode() != DisplayModeHidden
&& prevDV->getActualDisplayMode() != DisplayModeHidden)
{
// special case for regions
if (nextZone->isKindOf("Region"))
{
// first case : previous zone is not a region
if (!prevZone || !prevZone->isKindOf("Region"))
{
// search shortest distance bewteen last pos and the region
CVector entryPos;
if (nextDV->evalEnterPoint(prevDV->evalExitPoint(), entryPos))
{
addFootSteps(CLine(prevDV->evalExitPoint(), entryPos));
}
else
{
addWanderSteps(prevDV->evalExitPoint());
}
}
else
{
// region-region footsteps
// just use the couple of vertices for which the distance is the smallest
static std::vector r0;
static std::vector r1;
prevDV->getSonsWorldPos2f(r0);
nextDV->getSonsWorldPos2f(r1);
if (!r0.empty() && !r1.empty())
{
CVector2f p0, p1;
float bestDist = FLT_MAX;
for(uint k = 0; k < r0.size(); ++k)
{
for(uint l = 0; l < r0.size(); ++l)
{
float dist = (r0[k] - r1[l]).norm();
if (dist isKindOf("Region"))
{
// search shortest distance bewteen last pos and the region
CVector entryPos;
if (prevDV->evalEnterPoint(nextDV->evalLinkPoint(), entryPos))
{
addFootSteps(CLine(entryPos, nextDV->evalLinkPoint()));
}
else
{
addWanderSteps(nextDV->evalLinkPoint());
}
}
else
{
// simple footsteps between last & new pos
addFootSteps(CLine(prevDV->evalExitPoint(), nextDV->evalLinkPoint()));
}
}
}
prevDV = nextDV;
prevZone = nextZone;
}
//
CGroupMap *gm = CTool::getWorldMap();
if (!_AddedToWorldMap && gm)
{
gm->addDeco(this);
}
if (_AddedToWorldMap)
{
setWorldMapNumEdges(_FootSteps.size());
nlassert(gm);
onUpdate(*gm);
}
}
//*********************************************************************************************************
void CDisplayerVisualActivitySequence::addFootSteps(const NLMISC::CLine &line)
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_addFootSteps)
CDecal *decal = new CDecal;
decal->setTexture(CV_FootStepDecalTexture.get(), false, true);
CVector2f start2f(line.V0.x, line.V0.y);
CVector2f end2f(line.V1.x, line.V1.y);
float dist = (end2f - start2f).norm();
if (dist != 0.f)
{
decal->setWorldMatrixForArrow(start2f, end2f, CV_FootStepDecalWidth.get());
CMatrix uvMatrix;
uvMatrix.setScale(CVector(CV_FootStepDecalUScale.get() * dist, 1.f, 1.f));
decal->setTextureMatrix(uvMatrix);
}
_Decals.push_back(decal);
_FootSteps.push_back(line);
}
//*********************************************************************************************************
void CDisplayerVisualActivitySequence::addWanderSteps(const CVector &pos)
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_addWanderSteps)
CDecal *decal = new CDecal;
decal->setTexture(CV_WanderDecalTexture.get(), true, true);
decal->setWorldMatrixForSpot(CVector2f(pos.x, pos.y), CV_WanderDecalSize.get());
_Decals.push_back(decal);
}
enum TSequenceState { Hidden = 0, HasFocus, Selected };
static void updateState(TSequenceState &state, CDisplayerVisual *dv)
{
if (dv->getDisplayFlag(CDisplayerVisual::FlagHasFocus)) state = (TSequenceState) std::max((sint) state, (sint) HasFocus);
if (dv->getDisplayFlag(CDisplayerVisual::FlagSelected)) state = (TSequenceState) std::max((sint) state, (sint) Selected);
}
//*********************************************************************************************************
CDisplayerVisual *CDisplayerVisualActivitySequence::getPossibleGroupDV(CDisplayerVisual *entityDV)
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_getPossibleGroupDV)
if (!entityDV) return NULL;
CInstance *parentGroup = entityDV->getDisplayedInstance()->getParent();
while (parentGroup)
{
if (dynamic_cast(parentGroup->getDisplayerVisual()))
{
return parentGroup->getDisplayerVisual();
break;
}
parentGroup = parentGroup->getParent();
}
return entityDV;
}
//*********************************************************************************************************
bool CDisplayerVisualActivitySequence::isVisible(CDisplayerVisual *groupDV, CDisplayerVisual *entityDV)
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_isVisible)
if (!entityDV || !groupDV || !_Active) return false;
if (entityDV->getActualDisplayMode() == DisplayModeHidden)
{
return false;
}
// see if render is on for primitives (don't use a config var because may be dynamically changed)
if (entityDV->getDisplayFlag(CDisplayerVisual::FlagHideActivities))
{
return false; // when selected for auto group, the activity won't be duplicated
}
// If in a group and not the leader of this group, or not selected, don't display
if(entityDV != groupDV)
{
if (groupDV->getSon(0) != entityDV) return false;
}
//if (!getEditor().getConfig()["PrimDisplayEnabled"].toBoolean()) return false;
return true;
}
//*********************************************************************************************************
void CDisplayerVisualActivitySequence::removeFromWorldMap()
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_removeFromWorldMap)
if (_AddedToWorldMap)
{
CGroupMap *gm = CTool::getWorldMap();
nlassert(gm);
gm->removeDeco(this);
_AddedToWorldMap = false;
}
}
//*********************************************************************************************************
void CDisplayerVisualActivitySequence::onPostRender()
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_onPostRender)
CDisplayerVisual *entityDV = getParentDV();
CDisplayerVisual *groupDV = getPossibleGroupDV(entityDV);
if (!isVisible(groupDV, entityDV))
{
removeFromWorldMap();
return;
}
//
TSequenceState state = Hidden;
// if this activity sequence is not the selected one in its parent then it is hidden
updateState(state, groupDV);
// if current selection is a son of the group...
static std::vector groupSons;
groupDV->getSons(groupSons);
for(uint k = 0; k < groupSons.size(); ++k)
{
nlassert(groupSons[k]);
updateState(state, groupSons[k]);
}
// if one of the route is selected or highlighted
CObjectTable *activities = getProps().toTable("Components");
if (!activities)
{
removeFromWorldMap();
return;
}
// get first world object parent to get start position
for(uint k = 0; k < activities->getSize(); ++k)
{
// search next zone of activity
CObjectTable *activity = activities->getValue(k)->toTable();
if (!activity) continue;
std::string zoneId = getString(activity, "ActivityZoneId");
if (zoneId.empty()) continue;
std::string activityStr = getString(activity, "Activity");
if (activityStr == "Stand Still" || activityStr == "Inactive") continue;
CInstance *zone = getEditor().getInstanceFromId(zoneId);
if (!zone) continue;
CDisplayerVisual *dv = zone->getDisplayerVisual();
if (!dv) continue;
updateState(state, dv);
static std::vector vertices;
dv->getSons(vertices);
for (uint l = 0; l < vertices.size(); ++l)
{
nlassert(vertices[l]);
updateState(state, vertices[l]);
}
}
//
if (state != Hidden && entityDV)
{
// see if this sequence is the selected one
sint index = entityDV->getDisplayedInstance()->getSelectedSequence();
CObject *sequences = getProps().getParent(); // see what is my index in the sequences list of my parent
if (sequences)
{
clamp(index, (sint) 0, (sint) sequences->getSize() - 1);
if (index != sequences->findIndex(&getProps()))
{
state = Hidden; // not the current selected sequence
}
}
}
//
_DecalColor = CV_FootStepDecalSelectedColor.get();
CRGBA mapColor = CV_FootStepMapSelectedColor.get();
switch(state)
{
case Hidden:
_DecalColor = CV_FootStepDecalHiddenColor.get();
mapColor = CV_FootStepMapHiddenColor.get();
break;
case HasFocus:
_DecalColor = CV_FootStepDecalFocusedColor.get();
mapColor = CV_FootStepMapFocusedColor.get();
break;
}
for(uint k = 0; k < _WPCache.size(); ++k)
{
if (_WPCache[k].DV)
{
if (_WPCache[k].DV->getWorldPos2f() != _WPCache[k].WorldPos2f)
{
touch();
break;
}
}
}
update();
if (_AddedToWorldMap)
{
setWorldMapColor(mapColor);
}
}
//*********************************************************************************************************
void CDisplayerVisualActivitySequence::onPreRender()
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_onPreRender)
CDisplayerVisual *entityDV = getParentDV();
CDisplayerVisual *groupDV = getPossibleGroupDV(entityDV);
if (!isVisible(groupDV, entityDV))
{
removeFromWorldMap();
return;
}
// if visibility of one of the group has been modified then update our display
bool recompute = false;
for(uint k = 0; k < _TraversedPrimInfos.size(); ++k)
{
if (!_TraversedPrimInfos[k].PrimDisplay)
{
recompute = true;
break;
}
bool visible = (_TraversedPrimInfos[k].PrimDisplay->getActualDisplayMode() != DisplayModeHidden);
if (visible != _TraversedPrimInfos[k].Visible)
{
recompute = true;
break;
}
}
if (recompute)
{
touch();
update();
}
//
for(uint k = 0; k < _Decals.size(); ++k)
{
_Decals[k]->setDiffuse(_DecalColor);
_Decals[k]->addToRenderList();
}
}
//*********************************************************************************************************
void CDisplayerVisualActivitySequence::updateWorldPos()
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_updateWorldPos)
touch(); // world pos of containing parent is modified
}
//*********************************************************************************************************
void CDisplayerVisualActivitySequence::onInstanceCreated(CInstance &/* instance */)
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_onInstanceCreated)
touch();
}
//*********************************************************************************************************
void CDisplayerVisualActivitySequence::onInstanceErased(CInstance &/* instance */)
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_onInstanceErased)
touch();
}
//*********************************************************************************************************
void CDisplayerVisualActivitySequence::onAttrModified(CInstance &/* instance */,const std::string &/* attrName */,sint32 /* attrIndex */)
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_onAttrModified)
touch();
}
//*********************************************************************************************************
void CDisplayerVisualActivitySequence::setActive(bool active)
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_setActive)
if (active == _Active) return;
if (!active)
{
clear();
}
else
{
touch();
}
_Active = active;
}
//*********************************************************************************************************
bool CDisplayerVisualActivitySequence::getActive() const
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_getActive)
return _Active;
}
//*********************************************************
void CDisplayerVisualActivitySequence::setWorldMapNumEdges(uint count)
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_setWorldMapNumEdges)
// TODO nico : try to factor with CPrimRender::setWorldMapNumEdges
nlassert(_AddedToWorldMap);
CGroupMap *gm = CTool::getWorldMap();
nlassert(gm);
if (count < _WorldMapEdges.size())
{
for(uint k = count; k < _WorldMapEdges.size(); ++k)
{
gm->delCtrl(_WorldMapEdges[k]);
}
}
else
{
uint left = count - _WorldMapEdges.size();
while (left --)
{
CCtrlQuad *cq = new CCtrlQuad;
cq->setModulateGlobalColor(false);
cq->setActive(true);
gm->addCtrl(cq);
cq->setParent(gm);
cq->setRenderLayer(1);
_WorldMapEdges.push_back(cq);
}
}
_WorldMapEdges.resize(count);
for(uint k = 0; k < count; ++k)
{
_WorldMapEdges[k]->setTexture(CV_FootStepMapTexture.get());
}
}
//*********************************************************
void CDisplayerVisualActivitySequence::onAdd(CGroupMap &/* owner */)
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_onAdd)
nlassert(!_AddedToWorldMap);
_AddedToWorldMap = true;
_Touched = true;
setWorldMapNumEdges(_FootSteps.size());
}
//*********************************************************
void CDisplayerVisualActivitySequence::onRemove(CGroupMap &/* owner */)
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_onRemove)
nlassert(_AddedToWorldMap);
setWorldMapNumEdges(0);
_AddedToWorldMap = false;
}
//*********************************************************
void CDisplayerVisualActivitySequence::onPreRender(CGroupMap &/* owner */)
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_onPreRender)
}
//*********************************************************
void CDisplayerVisualActivitySequence::onUpdate(CGroupMap &owner)
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_onUpdate)
nlassert(_AddedToWorldMap);
nlassert(_FootSteps.size() == _WorldMapEdges.size());
CViewRenderer &vr = getEditor().getUI().getViewRenderer();
sint32 id = vr.getTextureIdFromName(CV_FootStepMapTexture.get());
sint32 width, height;
vr.getTextureSizeFromId(id, width, height);
float invWorldTextureWidth = width > 0 ? 1.f / width : 0.f;
for(uint k = 0; k < _WorldMapEdges.size(); ++k)
{
sint32 px0, py0;
sint32 px1, py1;
owner.worldToWindowSnapped(px0, py0, _FootSteps[k].V0);
owner.worldToWindowSnapped(px1, py1, _FootSteps[k].V1);
CVector start((float) px0, (float) py0, 0.f);
CVector end((float) px1, (float) py1, 0.f);
_WorldMapEdges[k]->setQuad(start, end, CV_FootStepMapWidth.get());
_WorldMapEdges[k]->setPattern(0.f, (start - end).norm() * invWorldTextureWidth, CCtrlQuad::Repeat);
_WorldMapEdges[k]->updateCoords();
}
}
//*********************************************************
void CDisplayerVisualActivitySequence::setWorldMapColor(CRGBA color)
{
//H_AUTO(R2_CDisplayerVisualActivitySequence_setWorldMapColor)
nlassert(_AddedToWorldMap);
for(uint k = 0; k < _WorldMapEdges.size(); ++k)
{
_WorldMapEdges[k]->setColorRGBA(color);
}
}
} // R2