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