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 "UVOriginTool.h"
21 #include "PreferenceManager.h"
22 #include "Preferences.h"
23 #include "Assets/Texture.h"
24 #include "Model/BrushFace.h"
25 #include "Model/BrushGeometry.h"
26 #include "Model/HitQuery.h"
27 #include "Model/ModelTypes.h"
28 #include "Model/PickResult.h"
29 #include "Renderer/Circle.h"
30 #include "Renderer/EdgeRenderer.h"
31 #include "Renderer/Renderable.h"
32 #include "Renderer/RenderBatch.h"
33 #include "Renderer/RenderContext.h"
34 #include "Renderer/Shaders.h"
35 #include "Renderer/ShaderManager.h"
36 #include "Renderer/Transformation.h"
37 #include "Renderer/VertexSpec.h"
38 #include "View/InputState.h"
39 #include "View/UVViewHelper.h"
40 
41 namespace TrenchBroom {
42     namespace View {
43         const Model::Hit::HitType UVOriginTool::XHandleHit = Model::Hit::freeHitType();
44         const Model::Hit::HitType UVOriginTool::YHandleHit = Model::Hit::freeHitType();
45         const FloatType UVOriginTool::MaxPickDistance = 5.0;
46         const float UVOriginTool::OriginHandleRadius =  5.0f;
47 
UVOriginTool(UVViewHelper & helper)48         UVOriginTool::UVOriginTool(UVViewHelper& helper) :
49         ToolControllerBase(),
50         Tool(true),
51         m_helper(helper) {}
52 
doGetTool()53         Tool* UVOriginTool::doGetTool() {
54             return this;
55         }
56 
doPick(const InputState & inputState,Model::PickResult & pickResult)57         void UVOriginTool::doPick(const InputState& inputState, Model::PickResult& pickResult) {
58             if (m_helper.valid()) {
59                 Line3 xHandle, yHandle;
60                 computeOriginHandles(xHandle, yHandle);
61 
62                 const Model::BrushFace* face = m_helper.face();
63                 const Mat4x4 fromTex = face->fromTexCoordSystemMatrix(Vec2f::Null, Vec2f::One, true);
64                 const Vec3 origin = fromTex * Vec3(m_helper.originInFaceCoords());
65 
66                 const Ray3& pickRay = inputState.pickRay();
67                 const Ray3::PointDistance oDistance = pickRay.distanceToPoint(origin);
68                 if (oDistance.distance <= OriginHandleRadius / m_helper.cameraZoom()) {
69                     const Vec3 hitPoint = pickRay.pointAtDistance(oDistance.rayDistance);
70                     pickResult.addHit(Model::Hit(XHandleHit, oDistance.rayDistance, hitPoint, xHandle, oDistance.distance));
71                     pickResult.addHit(Model::Hit(YHandleHit, oDistance.rayDistance, hitPoint, xHandle, oDistance.distance));
72                 } else {
73                     const Ray3::LineDistance xDistance = pickRay.distanceToLine(xHandle.point, xHandle.direction);
74                     const Ray3::LineDistance yDistance = pickRay.distanceToLine(yHandle.point, yHandle.direction);
75 
76                     assert(!xDistance.parallel);
77                     assert(!yDistance.parallel);
78 
79                     const FloatType maxDistance  = MaxPickDistance / m_helper.cameraZoom();
80                     if (xDistance.distance <= maxDistance) {
81                         const Vec3 hitPoint = pickRay.pointAtDistance(xDistance.rayDistance);
82                         pickResult.addHit(Model::Hit(XHandleHit, xDistance.rayDistance, hitPoint, xHandle, xDistance.distance));
83                     }
84 
85                     if (yDistance.distance <= maxDistance) {
86                         const Vec3 hitPoint = pickRay.pointAtDistance(yDistance.rayDistance);
87                         pickResult.addHit(Model::Hit(YHandleHit, yDistance.rayDistance, hitPoint, yHandle, yDistance.distance));
88                     }
89                 }
90             }
91         }
92 
computeOriginHandles(Line3 & xHandle,Line3 & yHandle) const93         void UVOriginTool::computeOriginHandles(Line3& xHandle, Line3& yHandle) const {
94             const Model::BrushFace* face = m_helper.face();
95             const Mat4x4 toWorld = face->fromTexCoordSystemMatrix(Vec2f::Null, Vec2f::One, true);
96 
97             const Vec3 origin = m_helper.originInFaceCoords();
98             xHandle.point = yHandle.point = toWorld * origin;
99 
100             xHandle.direction = (toWorld * (origin + Vec3::PosY) - xHandle.point).normalized();
101             yHandle.direction = (toWorld * (origin + Vec3::PosX) - yHandle.point);
102         }
103 
doStartMouseDrag(const InputState & inputState)104         bool UVOriginTool::doStartMouseDrag(const InputState& inputState) {
105             assert(m_helper.valid());
106 
107             if (!inputState.modifierKeysPressed(ModifierKeys::MKNone) ||
108                 !inputState.mouseButtonsPressed(MouseButtons::MBLeft))
109                 return false;
110 
111             const Model::PickResult& pickResult = inputState.pickResult();
112             const Model::Hit& xHandleHit = pickResult.query().type(XHandleHit).occluded().first();
113             const Model::Hit& yHandleHit = pickResult.query().type(YHandleHit).occluded().first();
114 
115             if (!xHandleHit.isMatch() && !yHandleHit.isMatch())
116                 return false;
117 
118             if (xHandleHit.isMatch())
119                 m_selector[0] = 1.0f;
120             else
121                 m_selector[0] = 0.0f;
122 
123             if (yHandleHit.isMatch())
124                 m_selector[1] = 1.0f;
125             else
126                 m_selector[1] = 0.0f;
127 
128             m_lastPoint = computeHitPoint(inputState.pickRay());
129             return true;
130         }
131 
doMouseDrag(const InputState & inputState)132         bool UVOriginTool::doMouseDrag(const InputState& inputState) {
133             const Vec2f curPoint = computeHitPoint(inputState.pickRay());
134             const Vec2f delta = curPoint - m_lastPoint;
135 
136             const Vec2f snapped = snapDelta(delta * m_selector);
137             if (snapped.null())
138                 return true;
139 
140             m_helper.setOrigin(m_helper.originInFaceCoords() + snapped);
141             m_lastPoint += snapped;
142 
143             return true;
144         }
145 
computeHitPoint(const Ray3 & ray) const146         Vec2f UVOriginTool::computeHitPoint(const Ray3& ray) const {
147             const Model::BrushFace* face = m_helper.face();
148             const Plane3& boundary = face->boundary();
149             const FloatType distance = boundary.intersectWithRay(ray);
150             const Vec3 hitPoint = ray.pointAtDistance(distance);
151 
152             const Mat4x4 transform = face->toTexCoordSystemMatrix(Vec2f::Null, Vec2f::One, true);
153             return Vec2f(transform * hitPoint);
154         }
155 
snapDelta(const Vec2f & delta) const156         Vec2f UVOriginTool::snapDelta(const Vec2f& delta) const {
157             if (delta.null())
158                 return delta;
159 
160             const Model::BrushFace* face = m_helper.face();
161             assert(face != NULL);
162 
163             // The delta is given in non-translated and non-scaled texture coordinates because that's how the origin
164             // is stored. We have to convert to translated and scaled texture coordinates to do our snapping because
165             // that's how the helper computes the distance to the texture grid.
166             // Finally, we will convert the distance back to non-translated and non-scaled texture coordinates and
167             // snap the delta to the distance.
168 
169             const Mat4x4 w2fTransform = face->toTexCoordSystemMatrix(Vec2f::Null, Vec2f::One, true);
170             const Mat4x4 w2tTransform = face->toTexCoordSystemMatrix(face->offset(), face->scale(), true);
171             const Mat4x4 f2wTransform = face->fromTexCoordSystemMatrix(Vec2f::Null, Vec2f::One, true);
172             const Mat4x4 t2wTransform = face->fromTexCoordSystemMatrix(face->offset(), face->scale(), true);
173             const Mat4x4 f2tTransform = w2tTransform * f2wTransform;
174             const Mat4x4 t2fTransform = w2fTransform * t2wTransform;
175 
176             const Vec2f newOriginInFaceCoords = m_helper.originInFaceCoords() + delta;
177             const Vec2f newOriginInTexCoords  = Vec2f(f2tTransform * Vec3(newOriginInFaceCoords));
178 
179             // now snap to the vertices
180             // TODO: this actually doesn't work because we're snapping to the X or Y coordinate of the vertices
181             // instead, we must snap to the edges!
182             const Model::BrushFace::VertexList vertices = face->vertices();
183             Model::BrushFace::VertexList::const_iterator it, end;
184 
185             Vec2f distanceInTexCoords = Vec2f::Max;
186             for (it = vertices.begin(), end = vertices.end(); it != end; ++it)
187                 distanceInTexCoords = absMin(distanceInTexCoords, Vec2f(w2tTransform * (*it)->position()) - newOriginInTexCoords);
188 
189             // and to the texture grid
190             const Assets::Texture* texture = face->texture();
191             if (texture != NULL)
192                 distanceInTexCoords = absMin(distanceInTexCoords, m_helper.computeDistanceFromTextureGrid(Vec3(newOriginInTexCoords)));
193 
194             // finally snap to the face center
195             const Vec2f faceCenter(w2tTransform * face->boundsCenter());
196             distanceInTexCoords = absMin(distanceInTexCoords, faceCenter - newOriginInTexCoords);
197 
198             // now we have a distance in the scaled and translated texture coordinate system
199             // so we transform the new position plus distance back to the unscaled and untranslated texture coordinate system
200             // and take the actual distance
201             const Vec2f distanceInFaceCoords = newOriginInFaceCoords - Vec2f(t2fTransform * Vec3(newOriginInTexCoords + distanceInTexCoords));
202             return m_helper.snapDelta(delta, -distanceInFaceCoords);
203         }
204 
doEndMouseDrag(const InputState & inputState)205         void UVOriginTool::doEndMouseDrag(const InputState& inputState) {}
doCancelMouseDrag()206         void UVOriginTool::doCancelMouseDrag() {}
207 
doRender(const InputState & inputState,Renderer::RenderContext & renderContext,Renderer::RenderBatch & renderBatch)208         void UVOriginTool::doRender(const InputState& inputState, Renderer::RenderContext& renderContext, Renderer::RenderBatch& renderBatch) {
209             if (!m_helper.valid())
210                 return;
211 
212             renderLineHandles(inputState, renderContext, renderBatch);
213             renderOriginHandle(inputState, renderContext, renderBatch);
214         }
215 
renderLineHandles(const InputState & inputState,Renderer::RenderContext & renderContext,Renderer::RenderBatch & renderBatch)216         void UVOriginTool::renderLineHandles(const InputState& inputState, Renderer::RenderContext& renderContext, Renderer::RenderBatch& renderBatch) {
217             EdgeVertex::List vertices = getHandleVertices(inputState);
218 
219             Renderer::DirectEdgeRenderer edgeRenderer(Renderer::VertexArray::swap(vertices), GL_LINES);
220             edgeRenderer.renderOnTop(renderBatch, 0.25f);
221         }
222 
getHandleVertices(const InputState & inputState) const223         UVOriginTool::EdgeVertex::List UVOriginTool::getHandleVertices(const InputState& inputState) const {
224             const Model::PickResult& pickResult = inputState.pickResult();
225             const Model::Hit& xHandleHit = pickResult.query().type(XHandleHit).occluded().first();
226             const Model::Hit& yHandleHit = pickResult.query().type(YHandleHit).occluded().first();
227 
228             const bool highlightXHandle = (thisToolDragging() && m_selector.x() > 0.0) || (!thisToolDragging() && xHandleHit.isMatch());
229             const bool highlightYHandle = (thisToolDragging() && m_selector.y() > 0.0) || (!thisToolDragging() && yHandleHit.isMatch());
230 
231             const Color xColor = highlightXHandle ? Color(1.0f, 0.0f, 0.0f, 1.0f) : Color(0.7f, 0.0f, 0.0f, 1.0f);
232             const Color yColor = highlightYHandle ? Color(1.0f, 0.0f, 0.0f, 1.0f) : Color(0.7f, 0.0f, 0.0f, 1.0f);
233 
234             Vec3 x1, x2, y1, y2;
235             m_helper.computeOriginHandleVertices(x1, x2, y1, y2);
236 
237             EdgeVertex::List vertices(4);
238             vertices[0] = EdgeVertex(Vec3f(x1), xColor);
239             vertices[1] = EdgeVertex(Vec3f(x2), xColor);
240             vertices[2] = EdgeVertex(Vec3f(y1), yColor);
241             vertices[3] = EdgeVertex(Vec3f(y2), yColor);
242             return vertices;
243         }
244 
245         class UVOriginTool::RenderOrigin : public Renderer::DirectRenderable {
246         private:
247             const UVViewHelper& m_helper;
248             bool m_highlight;
249             Renderer::Circle m_originHandle;
250         public:
RenderOrigin(const UVViewHelper & helper,const float originRadius,const bool highlight)251             RenderOrigin(const UVViewHelper& helper, const float originRadius, const bool highlight) :
252             m_helper(helper),
253             m_highlight(highlight),
254             m_originHandle(makeCircle(m_helper, originRadius, 16, true)) {}
255         private:
makeCircle(const UVViewHelper & helper,const float radius,const size_t segments,const bool fill)256             static Renderer::Circle makeCircle(const UVViewHelper& helper, const float radius, const size_t segments, const bool fill) {
257                 const float zoom = helper.cameraZoom();
258                 return Renderer::Circle(radius / zoom, segments, fill);
259             }
260         private:
doPrepareVertices(Renderer::Vbo & vertexVbo)261             void doPrepareVertices(Renderer::Vbo& vertexVbo) {
262                 m_originHandle.prepare(vertexVbo);
263             }
264 
doRender(Renderer::RenderContext & renderContext)265             void doRender(Renderer::RenderContext& renderContext) {
266                 const Model::BrushFace* face = m_helper.face();
267                 const Mat4x4 fromFace = face->fromTexCoordSystemMatrix(Vec2f::Null, Vec2f::One, true);
268 
269                 const Plane3& boundary = face->boundary();
270                 const Mat4x4 toPlane = planeProjectionMatrix(boundary.distance, boundary.normal);
271                 const Mat4x4 fromPlane = invertedMatrix(toPlane);
272                 const Vec2f originPosition(toPlane * fromFace * Vec3(m_helper.originInFaceCoords()));
273 
274                 const Color& handleColor = pref(Preferences::HandleColor);
275                 const Color& highlightColor = pref(Preferences::SelectedHandleColor);
276 
277                 const Renderer::MultiplyModelMatrix toWorldTransform(renderContext.transformation(), fromPlane);
278                 const Mat4x4 translation = translationMatrix(Vec3(originPosition));
279                 const Renderer::MultiplyModelMatrix centerTransform(renderContext.transformation(), translation);
280 
281                 Renderer::ActiveShader shader(renderContext.shaderManager(), Renderer::Shaders::VaryingPUniformCShader);
282                 shader.set("Color", m_highlight ? highlightColor : handleColor);
283                 m_originHandle.render();
284             }
285         };
286 
renderOriginHandle(const InputState & inputState,Renderer::RenderContext & renderContext,Renderer::RenderBatch & renderBatch)287         void UVOriginTool::renderOriginHandle(const InputState& inputState, Renderer::RenderContext& renderContext, Renderer::RenderBatch& renderBatch) {
288             const Model::PickResult& pickResult = inputState.pickResult();
289             const Model::Hit& xHandleHit = pickResult.query().type(XHandleHit).occluded().first();
290             const Model::Hit& yHandleHit = pickResult.query().type(YHandleHit).occluded().first();
291 
292             const bool highlight = xHandleHit.isMatch() && yHandleHit.isMatch();;
293             renderBatch.addOneShot(new RenderOrigin(m_helper, OriginHandleRadius, highlight));
294         }
295 
doCancel()296         bool UVOriginTool::doCancel() {
297             return false;
298         }
299     }
300 }
301