// 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 "editor.h" #include "tool_choose_pos.h" #include "../interface_v3/interface_manager.h" #include "../global.h" #include "../interface_v3/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 &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 &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