1 /* 2 Copyright (C) 2010-2014 Kristian Duske 3 4 This file is part of TrenchBroom. 5 6 TrenchBroom is free software: you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 TrenchBroom is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with TrenchBroom. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 #include "ResizeBrushesTool.h" 21 22 #include "Model/Brush.h" 23 #include "Model/BrushFace.h" 24 #include "Model/BrushGeometry.h" 25 #include "Model/CollectMatchingBrushFacesVisitor.h" 26 #include "Model/FindMatchingBrushFaceVisitor.h" 27 #include "Model/HitAdapter.h" 28 #include "Model/HitQuery.h" 29 #include "Model/NodeVisitor.h" 30 #include "Model/PickResult.h" 31 #include "Renderer/Camera.h" 32 #include "View/Grid.h" 33 #include "View/MapDocument.h" 34 35 namespace TrenchBroom { 36 namespace View { 37 const Model::Hit::HitType ResizeBrushesTool::ResizeHit2D = Model::Hit::freeHitType(); 38 const Model::Hit::HitType ResizeBrushesTool::ResizeHit3D = Model::Hit::freeHitType(); 39 ResizeBrushesTool(MapDocumentWPtr document)40 ResizeBrushesTool::ResizeBrushesTool(MapDocumentWPtr document) : 41 Tool(true), 42 m_document(document), 43 m_splitBrushes(false) {} 44 applies() const45 bool ResizeBrushesTool::applies() const { 46 MapDocumentSPtr document = lock(m_document); 47 return document->selectedNodes().hasBrushes(); 48 } 49 pick2D(const Ray3 & pickRay,const Model::PickResult & pickResult)50 Model::Hit ResizeBrushesTool::pick2D(const Ray3& pickRay, const Model::PickResult& pickResult) { 51 MapDocumentSPtr document = lock(m_document); 52 const Model::Hit& hit = pickResult.query().pickable().type(Model::Brush::BrushHit).occluded().selected().first(); 53 if (hit.isMatch()) 54 return Model::Hit::NoHit; 55 return pickProximateFace(ResizeHit2D, pickRay); 56 } 57 pick3D(const Ray3 & pickRay,const Model::PickResult & pickResult)58 Model::Hit ResizeBrushesTool::pick3D(const Ray3& pickRay, const Model::PickResult& pickResult) { 59 MapDocumentSPtr document = lock(m_document); 60 const Model::Hit& hit = pickResult.query().pickable().type(Model::Brush::BrushHit).occluded().selected().first(); 61 if (hit.isMatch()) 62 return Model::Hit(ResizeHit3D, hit.distance(), hit.hitPoint(), Model::hitToFace(hit)); 63 return pickProximateFace(ResizeHit3D, pickRay); 64 } 65 66 class ResizeBrushesTool::PickProximateFace : public Model::ConstNodeVisitor, public Model::NodeQuery<Model::Hit> { 67 private: 68 const Model::Hit::HitType m_hitType; 69 const Ray3& m_pickRay; 70 FloatType m_closest; 71 public: PickProximateFace(const Model::Hit::HitType hitType,const Ray3 & pickRay)72 PickProximateFace(const Model::Hit::HitType hitType, const Ray3& pickRay) : 73 NodeQuery(Model::Hit::NoHit), 74 m_hitType(hitType), 75 m_pickRay(pickRay), 76 m_closest(std::numeric_limits<FloatType>::max()) {} 77 private: doVisit(const Model::World * world)78 void doVisit(const Model::World* world) {} doVisit(const Model::Layer * layer)79 void doVisit(const Model::Layer* layer) {} doVisit(const Model::Group * group)80 void doVisit(const Model::Group* group) {} doVisit(const Model::Entity * entity)81 void doVisit(const Model::Entity* entity) {} doVisit(const Model::Brush * brush)82 void doVisit(const Model::Brush* brush) { 83 const Model::Brush::EdgeList edges = brush->edges(); 84 Model::Brush::EdgeList::const_iterator it, end; 85 for (it = edges.begin(), end = edges.end(); it != end; ++it) 86 visitEdge(*it); 87 } 88 visitEdge(Model::BrushEdge * edge)89 void visitEdge(Model::BrushEdge* edge) { 90 Model::BrushFace* left = edge->firstFace()->payload(); 91 Model::BrushFace* right = edge->secondFace()->payload(); 92 const double leftDot = left->boundary().normal.dot(m_pickRay.direction); 93 const double rightDot = right->boundary().normal.dot(m_pickRay.direction); 94 95 if ((leftDot > 0.0) != (rightDot > 0.0)) { 96 const Ray3::LineDistance result = m_pickRay.distanceToSegment(edge->firstVertex()->position(), edge->secondVertex()->position()); 97 if (!Math::isnan(result.distance) && result.distance < m_closest) { 98 m_closest = result.distance; 99 const Vec3 hitPoint = m_pickRay.pointAtDistance(result.rayDistance); 100 if (m_hitType == ResizeBrushesTool::ResizeHit2D) { 101 Model::BrushFaceList faces; 102 if (Math::zero(leftDot)) { 103 faces.push_back(left); 104 } else if (Math::zero(rightDot)) { 105 faces.push_back(right); 106 } else { 107 if (Math::abs(leftDot) < 1.0) 108 faces.push_back(left); 109 if (Math::abs(rightDot) < 1.0) 110 faces.push_back(right); 111 } 112 setResult(Model::Hit(m_hitType, result.rayDistance, hitPoint, faces)); 113 } else { 114 Model::BrushFace* face = leftDot > rightDot ? left : right; 115 setResult(Model::Hit(m_hitType, result.rayDistance, hitPoint, face)); 116 } 117 } 118 } 119 } 120 }; 121 pickProximateFace(const Model::Hit::HitType hitType,const Ray3 & pickRay) const122 Model::Hit ResizeBrushesTool::pickProximateFace(const Model::Hit::HitType hitType, const Ray3& pickRay) const { 123 PickProximateFace visitor(hitType, pickRay); 124 125 MapDocumentSPtr document = lock(m_document); 126 const Model::NodeList& nodes = document->selectedNodes().nodes(); 127 Model::Node::accept(nodes.begin(), nodes.end(), visitor); 128 129 if (!visitor.hasResult()) 130 return Model::Hit::NoHit; 131 return visitor.result(); 132 } 133 hasDragFaces() const134 bool ResizeBrushesTool::hasDragFaces() const { 135 return !m_dragFaces.empty(); 136 } 137 dragFaces() const138 const Model::BrushFaceList& ResizeBrushesTool::dragFaces() const { 139 return m_dragFaces; 140 } 141 updateDragFaces(const Model::PickResult & pickResult)142 void ResizeBrushesTool::updateDragFaces(const Model::PickResult& pickResult) { 143 const Model::Hit& hit = pickResult.query().type(ResizeHit2D | ResizeHit3D).occluded().first(); 144 Model::BrushFaceList newDragFaces = getDragFaces(hit); 145 if (newDragFaces != m_dragFaces) 146 refreshViews(); 147 148 using std::swap; 149 swap(m_dragFaces, newDragFaces); 150 } 151 getDragFaces(const Model::Hit & hit) const152 Model::BrushFaceList ResizeBrushesTool::getDragFaces(const Model::Hit& hit) const { 153 return !hit.isMatch() ? Model::EmptyBrushFaceList : collectDragFaces(hit); 154 } 155 156 class ResizeBrushesTool::MatchFaceBoundary { 157 private: 158 const Model::BrushFace* m_reference; 159 public: MatchFaceBoundary(const Model::BrushFace * reference)160 MatchFaceBoundary(const Model::BrushFace* reference) : 161 m_reference(reference) { 162 assert(m_reference != NULL); 163 } 164 operator ()(Model::BrushFace * face) const165 bool operator()(Model::BrushFace* face) const { 166 return face != m_reference && face->boundary().equals(m_reference->boundary()); 167 } 168 }; 169 collectDragFaces(const Model::Hit & hit) const170 Model::BrushFaceList ResizeBrushesTool::collectDragFaces(const Model::Hit& hit) const { 171 assert(hit.isMatch()); 172 assert(hit.type() == ResizeHit2D || hit.type() == ResizeHit3D); 173 174 Model::BrushFaceList result; 175 if (hit.type() == ResizeHit2D) { 176 const Model::BrushFaceList& faces = hit.target<Model::BrushFaceList>(); 177 assert(!faces.empty()); 178 VectorUtils::append(result, faces); 179 VectorUtils::append(result, collectDragFaces(faces[0])); 180 if (faces.size() > 1) { 181 VectorUtils::append(result, collectDragFaces(faces[1])); 182 } 183 } else { 184 Model::BrushFace* face = hit.target<Model::BrushFace*>(); 185 result.push_back(face); 186 VectorUtils::append(result, collectDragFaces(face)); 187 } 188 189 return result; 190 } 191 collectDragFaces(Model::BrushFace * face) const192 Model::BrushFaceList ResizeBrushesTool::collectDragFaces(Model::BrushFace* face) const { 193 Model::CollectMatchingBrushFacesVisitor<MatchFaceBoundary> visitor((MatchFaceBoundary(face))); 194 195 MapDocumentSPtr document = lock(m_document); 196 const Model::NodeList& nodes = document->selectedNodes().nodes(); 197 Model::Node::accept(nodes.begin(), nodes.end(), visitor); 198 return visitor.faces(); 199 } 200 beginResize(const Model::PickResult & pickResult,const bool split)201 bool ResizeBrushesTool::beginResize(const Model::PickResult& pickResult, const bool split) { 202 const Model::Hit& hit = pickResult.query().type(ResizeHit2D | ResizeHit3D).occluded().first(); 203 if (!hit.isMatch()) 204 return false; 205 206 m_dragOrigin = hit.hitPoint(); 207 m_totalDelta = Vec3::Null; 208 m_splitBrushes = split; 209 210 MapDocumentSPtr document = lock(m_document); 211 document->beginTransaction("Resize Brushes"); 212 return true; 213 } 214 resize(const Ray3 & pickRay,const Renderer::Camera & camera)215 bool ResizeBrushesTool::resize(const Ray3& pickRay, const Renderer::Camera& camera) { 216 assert(!m_dragFaces.empty()); 217 218 Model::BrushFace* dragFace = m_dragFaces.front(); 219 const Vec3& faceNormal = dragFace->boundary().normal; 220 221 const Ray3::LineDistance distance = pickRay.distanceToLine(m_dragOrigin, faceNormal); 222 if (distance.parallel) 223 return true; 224 225 const FloatType dragDist = distance.lineDistance; 226 227 MapDocumentSPtr document = lock(m_document); 228 const View::Grid& grid = document->grid(); 229 const Vec3 relativeFaceDelta = grid.snap(dragDist) * faceNormal; 230 const Vec3 absoluteFaceDelta = grid.moveDelta(dragFace, faceNormal * dragDist); 231 232 const Vec3 faceDelta = selectDelta(relativeFaceDelta, absoluteFaceDelta, dragDist); 233 if (faceDelta.null()) 234 return true; 235 236 if (m_splitBrushes) { 237 if (splitBrushes(faceDelta)) { 238 m_totalDelta += faceDelta; 239 m_dragOrigin += faceDelta; 240 m_splitBrushes = false; 241 } 242 } else { 243 if (document->resizeBrushes(m_dragFaces, faceDelta)) { 244 m_totalDelta += faceDelta; 245 m_dragOrigin += faceDelta; 246 } 247 } 248 249 return true; 250 } 251 selectDelta(const Vec3 & relativeDelta,const Vec3 & absoluteDelta,const FloatType mouseDistance) const252 Vec3 ResizeBrushesTool::selectDelta(const Vec3& relativeDelta, const Vec3& absoluteDelta, const FloatType mouseDistance) const { 253 // select the delta that is closest to the actual delta indicated by the mouse cursor 254 const FloatType mouseDistance2 = mouseDistance * mouseDistance; 255 return (std::abs(relativeDelta.squaredLength() - mouseDistance2) < 256 std::abs(absoluteDelta.squaredLength() - mouseDistance2) ? 257 relativeDelta : 258 absoluteDelta); 259 } 260 commitResize()261 void ResizeBrushesTool::commitResize() { 262 MapDocumentSPtr document = lock(m_document); 263 if (m_totalDelta.null()) 264 document->cancelTransaction(); 265 else 266 document->commitTransaction(); 267 m_dragFaces.clear(); 268 } 269 cancelResize()270 void ResizeBrushesTool::cancelResize() { 271 MapDocumentSPtr document = lock(m_document); 272 document->cancelTransaction(); 273 m_dragFaces.clear(); 274 } 275 splitBrushes(const Vec3 & delta)276 bool ResizeBrushesTool::splitBrushes(const Vec3& delta) { 277 MapDocumentSPtr document = lock(m_document); 278 const BBox3& worldBounds = document->worldBounds(); 279 const bool lockTextures = document->textureLock(); 280 281 Model::BrushFaceList::const_iterator fIt, fEnd; 282 283 // first ensure that the drag can be applied at all 284 for (fIt = m_dragFaces.begin(), fEnd = m_dragFaces.end(); fIt != fEnd; ++fIt) { 285 const Model::BrushFace* face = *fIt; 286 if (!Math::pos(face->boundary().normal.dot(delta))) 287 return false; 288 } 289 290 Model::ParentChildrenMap newNodes; 291 Model::BrushFaceList newDragFaces; 292 for (fIt = m_dragFaces.begin(), m_dragFaces.end(); fIt != fEnd; ++fIt) { 293 Model::BrushFace* dragFace = *fIt; 294 Model::Brush* brush = dragFace->brush(); 295 296 Model::Brush* newBrush = brush->clone(worldBounds); 297 Model::BrushFace* newDragFace = findMatchingFace(newBrush, dragFace); 298 Model::BrushFace* clipFace = newDragFace->clone(); 299 clipFace->invert(); 300 301 newBrush->moveBoundary(worldBounds, newDragFace, delta, lockTextures); 302 const bool clipResult = newBrush->clip(worldBounds, clipFace); 303 assert(clipResult); 304 unused(clipResult); 305 306 newNodes[brush->parent()].push_back(newBrush); 307 newDragFaces.push_back(newDragFace); 308 } 309 310 document->deselectAll(); 311 const Model::NodeList addedNodes = document->addNodes(newNodes); 312 document->select(addedNodes); 313 m_dragFaces = newDragFaces; 314 315 return true; 316 } 317 findMatchingFace(Model::Brush * brush,const Model::BrushFace * reference) const318 Model::BrushFace* ResizeBrushesTool::findMatchingFace(Model::Brush* brush, const Model::BrushFace* reference) const { 319 Model::FindMatchingBrushFaceVisitor<MatchFaceBoundary> visitor((MatchFaceBoundary(reference))); 320 visitor.visit(brush); 321 if (!visitor.hasResult()) 322 return NULL; 323 return visitor.result(); 324 } 325 } 326 } 327