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