// 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 "../interface_v3/group_map.h" // #include "tool_draw_prim.h" #include "game_share/object.h" #include "dmc/idmc.h" #include "editor.h" #include "r2_config.h" #include "object_factory_client.h" #include "displayer_visual_group.h" #include "tool_maintained_action.h" // #include "nel/misc/vectord.h" #include "nel/misc/i18n.h" #include "nel/misc/polygon.h" // // tmp tmp #include "../interface_v3/ctrl_quad.h" using namespace NLMISC; namespace R2 { //*************************************************************** CToolDrawPrim::CToolDrawPrim(TPrimType primType, CInstance *extending /*= NULL*/) { _NumPoints = 0; _ValidPos = false; _PrimType = primType; _MustClose = false; _PrimInitialized = false; _ValidPrim = true; _DistinctLastPoint = true; _ExtendedPrimitive = extending; _Extending = (_ExtendedPrimitive != NULL); _StartNumPoints = 0; _InaccessibleParts = false; _Commited = false; _ForceShowPrims = false; } //*************************************************************** bool CToolDrawPrim::canTerminate() const { //H_AUTO(R2_CToolDrawPrim_canTerminate) return ((_PrimType == Road && _NumPoints >= 1) || _NumPoints >= 3) && _ValidPrim; } //*************************************************************** const char *CToolDrawPrim::getToolUIName() const { //H_AUTO(R2_CToolDrawPrim_getToolUIName) return _PrimType == Road ? "drawRoad" : "drawRegion"; } //*************************************************************** bool CToolDrawPrim::init(const CLuaObject ¶meters) { //H_AUTO(R2_CToolDrawPrim_init) _Points.clear(); _PrimLook.init(parameters["Look"]); if (!parameters["InvalidLook"].isNil()) { _PrimLookInvalid.init(parameters["InvalidLook"]); } else { _PrimLookInvalid = _PrimLook; } if (!parameters["CanCloseLook"].isNil()) { _PrimLookCanClose.init(parameters["CanCloseLook"]); } else { _PrimLookCanClose = _PrimLook; } /* if (!parameters["InaccessibleLook"].isNil()) { _PrimLookInaccessible.init(parameters["InaccessibleLook"]); } else { _PrimLookInaccessible = _PrimLook; } */ //TMP TMP hardcoded for first test _PrimLookInaccessible.init(getEditor().getEnv()["PrimRender"]["RoadLookInaccessible"]); // _InaccessiblePrim.setLook(_PrimLookInaccessible); // CLuaObject vertices = parameters["Vertices"]; if (vertices.isTable()) { // if start points where given, use them ENUM_LUA_TABLE(vertices, it) { CLuaObject &vertex = it.nextValue(); _Points.push_back(NLMISC::CVector((float) vertex["x"].toNumber(), (float) vertex["y"].toNumber(), (float) vertex["z"].toNumber())); } } if (!parameters["ExtendedPrimitiveId"].isNil()) { std::string extendedPrimitiveId = parameters["ExtendedPrimitiveId"]; _ExtendedPrimitive = getEditor().getInstanceFromId(extendedPrimitiveId); if (!_ExtendedPrimitive) { _Extending = false; nlwarning("Can't extend primitive with id %s", extendedPrimitiveId.c_str()); return false; } _Extending = true; } _CancelFunc = parameters["OnCancel"]; _CookieKey = parameters["CookieKey"].toString(); _CookieValue = parameters["CookieValue"]; _PrimType = parameters["Type"].toString() == "Region" ? Region : Road; _Prim.setLook(_PrimLook); _NumPoints = _Points.size(); if (!parameters["ForceShowPrims"].isNil()) { _ForceShowPrims = parameters["ForceShowPrims"].toBoolean(); } if (!parameters["SelectInstance"].isNil()) { _SelectInstance = parameters["SelectInstance"].toBoolean(); } else { _SelectInstance = true; } CGroupMap *worldMap = getWorldMap(); if (worldMap) { worldMap->addDeco(&_Prim); // display primitive on the map } return true; } //*************************************************************** void CToolDrawPrim::updateAfterRender() { //H_AUTO(R2_CToolDrawPrim_updateAfterRender) } //*************************************************************** void CToolDrawPrim::removeFromWorldMap() { //H_AUTO(R2_CToolDrawPrim_removeFromWorldMap) CGroupMap *worldMap = getWorldMap(); if (worldMap) { if (_Prim.isAddedToWorldMap()) { worldMap->removeDeco(&_Prim); } if (_InaccessiblePrim.isAddedToWorldMap()) { worldMap->removeDeco(&_InaccessiblePrim); } } } //*************************************************************** void CToolDrawPrim::cancel() { //H_AUTO(R2_CToolDrawPrim_cancel) if (_ExtendedPrimitive) { CDisplayerVisualGroup *dv = dynamic_cast(_ExtendedPrimitive->getDisplayerVisual()); if (dv) { dv->setActiveRecurse(true); } } removeFromWorldMap(); if (!_Commited) { if (_CancelFunc.isFunction()) { _CancelFunc.callNoThrow(0, 0); } } } //*************************************************************** void CToolDrawPrim::setPrimLook(bool closed, bool lastEdgeIsValid, bool valid) { //H_AUTO(R2_CToolDrawPrim_setPrimLook) CPrimLook *look; if (!valid) look = &_PrimLookInvalid; else if (closed) look = &_PrimLookCanClose; else look = &_PrimLook; look->LastEdgeIsValid = lastEdgeIsValid; _Prim.setLook(*look); } //*************************************************************** void CToolDrawPrim::updateBeforeRender() { //H_AUTO(R2_CToolDrawPrim_updateBeforeRender) doUpdateBeforeRender(); CGroupMap *worldMap = getWorldMap(); if (worldMap && !_InaccessibleParts && _InaccessiblePrim.isAddedToWorldMap()) { worldMap->removeDeco(&_InaccessiblePrim); // display primitive on the map } } //*************************************************************** void CToolDrawPrim::doUpdateBeforeRender() { //H_AUTO(R2_CToolDrawPrim_doUpdateBeforeRender) if (_Extending && !_ExtendedPrimitive) { cancel(); return; } _MustClose = false; // Build vector for direction pointed by mouse in world sint32 mouseX, mouseY; getMousePos(mouseX, mouseY); if (!isInScreen(mouseX, mouseY)) { // mouse not in screen so don't display the last point _Points.resize(_NumPoints); _Prim.setVertices(_Points); updateValidityFlag(false); setPrimLook(false, true, _ValidPrim); _Prim.addDecalsToRenderList(); return; } sint32 autoPanDx, autoPanDy; if (_NumPoints >= 1) { handleWorldMapAutoPan(autoPanDx, autoPanDy); } // CTool::CWorldViewRay worldViewRay; // computeWorldViewRay(mouseX, mouseY, worldViewRay); // _ValidPos = false; CVector inter; CVector wpPos; TRayIntersectionType interType = computeLandscapeRayIntersection(worldViewRay, inter); switch(interType) { case NoIntersection: if (worldViewRay.OnMiniMap) { _Points.resize(_NumPoints); _Prim.setVertices(_Points); updateValidityFlag(false); setPrimLook(false, true, _ValidPrim); _Prim.addDecalsToRenderList(); setMouseCursor("curs_stop.tga"); return; } // no collision, can't drop entity wpPos = worldViewRay.Origin + 3.f * worldViewRay.Dir; break; case ValidPacsPos: wpPos = inter; _ValidPos = isValid2DPos(inter); // good pos to drop entity break; case InvalidPacsPos: wpPos = inter; break; default: nlassert(0); break; } // If there are at least 3 vertices and the primitive is a region, then see wether mouse // is near the first vertex -> in this case, propose to the user to close the primitive // If landscape is intersected too, then the vertex must be nearer then the first vertex if (_PrimType == Region && _NumPoints >= 3) { bool canClose = false; CGroupMap *gm = isMouseOnWorldMap(); if (gm) { CViewBitmap *bm = _Prim.getWorldMapVertexView(0); if (bm) { canClose = bm->isIn(mouseX, mouseY); } } else { if (!_Prim.getVerticesShapeInstance().empty()) { if (!_Prim.getVerticesShapeInstance()[0].empty()) { NLMISC::CAABBox vertexBBox; _Prim.getVerticesShapeInstance()[0].getShapeAABBox(vertexBBox); vertexBBox.setCenter(_Points[0]); if (vertexBBox.intersect(worldViewRay.Origin, worldViewRay.Origin + 200.f * worldViewRay.Dir)) { // if vertex nearer than landscape bool landscapeNearest = false; if (interType != NoIntersection) { // TODO nico : the test is not exact : should use intersection with the bbox to do // length comparison over the same line ... landscapeNearest = (wpPos - worldViewRay.Origin).norm() < (_Points[0] - worldViewRay.Origin).norm(); } canClose = !landscapeNearest; } } } } if (canClose) { _Points.resize(_NumPoints); _Prim.setVertices(_Points); updateValidityFlag(false); setPrimLook(true, true, _ValidPrim); // show a closed polygon _MustClose = true; setMouseCursor("curs_pick.tga"); _Prim.addDecalsToRenderList(); return; } } _Prim.setEmissive(CRGBA::Black); // _Points.resize(_NumPoints + 1); if (_Points.size() >= 2) { _DistinctLastPoint = (_Points[_NumPoints] != _Points[_NumPoints - 1]); } else { _DistinctLastPoint = true; } _Points.back() = wpPos; _Prim.setVertices(_Points); updateValidityFlag(!_ValidPos); // updateValidityFlag(interType == NoIntersection); setPrimLook(false, _ValidPos, _ValidPrim); // change mouse depending on result if (!isMouseOnUI() || isMouseOnWorldMap() || !_ValidPrim) { setMouseCursor(_ValidPos /* && !_InaccessibleParts */ ? "curs_create.tga" : "curs_stop.tga"); } else { setMouseCursor("curs_create.tga"); } _Prim.addDecalsToRenderList(); CGroupMap *worldMap = getWorldMap(); //static CCtrlQuad *testQuad = NULL; if (_InaccessibleParts) { /*if (!testQuad) { testQuad = new CCtrlQuad; testQuad->setModulateGlobalColor(false); float thickness = 256.f; testQuad->setQuad(CVector(0.f, thickness, 0.f), CVector(thickness * 2.f, thickness, 0.f), thickness); testQuad->setTexture("*accessibility_texture*"); worldMap->addCtrl(testQuad); testQuad->setParent(worldMap); }*/ if (worldMap && !_InaccessiblePrim.isAddedToWorldMap()) { worldMap->addDeco(&_InaccessiblePrim); // display primitive on the map // } updateInaccessiblePrimRenderLook(_InaccessiblePrim); _InaccessiblePrim.setVertices(_Points); _InaccessiblePrim.addDecalsToRenderList(); } // TMP TMP static volatile bool testDecal = false; if (testDecal) { R2::CScenarioEntryPoints::CCompleteIsland *id = getEditor().getIslandCollision().getCurrIslandDesc(); if (id) { CMatrix m; m.setScale(CVector(40000.f, 40000.f, 40000.f)); m.setPos(CVector(0.f, -40000.f, 0.f)); _TestDecal.setWorldMatrix(m); _TestDecal.setCustomUVMatrix(true, getEditor().getIslandCollision().getWorldToAccessibilityTexMat(true)); _TestDecal.setTexture("*accessibility_texture*"); _TestDecal.addToRenderList(); } } } //*************************************************************** void CToolDrawPrim::updateInaccessiblePrimRenderLook(CPrimRender &dest) { //H_AUTO(R2_CToolDrawPrim_updateInaccessiblePrimRenderLook) double duration = CV_InaccessiblePosAnimDurationInMS.get(); if (duration <= 0) duration = 0.1; CRGBA color = blend(CV_InaccessiblePosColor0.get(), CV_InaccessiblePosColor1.get(), 0.5f + 0.5f * (float) cos(NLMISC::Pi * fmod((double) T1, duration) / duration)); CPrimLook look = dest.getLook(); look.EdgeLook.WorldMapColor = color; look.EdgeLook.DecalColor = color; dest.setLook(look); dest.setCustomWorldMapEdgeUVMatrix(true, getEditor().getIslandCollision().getWorldToAccessibilityTexMat()); dest.setCustomDecalEdgeUVMatrix(true, getEditor().getIslandCollision().getWorldToAccessibilityTexMat(true)); // cropped version for decal rendering } //*************************************************************** bool CToolDrawPrim::onMouseLeftButtonClicked() { //H_AUTO(R2_CToolDrawPrim_onMouseLeftButtonClicked) if (!checkRoomLeft()) { displayNoMoreRoomLeftMsg(); return true; } if (_ValidPos /*&& !_InaccessibleParts*/ && _DistinctLastPoint) { ++ _NumPoints; } if (_ValidPos) { startDoubleClickCheck(); } return true; } //*************************************************************** bool CToolDrawPrim::onMouseLeftButtonDown() { //H_AUTO(R2_CToolDrawPrim_onMouseLeftButtonDown) if (_MustClose && _ValidPos) { commit(); captureMouse(); // mouse will be released on "mouse up" by the default tool return true; } if (!checkDoubleClick()) return false; if (canTerminate()) { // the 2 last points must have a different position commit(); CToolMaintainedAction *tma = dynamic_cast(getEditor().getCurrentTool()); if (tma) { tma->markPreviousToolClickEnd(); } } return true; } //*************************************************************** void CToolDrawPrim::commit() { //H_AUTO(R2_CToolDrawPrim_commit) if (_Extending && !_ExtendedPrimitive) { cancel(); return; } removeFromWorldMap(); _Points.resize(_NumPoints); if (_Points.empty()) return; // send network command to create a new road CObject *desc = NULL; if (!_Extending) { getDMC().newAction(NLMISC::CI18N::get(_PrimType == Road ? "uiR2EDCreateRouteAction" : "uiR2EDCreateZoneAction")); } else { getDMC().newAction(NLMISC::CI18N::get(_PrimType == Road ? "uiR2EDExtendRouteAction" : "uiR2EDExtendZoneAction")); } if (!_Extending) { // creating a new primitive, rather than growing an existing one std::string className = _PrimType == Road ? "Road": "Region"; desc = getDMC().newComponent(className); } if (desc || _Extending) { CObject *points = NULL; if (!_Extending) { std::string instanceId = getString(desc, "InstanceId"); if (!instanceId.empty()) { // ask a flag to prompt user to enter name for this object when it is created getEditor().setCookie(instanceId, "AskName", true); if (_CookieValue.isValid()) { if (!_CookieKey.empty() && !_CookieValue.isNil()) { getEditor().setCookie(instanceId, _CookieKey, _CookieValue); } } getEditor().setCookie(instanceId, "Select", _SelectInstance); } static volatile bool wantDump = false; if (wantDump) { desc->dump(); } points = desc->getAttr("Points"); for(uint k = 0; k < _Points.size(); ++k) { CObject *wp = getDMC().newComponent(_PrimType == Road ? "WayPoint" : "RegionVertex"); if (!wp) continue; wp->setObject("Position", buildVector(CVectorD(_Points[k]))); points->insert("", wp, -1); } } if (!_Extending) { // set readable name ucstring readableName = NLMISC::CI18N::get(_PrimType == Road ? "uiR2EDNameBotRoad" : "uiR2EDNameBotRegion"); readableName = getEditor().genInstanceName(readableName); desc->set("Name", readableName.toUtf8()); // send creation command // tmp : static npc counter // add in component list of default feature if (getEditor().getDefaultFeature()) { getDMC().requestInsertNode(getEditor().getDefaultFeature(getEditor().getBaseAct())->getId(), "Components", -1, "", desc); } // delete desc; // NB : if display of primitives is 'hide all', then force to 'show all', else // the new prim wouldn't be visible if (_ForceShowPrims) { if (getEditor().getEnv()["PrimDisplayVisible"].toBoolean() == false || getEditor().getEnv()["PrimDisplayContextualVisibility"].toBoolean() == true) { getEditor().callEnvMethod("notifyPrimDisplayShowAll", 0, 0); } } } else { nlassert(!desc); CVectorD offset = CVectorD::Null; CDisplayerVisualGroup *dv = dynamic_cast(_ExtendedPrimitive->getDisplayerVisual()); if (dv) { offset = - dv->getWorldPos(); } std::vector sons; dv->getSons(sons); for(uint k = _StartNumPoints - 1; k > _Points.size() - 1; --k) { getDMC().requestEraseNode(sons[k]->getDisplayedInstance()->getId(), "", -1); } for (uint k = 0; k < std::min(_StartNumPoints, uint(_Points.size())); ++k) { // change modified vertices CVector newWorldPos = CVectorD(_Points[k]).asVector(); if (newWorldPos != _InitialPoints[k]) { CObject *wp = getDMC().newComponent(_PrimType == Road ? "WayPoint" : "RegionVertex"); if (wp) { CObject *newPos = buildVector(offset + CVectorD(_Points[k])); getDMC().requestSetNode(sons[k]->getDisplayedInstance()->getId(), "Position", newPos); delete newPos; } } } // insert newly created vertices for(uint k = _StartNumPoints; k < _Points.size(); ++k) { CObject *wp = getDMC().newComponent(_PrimType == Road ? "WayPoint" : "RegionVertex"); if (!wp) continue; wp->setObject("Position", buildVector(offset + CVectorD(_Points[k]))); getDMC().requestInsertNode(_ExtendedPrimitive->getId(), "Points", -1, "", wp); delete wp; } if (dv) { dv->setActiveRecurse(true); } } } _Commited = true; getEditor().setCurrentTool(NULL); // set the default tool return; } //*************************************************************** bool CToolDrawPrim::onMouseRightButtonClicked() { //H_AUTO(R2_CToolDrawPrim_onMouseRightButtonClicked) // TMP : exception for primitive drawing : right button finish the road if (!checkRoomLeft()) { displayNoMoreRoomLeftMsg(); return true; } if (_ValidPos && _DistinctLastPoint) { if (!_MustClose) { ++ _NumPoints; } if (canTerminate()) { commit(); } } return true; // cancel the drawing //getEditor().setCurrentTool(NULL); //return true; } //*************************************************************** bool CToolDrawPrim::onDeleteCmd() { //H_AUTO(R2_CToolDrawPrim_onDeleteCmd) CTool::TSmartPtr hold(this); if (_NumPoints == 0) { if (_Extending) { getDMC().newAction(CI18N::get("uiR2EDDeleteRoad") + _ExtendedPrimitive->getDisplayName()); // just erase the road getDMC().requestEraseNode(_ExtendedPrimitive->getId(), "", -1); getDMC().getActionHistoric().endAction(); } // cancel the drawing getEditor().setCurrentTool(NULL); } else { -- _NumPoints; _Points.resize(_NumPoints); } return true; } //*************************************************************** bool CToolDrawPrim::isValidPolyShape(bool ignoreLast, std::list &splitPoly) const { //H_AUTO(R2_CToolDrawPrim_isValidPolyShape) splitPoly.clear(); if(_Points.size() <= 3) { return true; } // test that no self-intersection is found static NLMISC::CPolygon2D poly2D; poly2D.Vertices.resize(_Points.size()); for(uint k = 0; k < _Points.size(); ++k) { poly2D.Vertices[k] = _Points[k]; } if (poly2D.selfIntersect()) return false; CPolygon poly; poly.Vertices = _Points; if (poly.Vertices.back() == poly.Vertices[poly.Vertices.size() - 2] || ignoreLast) { poly.Vertices.pop_back(); // duplicated point at the end will always fails the test, so remove it } bool validPrim = poly.toConvexPolygons(splitPoly, CMatrix::Identity); if (!validPrim) { std::reverse(poly.Vertices.begin(), poly.Vertices.end()); validPrim = poly.toConvexPolygons(splitPoly, CMatrix::Identity); } return validPrim; } //*************************************************************** bool CToolDrawPrim::testAccessibleEdges(bool ignoreLast) { //H_AUTO(R2_CToolDrawPrim_testAccessibleEdges) uint numPoints = _Points.size(); if (_PrimType == Road) { numPoints -= ignoreLast ? 2 : 1; } else { numPoints -= ignoreLast ? 2 : 0; } for (sint k = 0; k < (sint) numPoints; ++k) { if (!getEditor().getIslandCollision().isValidSegment(_Points[k], _Points[(k + 1) % _Points.size()])) { return false; } } return true; } //*************************************************************** void CToolDrawPrim::updateValidityFlag(bool ignoreLast) { //H_AUTO(R2_CToolDrawPrim_updateValidityFlag) _ValidPrim = true; if (_PrimType != Road) { std::list splitPoly; if (!isValidPolyShape(ignoreLast, splitPoly)) { _ValidPrim = false; } } } /* { nlassert(0); // old code if (_LastPoints.size() == _Points.size() && std::equal(_Points.begin(), _Points.end(), _LastPoints.begin())) return; // not modified, so last flag remains valid _InaccessibleParts = false; _LastPoints = _Points; // CHANGE: accessibility for edges not tested anymore ... // // bool accessible = testAccessibleEdges(ignoreLast); // _InaccessibleParts = !accessible; // _InaccessibleParts = false; sint testPointIndex = _Points.size() - (ignoreLast ? 2 : 1); if (testPointIndex < 0) { _ValidPrim = false; return; } // change : valid to create if current mouse pos is valid (test 0.5f meters around) CVector2f testPos = _Points[testPointIndex]; _ValidPrim = getEditor().getIslandCollision().isValidPos(testPos); if (!_ValidPrim) return; // old test : all parts crossed by the edges needed to be accessible // // if (!accessible) // { // _ValidPrim = false; //} if (_PrimType != Road) { std::list splitPoly; if (!isValidPolyShape(ignoreLast, splitPoly)) { _ValidPrim = false; } } }*/ //*************************************************************** void CToolDrawPrim::onActivate() { //H_AUTO(R2_CToolDrawPrim_onActivate) setContextHelp(CI18N::get("uiR2EDToolDrawPrim")); if (_ExtendedPrimitive) { CDisplayerVisualGroup *dv = dynamic_cast(_ExtendedPrimitive->getDisplayerVisual()); if (!dv) { cancel(); return; } else { dv->setActiveRecurse(false); // hide the primitive being extended, because we display // our own version until we are finished } std::vector sons; dv->getSons(sons); for(uint k = 0; k < sons.size(); ++k) { _Points.push_back(sons[k]->getWorldPos().asVector()); } _NumPoints = _Points.size(); _StartNumPoints = _NumPoints; _InitialPoints = _Points; } } //*************************************************************** bool CToolDrawPrim::checkRoomLeft() { //H_AUTO(R2_CToolDrawPrim_checkRoomLeft) return _NumPoints < PrimMaxNumPoints; } //*************************************************************** void CToolDrawPrim::displayNoMoreRoomLeftMsg() { //H_AUTO(R2_CToolDrawPrim_displayNoMoreRoomLeftMsg) getEditor().callEnvMethod("noMoreRoomLeftInPrimitveMsg", 0, 0); } } // R2