545 lines
15 KiB
C++
545 lines
15 KiB
C++
// Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
|
|
// 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 <http://www.gnu.org/licenses/>.
|
|
|
|
#include "stdpch.h"
|
|
//
|
|
#include "editor.h"
|
|
#include "tool_choose_pos.h"
|
|
#include "../interface_v3/interface_manager.h"
|
|
#include "../global.h"
|
|
#include "nel/gui/group_tree.h"
|
|
#include "../interface_v3/group_map.h"
|
|
#include "../landscape_poly_drawer.h"
|
|
//
|
|
#include "../entities.h"
|
|
//
|
|
#include "nel/misc/vector.h"
|
|
#include "nel/misc/quat.h"
|
|
#include "nel/misc/aabbox.h"
|
|
#include "nel/3d/u_instance.h"
|
|
//
|
|
#include "game_share/object.h"
|
|
#include "dmc/idmc.h"
|
|
#include "r2_config.h"
|
|
|
|
using namespace NLPACS;
|
|
using namespace NLMISC;
|
|
|
|
namespace R2
|
|
{
|
|
|
|
|
|
// ***************************************************************
|
|
CToolChoosePos::CToolChoosePos(sint ghostSlot,
|
|
const std::string &cursValid,
|
|
const std::string &cursInvalid,
|
|
const std::vector<CPolygon2D> &polyList,
|
|
const CPrimLook &polyValidLook,
|
|
const CPrimLook &polyInvalidLook
|
|
)
|
|
{
|
|
_CursValid = cursValid;
|
|
_CursInvalid = cursInvalid;
|
|
_GhostSlot = ghostSlot,
|
|
_Valid = false;
|
|
_WantedAction = NoAction;
|
|
_WarningNoMeshOrSkeletonShown = false;
|
|
CEntityCL *entity = getGhost();
|
|
_LocalSelectBoxSize = 1;
|
|
if (entity)
|
|
{
|
|
entity->getMeshDefaultScale(_DefaultScale);
|
|
if (!entity->skeleton())
|
|
{
|
|
const CVector &hs = getEditor().getLocalSelectBox(*entity).getHalfSize();
|
|
_LocalSelectBoxSize = maxof(fabsf(hs.x), fabsf(hs.y), fabsf(hs.z));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_DefaultScale.set(1.f, 1.f, 1.f);
|
|
}
|
|
_MultiPos = false;
|
|
_MultiPosLocked = false;
|
|
_BadPlaceDecal.setTexture("*accessibility_texture*", true, true, false);
|
|
_TestDecal.setTexture("encyclopedia_box_zo.tga", true, true, false);
|
|
//
|
|
_PolyList = polyList;
|
|
_PolyRender.resize(polyList.size());
|
|
_PolyValidLook = polyValidLook;
|
|
_PolyInvalidLook = polyInvalidLook;
|
|
CGroupMap *gm = getWorldMap();
|
|
for (uint k = 0; k < _PolyRender.size(); ++k)
|
|
{
|
|
_PolyRender[k].setActive(false);
|
|
if (gm)
|
|
{
|
|
gm->addDeco(&_PolyRender[k]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ***************************************************************
|
|
void CToolChoosePos::showPolys(bool visible)
|
|
{
|
|
//H_AUTO(R2_CToolChoosePos_showPolys)
|
|
for (uint k = 0; k < _PolyRender.size(); ++k)
|
|
{
|
|
_PolyRender[k].setActive(visible);
|
|
}
|
|
}
|
|
|
|
// ***************************************************************
|
|
void CToolChoosePos::enableMultiPos(const std::string &cursValidMulti /*="curs_create_multi.tga"*/)
|
|
{
|
|
//H_AUTO(R2_CToolChoosePos_enableMultiPos)
|
|
_MultiPos = true;
|
|
_CursValidMulti = cursValidMulti;
|
|
}
|
|
|
|
// ***************************************************************
|
|
CToolChoosePos::~CToolChoosePos()
|
|
{
|
|
cancel();
|
|
CGroupMap *gm = getWorldMap();
|
|
if (gm)
|
|
{
|
|
for (uint k = 0; k < _PolyRender.size(); ++k)
|
|
{
|
|
_PolyRender[k].setActive(false);
|
|
gm->removeDeco(&_PolyRender[k]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ***************************************************************
|
|
CEntityCL *CToolChoosePos::getGhost()
|
|
{
|
|
//H_AUTO(R2_CToolChoosePos_getGhost)
|
|
if (_GhostSlot == -1) return NULL;
|
|
return EntitiesMngr.entity(_GhostSlot);
|
|
}
|
|
|
|
// ***************************************************************
|
|
void CToolChoosePos::hideMapSelectionAxis()
|
|
{
|
|
//H_AUTO(R2_CToolChoosePos_hideMapSelectionAxis)
|
|
CGroupMap *worldMap = getWorldMap();
|
|
if (worldMap) worldMap->setSelectionAxis(false);
|
|
}
|
|
|
|
// ***************************************************************
|
|
void CToolChoosePos::updateInvalidCursorOnUI()
|
|
{
|
|
//H_AUTO(R2_CToolChoosePos_updateInvalidCursorOnUI)
|
|
setMouseCursor(DEFAULT_CURSOR);
|
|
}
|
|
|
|
// ***************************************************************
|
|
void CToolChoosePos::updateBeforeRender()
|
|
{
|
|
//H_AUTO(R2_CToolChoosePos_updateBeforeRender)
|
|
CGroupMap *worldMap = getWorldMap();
|
|
CEntityCL *entity = getGhost();
|
|
float dtBackup = DT;
|
|
DT = RZ_TIME_TO_BECOME_TRANSPARENT_IN_SECOND; // don't want a transition
|
|
if (entity)
|
|
{
|
|
uint32 oldOpacity = CEntityCL::getOpacityMin();
|
|
CEntityCL::setOpacityMin(DEFAULT_ENTITY_MIN_OPACITY);
|
|
entity->makeTransparent(true);
|
|
CEntityCL::setOpacityMin(oldOpacity);
|
|
}
|
|
DT = dtBackup;
|
|
// Build vector for direction pointed by mouse in world
|
|
sint32 mouseX, mouseY;
|
|
getMousePos(mouseX, mouseY);
|
|
if (!isInScreen(mouseX, mouseY) || (isMouseOnUI() && !isMouseOnWorldMap()))
|
|
{
|
|
if (worldMap) worldMap->setSelectionAxis(false);
|
|
if (entity)
|
|
{
|
|
entity->show(false);
|
|
}
|
|
updateInvalidCursorOnUI();
|
|
showPolys(false);
|
|
return;
|
|
}
|
|
//
|
|
CTool::CWorldViewRay worldViewRay;
|
|
//
|
|
computeWorldViewRay(mouseX, mouseY, worldViewRay);
|
|
//
|
|
CVector entityPos; // the pos where the ghost will be shown
|
|
CVector inter; // intersection of view ray with landscape
|
|
_Valid = false;
|
|
CVector scale = _DefaultScale;
|
|
TRayIntersectionType rayIntersectionType = computeLandscapeRayIntersection(worldViewRay, inter);
|
|
switch(rayIntersectionType)
|
|
{
|
|
case NoIntersection:
|
|
{
|
|
// no collision, can't drop entity
|
|
entityPos = worldViewRay.Origin + 3.f * worldViewRay.Dir;
|
|
// change the scale so that the entity has always the same size on screen
|
|
float refScale = CV_FloatingShapeRefScale.get();
|
|
if (refScale == 0.f) refScale = 1.f;
|
|
if (_LocalSelectBoxSize != 0.f)
|
|
{
|
|
scale *= refScale / _LocalSelectBoxSize;
|
|
}
|
|
if (worldMap) worldMap->setSelectionAxis(false);
|
|
showPolys(false);
|
|
}
|
|
break;
|
|
case ValidPacsPos:
|
|
entityPos = inter;
|
|
_CreatePosition = entityPos;
|
|
_Valid = isValidChoosePos(inter); // good pos to drop entity
|
|
if (worldMap) worldMap->setSelectionAxis(true, inter);
|
|
showPolys(true);
|
|
break;
|
|
case InvalidPacsPos:
|
|
entityPos = inter;
|
|
if (worldMap) worldMap->setSelectionAxis(true, inter);
|
|
showPolys(true);
|
|
break;
|
|
default:
|
|
nlassert(0);
|
|
break;
|
|
}
|
|
// compute front vector
|
|
CVector front = - MainCam.getMatrix().getJ();
|
|
front.z = 0.f;
|
|
front.normalize();
|
|
// compute angle around Z
|
|
_CreateAngle = (float) atan2(front.y, front.x);
|
|
//
|
|
if (entity)
|
|
{
|
|
CMatrix mat;
|
|
bool shown = false;
|
|
if (worldViewRay.OnMiniMap && rayIntersectionType == NoIntersection)
|
|
{
|
|
entity->show(false);
|
|
}
|
|
else
|
|
if (!entity->skeleton())
|
|
{
|
|
/*
|
|
nlwarning("Selected entity for the 'create' tool has no skeleton");
|
|
entity->show(false);
|
|
return;
|
|
*/
|
|
//bool loading = true;
|
|
NL3D::UInstance inst = entity->instance();
|
|
if (inst.empty())
|
|
{
|
|
if (!entity->instances().empty())
|
|
{
|
|
inst = entity->instances()[0].Current;
|
|
//loading = !entity->instances()[0].Loading.empty() && entity->instances()[0].Current.empty();
|
|
}
|
|
}
|
|
if (inst.empty())
|
|
{
|
|
if (!_WarningNoMeshOrSkeletonShown)
|
|
{
|
|
nlwarning("Selected entity for the 'create' tool has no instance");
|
|
_WarningNoMeshOrSkeletonShown = true;
|
|
}
|
|
entity->show(false);
|
|
showPolys(false);
|
|
return;
|
|
}
|
|
entity->show(true);
|
|
shown = true;
|
|
//
|
|
CQuat frontQuat(CVector::K, _CreateAngle - (float) (NLMISC::Pi / 2));
|
|
inst.setRotQuat(frontQuat);
|
|
inst.setPos(entityPos);
|
|
inst.setScale(scale);
|
|
mat = inst.getMatrix();
|
|
}
|
|
else
|
|
{
|
|
entity->show(true);
|
|
shown = true;
|
|
//
|
|
entity->updateVisible(T1, NULL);
|
|
entity->updatePos(T1, NULL);
|
|
|
|
/*
|
|
CMatrix skelMatrix = entity->skeleton()->getMatrix();
|
|
// relative position to skeleton root
|
|
CVector skelRootRelativePos = entity->skeleton()->getMatrix().getPos() - entity->pos().asVector();
|
|
// combine quat for front face xform & anim quat
|
|
CQuat frontQuat(CVector::K, _CreateAngle);
|
|
entity->skeleton()->setRotQuat(frontQuat * entity->skeleton()->getRotQuat());
|
|
entity->skeleton()->setPos(entityPos + skelRootRelativePos);
|
|
mat.setRot(frontQuat);
|
|
mat.setPos(entityPos);
|
|
*/
|
|
CMatrix skelMatrix = entity->skeleton()->getMatrix();
|
|
// relative position to skeleton root
|
|
CVector skelRootRelativePos = entity->skeleton()->getMatrix().getPos() - entity->pos().asVector();
|
|
// combine quat for front face xform & anim quat
|
|
CQuat frontQuat(CVector::K, _CreateAngle);
|
|
entity->skeleton()->setRotQuat(frontQuat * entity->skeleton()->getRotQuat());
|
|
CMatrix frontMat;
|
|
frontMat.setRot(frontQuat);
|
|
entity->skeleton()->setPos(entityPos + frontMat * skelRootRelativePos);
|
|
/*mat.setRot(frontQuat);
|
|
mat.setPos(entityPos);*/
|
|
|
|
}
|
|
// see if all pos are accessible and update the _Valid flag
|
|
// NB NICO : THE FOLLOWING CODE IS WORKING BUT
|
|
// see with others if it's useful to check this type of collisions
|
|
// finally -> since check is never done, limited/no interest ...
|
|
/*
|
|
if (shown && entity->getPrimitive())
|
|
{
|
|
float w;
|
|
float h;
|
|
if (entity->getPrimitive()->getPrimitiveType() == UMovePrimitive::_2DOrientedBox)
|
|
{
|
|
entity->getPrimitive()->getSize(w, h);
|
|
}
|
|
else
|
|
{
|
|
w = h = 2.f * entity->getPrimitive()->getRadius();
|
|
}
|
|
// transform from [0, 1] x [0, 1] to local bbox
|
|
CMatrix unitBoxToLocalPrim;
|
|
unitBoxToLocalPrim.setScale(CVector(w, h, 1.f));
|
|
//unitBoxToLocalBox.setPos(CVector(localBox.getMin().x / favoid0(w), localBox.getMin().y / favoid0(h), 1.f);
|
|
unitBoxToLocalPrim.setPos(CVector(- 0.5f * w, - 0.5f * h, 0.f));
|
|
CMatrix worldMat = mat * unitBoxToLocalPrim;
|
|
// test if place is valid
|
|
CPolygon poly;
|
|
poly.Vertices.resize(4);
|
|
poly.Vertices[0].set(0.f, 0.f, 0.f);
|
|
poly.Vertices[1].set(1.f, 0.f, 0.f);
|
|
poly.Vertices[2].set(1.f, 1.f, 0.f);
|
|
poly.Vertices[3].set(0.f, 1.f, 0.f);
|
|
CPolygon2D poly2D(poly, worldMat);
|
|
|
|
if (getEditor().getIslandCollision().isValidPoly(poly2D))
|
|
{
|
|
_TestDecal.setWorldMatrix(worldMat);
|
|
_TestDecal.addToRenderList();
|
|
}
|
|
else
|
|
{
|
|
_Valid = false;
|
|
_BadPlaceDecal.setWorldMatrix(worldMat);
|
|
_BadPlaceDecal.setDiffuse(getInvalidPosColor());
|
|
_BadPlaceDecal.setCustomUVMatrix(true, getEditor().getIslandCollision().getWorldToAccessibilityTexMat(true));
|
|
_BadPlaceDecal.addToRenderList();
|
|
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
// additionnal polygons
|
|
if (!_PolyRender.empty() && _PolyRender[0].getActive())
|
|
{
|
|
static NLMISC::CPolygon2D tmpPoly;
|
|
std::vector<NLMISC::CVector2f> &verts = tmpPoly.Vertices;
|
|
nlassert(_PolyRender.size() == _PolyList.size());
|
|
CIslandCollision &ic = getEditor().getIslandCollision();
|
|
for (uint k = 0; k < _PolyRender.size(); ++k)
|
|
{
|
|
verts.resize(_PolyList[k].Vertices.size());
|
|
for (uint l = 0; l < verts.size(); ++l)
|
|
{
|
|
verts[l].set(_PolyList[k].Vertices[l].x + _CreatePosition.x,
|
|
_PolyList[k].Vertices[l].y + _CreatePosition.y);
|
|
}
|
|
bool validPoly = ic.isValidPoly(tmpPoly); // Test all position because entities may be created in that zone
|
|
|
|
|
|
// also test zone vertices because in the end, a valid zone is a zone for which all vertices
|
|
// are valid
|
|
for (uint l = 0; validPoly && l < verts.size(); ++l)
|
|
{
|
|
if (!ic.isValidPos(verts[l])) validPoly = false;
|
|
}
|
|
|
|
|
|
if (!validPoly) _Valid = false;
|
|
|
|
/*
|
|
bool validPoly = true;
|
|
for (uint l = 0; l < verts.size() && validPoly; ++l)
|
|
{
|
|
if (!ic.isValidSegment(verts[l], verts[(l + 1) % verts.size()]))
|
|
{
|
|
validPoly = false;
|
|
_Valid = false;
|
|
}
|
|
}
|
|
*/
|
|
_PolyRender[k].setLook(validPoly ? _PolyValidLook : _PolyInvalidLook);
|
|
_PolyRender[k].setVertices(verts);
|
|
_PolyRender[k].addDecalsToRenderList();
|
|
|
|
if (_PolyRender[k].getLook().Shape == CPrimLook::ClosedPolyLine)
|
|
{
|
|
NLMISC::CAABBox bbox;
|
|
static volatile bool showPoly = true;
|
|
if (showPoly)
|
|
{
|
|
CLandscapePolyDrawer::getInstance().computeBBoxFromPolygon(tmpPoly, bbox);
|
|
CLandscapePolyDrawer::getInstance().addPoly(tmpPoly, _PolyRender[k].getLook().EdgeLook.WorldMapColor, bbox);
|
|
}
|
|
_PolyRender[k].setWorldMapPolyColor(_PolyRender[k].getLook().EdgeLook.WorldMapColor);
|
|
}
|
|
}
|
|
}
|
|
// change mouse depending on result
|
|
if (!isMouseOnUI() || isMouseOnWorldMap())
|
|
{
|
|
|
|
if (_Valid)
|
|
{
|
|
if (_MultiPos && isShiftDown() && !_MultiPosLocked)
|
|
{
|
|
setMouseCursor(_CursValidMulti.c_str());
|
|
}
|
|
else
|
|
{
|
|
setMouseCursor(_CursValid.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
setMouseCursor(_CursInvalid.c_str());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
setMouseCursor(_CursValid.c_str());
|
|
}
|
|
}
|
|
|
|
// ***************************************************************
|
|
void CToolChoosePos::updateAfterRender()
|
|
{
|
|
//H_AUTO(R2_CToolChoosePos_updateAfterRender)
|
|
CTool::TSmartPtr oldThis(this); // may be changed during commit
|
|
switch(_WantedAction)
|
|
{
|
|
case SelectPos:
|
|
{
|
|
showPolys(false);
|
|
hideMapSelectionAxis();
|
|
if (_Valid)
|
|
{
|
|
// signal deriver that a position has been chosen
|
|
commit(_CreatePosition, _CreateAngle);
|
|
if (stopAfterCommit() && (!_MultiPos || !isShiftDown()) && !_MultiPosLocked)
|
|
{
|
|
if (_GhostSlot != -1)
|
|
{
|
|
//EntitiesMngr.remove(_GhostSlot, true);
|
|
CEntityCL *ghost = EntitiesMngr.entity(_GhostSlot);
|
|
if (ghost)
|
|
{
|
|
// just hide the slot because creation may happen just after, so keep
|
|
// a ref on textures and shape
|
|
ghost->show(false);
|
|
}
|
|
}
|
|
getEditor().setCurrentTool(NULL);
|
|
}
|
|
else
|
|
{
|
|
_WantedAction = NoAction;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case Cancel:
|
|
{
|
|
cancel();
|
|
getEditor().setCurrentTool(NULL);
|
|
}
|
|
break;
|
|
case NoAction:
|
|
// no-op
|
|
break;
|
|
default:
|
|
nlassert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************
|
|
void CToolChoosePos::removeGhostSlot()
|
|
{
|
|
//H_AUTO(R2_CToolChoosePos_removeGhostSlot)
|
|
if (_GhostSlot != - 1 && getGhost())
|
|
{
|
|
CEntityCL *ghost = EntitiesMngr.entity(_GhostSlot);
|
|
if (ghost)
|
|
{
|
|
// just hide the slot because creation may happen just after, so keep
|
|
// a ref on textures and shape (this avoid a release from memory)
|
|
ghost->show(false);
|
|
ghost->displayable(false);
|
|
}
|
|
//EntitiesMngr.remove(_GhostSlot, true); // remove the ghost
|
|
_GhostSlot = -1;
|
|
}
|
|
}
|
|
|
|
// ***************************************************************
|
|
bool CToolChoosePos::onMouseLeftButtonClicked()
|
|
{
|
|
//H_AUTO(R2_CToolChoosePos_onMouseLeftButtonClicked)
|
|
if (_Valid)
|
|
{
|
|
_WantedAction = SelectPos;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// ***************************************************************
|
|
bool CToolChoosePos::onMouseRightButtonClicked()
|
|
{
|
|
//H_AUTO(R2_CToolChoosePos_onMouseRightButtonClicked)
|
|
_WantedAction = Cancel;
|
|
return true;
|
|
}
|
|
|
|
// ***************************************************************
|
|
void CToolChoosePos::cancel()
|
|
{
|
|
//H_AUTO(R2_CToolChoosePos_cancel)
|
|
showPolys(false);
|
|
// TODO nico : potential crash here if called just before render (in updateBeforeRender)
|
|
// TODO nico : rethink the deferred action stuff to avoid this possible case
|
|
hideMapSelectionAxis();
|
|
removeGhostSlot();
|
|
}
|
|
|
|
|
|
|
|
|
|
} // R2
|