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