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 "RotateObjectsToolController.h"
21 
22 #include "PreferenceManager.h"
23 #include "Preferences.h"
24 #include "Renderer/Camera.h"
25 #include "Renderer/RenderBatch.h"
26 #include "Renderer/RenderContext.h"
27 #include "Renderer/RenderService.h"
28 #include "Renderer/ShaderManager.h"
29 #include "Renderer/Shaders.h"
30 #include "View/RotateObjectsTool.h"
31 #include "View/InputState.h"
32 #include "View/MoveToolController.h"
33 
34 namespace TrenchBroom {
35     namespace View {
36         class RotateObjectsToolController::RotateObjectsBase : public ToolControllerBase<NoPickingPolicy, NoKeyPolicy, MousePolicy, RestrictedDragPolicy, RenderPolicy, NoDropPolicy> {
37         protected:
38             RotateObjectsTool* m_tool;
39         private:
40             RotateObjectsHandle::HitArea m_area;
41             Vec3 m_center;
42             Vec3 m_start;
43             Vec3 m_axis;
44             FloatType m_angle;
45         protected:
RotateObjectsBase(RotateObjectsTool * tool)46             RotateObjectsBase(RotateObjectsTool* tool) :
47             m_tool(tool) {
48                 assert(m_tool != NULL);
49             }
50         private:
doGetTool()51             Tool* doGetTool() {
52                 return m_tool;
53             }
54 
doMouseClick(const InputState & inputState)55             bool doMouseClick(const InputState& inputState) {
56                 if (!inputState.mouseButtonsPressed(MouseButtons::MBLeft))
57                     return false;
58 
59                 const Model::Hit& hit = inputState.pickResult().query().type(RotateObjectsHandle::HandleHit).occluded().first();
60                 if (!hit.isMatch())
61                     return false;
62 
63                 const RotateObjectsHandle::HitArea area = hit.target<RotateObjectsHandle::HitArea>();
64                 if (area == RotateObjectsHandle::HitArea_Center)
65                     return false;
66 
67                 m_tool->updateToolPageAxis(area);
68                 return true;
69             }
70 
doStartDrag(const InputState & inputState)71             DragInfo doStartDrag(const InputState& inputState) {
72                 if (inputState.mouseButtons() != MouseButtons::MBLeft ||
73                     inputState.modifierKeys() != ModifierKeys::MKNone)
74                     return DragInfo();
75 
76                 const Model::Hit& hit = inputState.pickResult().query().type(RotateObjectsHandle::HandleHit).occluded().first();
77                 if (!hit.isMatch())
78                     return DragInfo();
79 
80                 const RotateObjectsHandle::HitArea area = hit.target<RotateObjectsHandle::HitArea>();
81                 if (area == RotateObjectsHandle::HitArea_Center)
82                     return DragInfo();
83 
84                 m_tool->beginRotation();
85 
86                 m_area = hit.target<RotateObjectsHandle::HitArea>();
87                 m_center = m_tool->rotationCenter();
88                 m_start = m_tool->rotationAxisHandle(m_area, inputState.camera().position());
89                 m_axis = m_tool->rotationAxis(m_area);
90                 m_angle = 0.0;
91                 const FloatType radius = m_tool->handleRadius();
92                 return DragInfo(new CircleDragRestricter(m_center, m_axis, radius), new CircleDragSnapper(m_tool->grid(), m_start, m_center, m_axis, radius));
93             }
94 
doDrag(const InputState & inputState,const Vec3 & lastPoint,const Vec3 & curPoint)95             DragResult doDrag(const InputState& inputState, const Vec3& lastPoint, const Vec3& curPoint) {
96                 const Vec3 ref = (m_start - m_center).normalized();
97                 const Vec3 vec = (curPoint - m_center).normalized();
98                 m_angle = angleBetween(vec, ref, m_axis);
99                 m_tool->applyRotation(m_center, m_axis, m_angle);
100                 return DR_Continue;
101             }
102 
doEndDrag(const InputState & inputState)103             void doEndDrag(const InputState& inputState) {
104                 m_tool->commitRotation();
105             }
106 
doCancelDrag()107             void doCancelDrag() {
108                 m_tool->cancelRotation();
109             }
110 
doRender(const InputState & inputState,Renderer::RenderContext & renderContext,Renderer::RenderBatch & renderBatch)111             void doRender(const InputState& inputState, Renderer::RenderContext& renderContext, Renderer::RenderBatch& renderBatch) {
112                 if (thisToolDragging()) {
113                     doRenderHighlight(inputState, renderContext, renderBatch, m_area);
114                     renderAngleIndicator(renderContext, renderBatch);
115                     renderAngleText(renderContext, renderBatch);
116                 } else {
117                     const Model::Hit& hit = inputState.pickResult().query().type(RotateObjectsHandle::HandleHit).occluded().first();
118                     if (hit.isMatch()) {
119                         const RotateObjectsHandle::HitArea area = hit.target<RotateObjectsHandle::HitArea>();
120                         if (area != RotateObjectsHandle::HitArea_Center)
121                             doRenderHighlight(inputState, renderContext, renderBatch, hit.target<RotateObjectsHandle::HitArea>());
122                     }
123                 }
124             }
125 
126             class AngleIndicatorRenderer : public Renderer::DirectRenderable {
127             private:
128                 Vec3 m_position;
129                 Renderer::Circle m_circle;
130             public:
AngleIndicatorRenderer(const Vec3 & position,const float radius,const Math::Axis::Type axis,const Vec3 & startAxis,const Vec3 & endAxis)131                 AngleIndicatorRenderer(const Vec3& position, const float radius, const Math::Axis::Type axis, const Vec3& startAxis, const Vec3& endAxis) :
132                 m_position(position),
133                 m_circle(radius, 24, true, axis, startAxis, endAxis) {}
134             private:
doPrepareVertices(Renderer::Vbo & vertexVbo)135                 void doPrepareVertices(Renderer::Vbo& vertexVbo) {
136                     m_circle.prepare(vertexVbo);
137                 }
138 
doRender(Renderer::RenderContext & renderContext)139                 void doRender(Renderer::RenderContext& renderContext) {
140                     glAssert(glDisable(GL_DEPTH_TEST));
141                     glAssert(glDisable(GL_CULL_FACE));
142                     glAssert(glPolygonMode(GL_FRONT_AND_BACK, GL_FILL));
143 
144                     Renderer::MultiplyModelMatrix translation(renderContext.transformation(), translationMatrix(m_position));
145                     Renderer::ActiveShader shader(renderContext.shaderManager(), Renderer::Shaders::VaryingPUniformCShader);
146                     shader.set("Color", Color(1.0f, 1.0f, 1.0f, 0.2f));
147                     m_circle.render();
148 
149                     glAssert(glPolygonMode(GL_FRONT, GL_FILL));
150                     glAssert(glEnable(GL_CULL_FACE));
151                     glAssert(glEnable(GL_DEPTH_TEST));
152                 }
153             };
154 
renderAngleIndicator(Renderer::RenderContext & renderContext,Renderer::RenderBatch & renderBatch)155             void renderAngleIndicator(Renderer::RenderContext& renderContext, Renderer::RenderBatch& renderBatch) {
156                 PreferenceManager& prefs = PreferenceManager::instance();
157                 const float handleRadius = static_cast<float>(prefs.get(Preferences::RotateHandleRadius));
158                 const Vec3 startAxis = (m_start - m_center).normalized();
159                 const Vec3 endAxis = Quat3(m_axis, m_angle) * startAxis;
160 
161                 renderBatch.addOneShot(new AngleIndicatorRenderer(m_center, handleRadius, m_axis.firstComponent(), startAxis, endAxis));
162             }
163 
renderAngleText(Renderer::RenderContext & renderContext,Renderer::RenderBatch & renderBatch)164             void renderAngleText(Renderer::RenderContext& renderContext, Renderer::RenderBatch& renderBatch) {
165                 Renderer::RenderService renderService(renderContext, renderBatch);
166 
167                 renderService.setForegroundColor(pref(Preferences::SelectedInfoOverlayTextColor));
168                 renderService.setBackgroundColor(pref(Preferences::SelectedInfoOverlayBackgroundColor));
169                 renderService.renderString(angleString(Math::degrees(m_angle)), m_center);
170             }
171 
angleString(const FloatType angle) const172             String angleString(const FloatType angle) const {
173                 StringStream str;
174                 str.precision(2);
175                 str.setf(std::ios::fixed);
176                 str << angle;
177                 return str.str();
178             }
179 
doCancel()180             bool doCancel() {
181                 return false;
182             }
183         private:
184             virtual void doRenderHighlight(const InputState& inputState, Renderer::RenderContext& renderContext, Renderer::RenderBatch& renderBatch, RotateObjectsHandle::HitArea area) = 0;
185         };
186 
187         class RotateObjectsToolController::MoveCenterBase : public MoveToolController<NoPickingPolicy, NoMousePolicy> {
188         protected:
189             RotateObjectsTool* m_tool;
190         protected:
MoveCenterBase(RotateObjectsTool * tool)191             MoveCenterBase(RotateObjectsTool* tool) :
192             MoveToolController(tool->grid()),
193             m_tool(tool) {
194                 assert(m_tool != NULL);
195             }
196 
doGetTool()197             Tool* doGetTool() {
198                 return m_tool;
199             }
200 
doStartMove(const InputState & inputState)201             MoveInfo doStartMove(const InputState& inputState) {
202                 if (inputState.mouseButtons() != MouseButtons::MBLeft ||
203                     inputState.modifierKeys() != ModifierKeys::MKNone)
204                     return MoveInfo();
205 
206                 const Model::Hit& hit = inputState.pickResult().query().type(RotateObjectsHandle::HandleHit).occluded().first();
207                 if (!hit.isMatch())
208                     return MoveInfo();
209 
210                 if (hit.target<RotateObjectsHandle::HitArea>() != RotateObjectsHandle::HitArea_Center)
211                     return MoveInfo();
212 
213                 return MoveInfo(m_tool->rotationCenter());
214             }
215 
doMove(const InputState & inputState,const Vec3 & lastPoint,const Vec3 & curPoint)216             DragResult doMove(const InputState& inputState, const Vec3& lastPoint, const Vec3& curPoint) {
217                 m_tool->setRotationCenter(curPoint);
218                 return DR_Continue;
219             }
220 
doEndMove(const InputState & inputState)221             void doEndMove(const InputState& inputState) {}
222 
doCancelMove()223             void doCancelMove() {
224                 m_tool->setRotationCenter(dragOrigin());
225             }
226 
doRender(const InputState & inputState,Renderer::RenderContext & renderContext,Renderer::RenderBatch & renderBatch)227             void doRender(const InputState& inputState, Renderer::RenderContext& renderContext, Renderer::RenderBatch& renderBatch) {
228                 MoveToolController::doRender(inputState, renderContext, renderBatch);
229                 if (thisToolDragging()) {
230                     doRenderHighlight(inputState, renderContext, renderBatch, RotateObjectsHandle::HitArea_Center);
231                 } else if (!anyToolDragging(inputState)) {
232                     const Model::Hit& hit = inputState.pickResult().query().type(RotateObjectsHandle::HandleHit).occluded().first();
233                     if (hit.isMatch() && hit.target<RotateObjectsHandle::HitArea>() == RotateObjectsHandle::HitArea_Center)
234                         doRenderHighlight(inputState, renderContext, renderBatch, RotateObjectsHandle::HitArea_Center);
235                 }
236             }
237 
doCancel()238             bool doCancel() {
239                 return false;
240             }
241         private:
242             virtual void doRenderHighlight(const InputState& inputState, Renderer::RenderContext& renderContext, Renderer::RenderBatch& renderBatch, RotateObjectsHandle::HitArea area) = 0;
243         };
244 
RotateObjectsToolController(RotateObjectsTool * tool)245         RotateObjectsToolController::RotateObjectsToolController(RotateObjectsTool* tool) :
246         m_tool(tool) {}
247 
~RotateObjectsToolController()248         RotateObjectsToolController::~RotateObjectsToolController() {}
249 
doGetTool()250         Tool* RotateObjectsToolController::doGetTool() {
251             return m_tool;
252         }
253 
doPick(const InputState & inputState,Model::PickResult & pickResult)254         void RotateObjectsToolController::doPick(const InputState& inputState, Model::PickResult& pickResult) {
255             const Model::Hit hit = doPick(inputState);
256             if (hit.isMatch())
257                 pickResult.addHit(hit);
258         }
259 
doSetRenderOptions(const InputState & inputState,Renderer::RenderContext & renderContext) const260         void RotateObjectsToolController::doSetRenderOptions(const InputState& inputState, Renderer::RenderContext& renderContext) const {
261             const Model::Hit& hit = inputState.pickResult().query().type(RotateObjectsHandle::HandleHit).occluded().first();
262             if (thisToolDragging() || hit.isMatch())
263                 renderContext.setForceShowSelectionGuide();
264         }
265 
doRender(const InputState & inputState,Renderer::RenderContext & renderContext,Renderer::RenderBatch & renderBatch)266         void RotateObjectsToolController::doRender(const InputState& inputState, Renderer::RenderContext& renderContext, Renderer::RenderBatch& renderBatch) {
267             doRenderHandle(renderContext, renderBatch);
268             ToolControllerGroup::doRender(inputState, renderContext, renderBatch);
269         }
270 
doCancel()271         bool RotateObjectsToolController::doCancel() {
272             return false;
273         }
274 
275         class RotateObjectsToolController2D::MoveCenterPart : public MoveCenterBase {
276         public:
MoveCenterPart(RotateObjectsTool * tool)277             MoveCenterPart(RotateObjectsTool* tool) :
278             MoveCenterBase(tool) {}
279         private:
doRenderHighlight(const InputState & inputState,Renderer::RenderContext & renderContext,Renderer::RenderBatch & renderBatch,RotateObjectsHandle::HitArea area)280             void doRenderHighlight(const InputState& inputState, Renderer::RenderContext& renderContext, Renderer::RenderBatch& renderBatch, RotateObjectsHandle::HitArea area) {
281                 m_tool->renderHighlight2D(renderContext, renderBatch, area);
282             }
283         };
284 
285         class RotateObjectsToolController2D::RotateObjectsPart : public RotateObjectsBase {
286         public:
RotateObjectsPart(RotateObjectsTool * tool)287             RotateObjectsPart(RotateObjectsTool* tool) :
288             RotateObjectsBase(tool) {}
289         private:
doRenderHighlight(const InputState & inputState,Renderer::RenderContext & renderContext,Renderer::RenderBatch & renderBatch,RotateObjectsHandle::HitArea area)290             void doRenderHighlight(const InputState& inputState, Renderer::RenderContext& renderContext, Renderer::RenderBatch& renderBatch, RotateObjectsHandle::HitArea area) {
291                 m_tool->renderHighlight2D(renderContext, renderBatch, area);
292             }
293         };
294 
RotateObjectsToolController2D(RotateObjectsTool * tool)295         RotateObjectsToolController2D::RotateObjectsToolController2D(RotateObjectsTool* tool) :
296         RotateObjectsToolController(tool) {
297             addController(new MoveCenterPart(tool));
298             addController(new RotateObjectsPart(tool));
299         }
300 
doPick(const InputState & inputState)301         Model::Hit RotateObjectsToolController2D::doPick(const InputState& inputState) {
302             return m_tool->pick2D(inputState.pickRay(), inputState.camera());
303         }
304 
doRenderHandle(Renderer::RenderContext & renderContext,Renderer::RenderBatch & renderBatch)305         void RotateObjectsToolController2D::doRenderHandle(Renderer::RenderContext& renderContext, Renderer::RenderBatch& renderBatch) {
306             m_tool->renderHandle2D(renderContext, renderBatch);
307         }
308 
309 
310         class RotateObjectsToolController3D::MoveCenterPart : public MoveCenterBase {
311         public:
MoveCenterPart(RotateObjectsTool * tool)312             MoveCenterPart(RotateObjectsTool* tool) :
313             MoveCenterBase(tool) {}
314         private:
doRenderHighlight(const InputState & inputState,Renderer::RenderContext & renderContext,Renderer::RenderBatch & renderBatch,RotateObjectsHandle::HitArea area)315             void doRenderHighlight(const InputState& inputState, Renderer::RenderContext& renderContext, Renderer::RenderBatch& renderBatch, RotateObjectsHandle::HitArea area) {
316                 m_tool->renderHighlight3D(renderContext, renderBatch, area);
317             }
318         };
319 
320         class RotateObjectsToolController3D::RotateObjectsPart : public RotateObjectsBase {
321         public:
RotateObjectsPart(RotateObjectsTool * tool)322             RotateObjectsPart(RotateObjectsTool* tool) :
323             RotateObjectsBase(tool) {}
324         private:
doRenderHighlight(const InputState & inputState,Renderer::RenderContext & renderContext,Renderer::RenderBatch & renderBatch,RotateObjectsHandle::HitArea area)325             void doRenderHighlight(const InputState& inputState, Renderer::RenderContext& renderContext, Renderer::RenderBatch& renderBatch, RotateObjectsHandle::HitArea area) {
326                 m_tool->renderHighlight3D(renderContext, renderBatch, area);
327             }
328         };
329 
RotateObjectsToolController3D(RotateObjectsTool * tool)330         RotateObjectsToolController3D::RotateObjectsToolController3D(RotateObjectsTool* tool) :
331         RotateObjectsToolController(tool) {
332             addController(new MoveCenterPart(tool));
333             addController(new RotateObjectsPart(tool));
334         }
335 
doPick(const InputState & inputState)336         Model::Hit RotateObjectsToolController3D::doPick(const InputState& inputState) {
337             return m_tool->pick3D(inputState.pickRay(), inputState.camera());
338         }
339 
doRenderHandle(Renderer::RenderContext & renderContext,Renderer::RenderBatch & renderBatch)340         void RotateObjectsToolController3D::doRenderHandle(Renderer::RenderContext& renderContext, Renderer::RenderBatch& renderBatch) {
341             m_tool->renderHandle3D(renderContext, renderBatch);
342         }
343     }
344 }
345