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 "ParaxialTexCoordSystem.h"
21 
22 #include "Assets/Texture.h"
23 #include "Model/BrushFace.h"
24 
25 namespace TrenchBroom {
26     namespace Model {
27         const Vec3 ParaxialTexCoordSystem::BaseAxes[] = {
28             Vec3( 0.0,  0.0,  1.0), Vec3( 1.0,  0.0,  0.0), Vec3( 0.0, -1.0,  0.0),
29             Vec3( 0.0,  0.0, -1.0), Vec3( 1.0,  0.0,  0.0), Vec3( 0.0, -1.0,  0.0),
30             Vec3( 1.0,  0.0,  0.0), Vec3( 0.0,  1.0,  0.0), Vec3( 0.0,  0.0, -1.0),
31             Vec3(-1.0,  0.0,  0.0), Vec3( 0.0,  1.0,  0.0), Vec3( 0.0,  0.0, -1.0),
32             Vec3( 0.0,  1.0,  0.0), Vec3( 1.0,  0.0,  0.0), Vec3( 0.0,  0.0, -1.0),
33             Vec3( 0.0, -1.0,  0.0), Vec3( 1.0,  0.0,  0.0), Vec3( 0.0,  0.0, -1.0),
34         };
35 
ParaxialTexCoordSystem(const Vec3 & point0,const Vec3 & point1,const Vec3 & point2,const BrushFaceAttributes & attribs)36         ParaxialTexCoordSystem::ParaxialTexCoordSystem(const Vec3& point0, const Vec3& point1, const Vec3& point2, const BrushFaceAttributes& attribs) :
37         m_index(0) {
38             const Vec3 normal = crossed(point2 - point0, point1 - point0).normalized();
39             setRotation(normal, 0.0f, attribs.rotation());
40         }
41 
ParaxialTexCoordSystem(const Vec3 & normal,const BrushFaceAttributes & attribs)42         ParaxialTexCoordSystem::ParaxialTexCoordSystem(const Vec3& normal, const BrushFaceAttributes& attribs) :
43         m_index(0) {
44             setRotation(normal, 0.0f, attribs.rotation());
45         }
46 
planeNormalIndex(const Vec3 & normal)47         size_t ParaxialTexCoordSystem::planeNormalIndex(const Vec3& normal) {
48             size_t bestIndex = 0;
49             FloatType bestDot = static_cast<FloatType>(0.0);
50             for (size_t i = 0; i < 6; ++i) {
51                 const FloatType dot = normal.dot(BaseAxes[i * 3]);
52                 if (dot > bestDot) { // no need to use -altaxis for qbsp, but -oldaxis is necessary
53                     bestDot = dot;
54                     bestIndex = i;
55                 }
56             }
57             return bestIndex;
58         }
59 
axes(const size_t index,Vec3 & xAxis,Vec3 & yAxis)60         void ParaxialTexCoordSystem::axes(const size_t index, Vec3& xAxis, Vec3& yAxis) {
61             Vec3 temp;
62             axes(index, xAxis, yAxis, temp);
63         }
64 
axes(size_t index,Vec3 & xAxis,Vec3 & yAxis,Vec3 & projectionAxis)65         void ParaxialTexCoordSystem::axes(size_t index, Vec3& xAxis, Vec3& yAxis, Vec3& projectionAxis) {
66             xAxis = BaseAxes[index * 3 + 1];
67             yAxis = BaseAxes[index * 3 + 2];
68             projectionAxis = BaseAxes[(index / 2) * 6];
69         }
70 
doClone() const71         TexCoordSystem* ParaxialTexCoordSystem::doClone() const {
72             return new ParaxialTexCoordSystem(*this);
73         }
74 
doTakeSnapshot()75         TexCoordSystemSnapshot* ParaxialTexCoordSystem::doTakeSnapshot() {
76             return NULL;
77         }
78 
getXAxis() const79         Vec3 ParaxialTexCoordSystem::getXAxis() const {
80             return m_xAxis;
81         }
82 
getYAxis() const83         Vec3 ParaxialTexCoordSystem::getYAxis() const {
84             return m_yAxis;
85         }
86 
getZAxis() const87         Vec3 ParaxialTexCoordSystem::getZAxis() const {
88             return BaseAxes[m_index * 3 + 0];
89         }
90 
doResetTextureAxes(const Vec3 & normal)91         void ParaxialTexCoordSystem::doResetTextureAxes(const Vec3& normal) {}
doResetTextureAxesToParaxial(const Vec3 & normal,const float angle)92         void ParaxialTexCoordSystem::doResetTextureAxesToParaxial(const Vec3& normal, const float angle) {}
doResetTextureAxesToParallel(const Vec3 & normal,const float angle)93         void ParaxialTexCoordSystem::doResetTextureAxesToParallel(const Vec3& normal, const float angle) {}
94 
isRotationInverted(const Vec3 & normal) const95         bool ParaxialTexCoordSystem::isRotationInverted(const Vec3& normal) const {
96             const size_t index = planeNormalIndex(normal);
97             return index % 2 == 0;
98         }
99 
doGetTexCoords(const Vec3 & point,const BrushFaceAttributes & attribs) const100         Vec2f ParaxialTexCoordSystem::doGetTexCoords(const Vec3& point, const BrushFaceAttributes& attribs) const {
101             return (computeTexCoords(point, attribs.scale()) + attribs.offset()) / attribs.textureSize();
102         }
103 
doSetRotation(const Vec3 & normal,const float oldAngle,const float newAngle)104         void ParaxialTexCoordSystem::doSetRotation(const Vec3& normal, const float oldAngle, const float newAngle) {
105             m_index = planeNormalIndex(normal);
106             axes(m_index, m_xAxis, m_yAxis);
107             rotateAxes(m_xAxis, m_yAxis, Math::radians(newAngle), m_index);
108         }
109 
doTransform(const Plane3 & oldBoundary,const Mat4x4 & transformation,BrushFaceAttributes & attribs,bool lockTexture,const Vec3 & oldInvariant)110         void ParaxialTexCoordSystem::doTransform(const Plane3& oldBoundary, const Mat4x4& transformation, BrushFaceAttributes& attribs, bool lockTexture, const Vec3& oldInvariant) {
111             const Vec3 offset     = transformation * Vec3::Null;
112             const Vec3& oldNormal = oldBoundary.normal;
113                   Vec3 newNormal  = transformation * oldNormal - offset;
114             assert(Math::eq(newNormal.length(), 1.0));
115 
116             // fix some rounding errors - if the old and new texture axes are almost the same, use the old axis
117             if (newNormal.equals(oldNormal, 0.01))
118                 newNormal = oldNormal;
119 
120             if (!lockTexture || attribs.xScale() == 0.0f || attribs.yScale() == 0.0f) {
121                 setRotation(newNormal, attribs.rotation(), attribs.rotation());
122                 return;
123             }
124 
125             // calculate the current texture coordinates of the origin
126             const Vec2f oldInvariantTexCoords = computeTexCoords(oldInvariant, attribs.scale()) + attribs.offset();
127 
128             // project the texture axes onto the boundary plane along the texture Z axis
129             const Vec3 boundaryOffset     = oldBoundary.project(Vec3::Null, getZAxis());
130             const Vec3 oldXAxisOnBoundary = oldBoundary.project(m_xAxis * attribs.xScale(), getZAxis()) - boundaryOffset;
131             const Vec3 oldYAxisOnBoundary = oldBoundary.project(m_yAxis * attribs.yScale(), getZAxis()) - boundaryOffset;
132 
133             // transform the projected texture axes and compensate the translational component
134             const Vec3 transformedXAxis = transformation * oldXAxisOnBoundary - offset;
135             const Vec3 transformedYAxis = transformation * oldYAxisOnBoundary - offset;
136 
137             const Vec2f textureSize = attribs.textureSize();
138             const bool preferX = textureSize.x() >= textureSize.y();
139 
140             /*
141             const FloatType dotX = transformedXAxis.normalized().dot(oldXAxisOnBoundary.normalized());
142             const FloatType dotY = transformedYAxis.normalized().dot(oldYAxisOnBoundary.normalized());
143             const bool preferX = Math::abs(dotX) < Math::abs(dotY);
144             */
145 
146             // obtain the new texture plane norm and the new base texture axes
147             Vec3 newBaseXAxis, newBaseYAxis, newProjectionAxis;
148             const size_t newIndex = planeNormalIndex(newNormal);
149             axes(newIndex, newBaseXAxis, newBaseYAxis, newProjectionAxis);
150 
151             const Plane3 newTexturePlane(0.0, newProjectionAxis);
152 
153             // project the transformed texture axes onto the new texture projection plane
154             const Vec3 projectedTransformedXAxis = newTexturePlane.project(transformedXAxis);
155             const Vec3 projectedTransformedYAxis = newTexturePlane.project(transformedYAxis);
156             assert(!projectedTransformedXAxis.nan() &&
157                    !projectedTransformedYAxis.nan());
158 
159             const Vec3 normalizedXAxis = projectedTransformedXAxis.normalized();
160             const Vec3 normalizedYAxis = projectedTransformedYAxis.normalized();
161 
162             // determine the rotation angle from the dot product of the new base axes and the transformed, projected and normalized texture axes
163             float cosX = static_cast<float>(newBaseXAxis.dot(normalizedXAxis.normalized()));
164             float cosY = static_cast<float>(newBaseYAxis.dot(normalizedYAxis.normalized()));
165             assert(!Math::isnan(cosX));
166             assert(!Math::isnan(cosY));
167 
168             float radX = std::acos(cosX);
169             if (crossed(newBaseXAxis, normalizedXAxis).dot(newProjectionAxis) < 0.0)
170                 radX *= -1.0f;
171 
172             float radY = std::acos(cosY);
173             if (crossed(newBaseYAxis, normalizedYAxis).dot(newProjectionAxis) < 0.0)
174                 radY *= -1.0f;
175 
176             // TODO: be smarter about choosing between the X and Y axis rotations - sometimes either
177             // one can be better
178             float rad = preferX ? radX : radY;
179 
180             // for some reason, when the texture plane normal is the Y axis, we must rotation clockwise
181             if (newIndex == 4)
182                 rad *= -1.0f;
183 
184             const float newRotation = Math::correct(Math::normalizeDegrees(Math::degrees(rad)), 4);
185             doSetRotation(newNormal, newRotation, newRotation);
186 
187             // finally compute the scaling factors
188             Vec2f newScale = Vec2f(projectedTransformedXAxis.length(),
189                                    projectedTransformedYAxis.length()).corrected(4);
190 
191             // the sign of the scaling factors depends on the angle between the new texture axis and the projected transformed axis
192             if (m_xAxis.dot(normalizedXAxis) < 0.0)
193                 newScale[0] *= -1.0f;
194             if (m_yAxis.dot(normalizedYAxis) < 0.0)
195                 newScale[1] *= -1.0f;
196 
197             // compute the parameters of the transformed texture coordinate system
198             const Vec3 newInvariant = transformation * oldInvariant;
199 
200             // determine the new texture coordinates of the transformed center of the face, sans offsets
201             const Vec2f newInvariantTexCoords = computeTexCoords(newInvariant, newScale);
202 
203             // since the center should be invariant, the offsets are determined by the difference of the current and
204             // the original texture coordiknates of the center
205             const Vec2f newOffset = attribs.modOffset(oldInvariantTexCoords - newInvariantTexCoords).corrected(4);
206 
207             assert(!newOffset.nan());
208             assert(!newScale.nan());
209             assert(!Math::isnan(newRotation));
210             assert(!Math::zero(newScale.x()));
211             assert(!Math::zero(newScale.y()));
212 
213             attribs.setOffset(newOffset);
214             attribs.setScale(newScale);
215             attribs.setRotation(newRotation);
216         }
217 
doUpdateNormal(const Vec3 & oldNormal,const Vec3 & newNormal,const BrushFaceAttributes & attribs)218         void ParaxialTexCoordSystem::doUpdateNormal(const Vec3& oldNormal, const Vec3& newNormal, const BrushFaceAttributes& attribs) {
219             setRotation(newNormal, attribs.rotation(), attribs.rotation());
220         }
221 
doShearTexture(const Vec3 & normal,const Vec2f & factors)222         void ParaxialTexCoordSystem::doShearTexture(const Vec3& normal, const Vec2f& factors) {
223             // not supported
224         }
225 
doMeasureAngle(const float currentAngle,const Vec2f & center,const Vec2f & point) const226         float ParaxialTexCoordSystem::doMeasureAngle(const float currentAngle, const Vec2f& center, const Vec2f& point) const {
227             const Vec3& zAxis = Vec3::PosZ; //m_index == 5 ? Vec3::NegZ : 	Vec3::PosZ;
228             const Quat3 rot(zAxis, -Math::radians(currentAngle));
229             const Vec3 vec = rot * (point - center);
230 
231             const FloatType angleInRadians = Math::C::twoPi() - angleBetween(vec.normalized(), Vec3::PosX, zAxis);
232             return static_cast<float>(Math::degrees(angleInRadians));
233         }
234 
rotateAxes(Vec3 & xAxis,Vec3 & yAxis,const FloatType angleInRadians,const size_t planeNormIndex) const235         void ParaxialTexCoordSystem::rotateAxes(Vec3& xAxis, Vec3& yAxis, const FloatType angleInRadians, const size_t planeNormIndex) const {
236             const Vec3 rotAxis = crossed(BaseAxes[planeNormIndex * 3 + 2], BaseAxes[planeNormIndex * 3 + 1]);
237             const Quat3 rot(rotAxis, angleInRadians);
238             xAxis = rot * xAxis;
239             yAxis = rot * yAxis;
240         }
241     }
242 }
243