1 /* 2 * Copyright (C) 1999 Antti Koivisto (koivisto (at) kde.org) 3 * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. 4 * 5 * This library is free software; you can redistribute it and/or 6 * modify it under the terms of the GNU Library General Public 7 * License as published by the Free Software Foundation; either 8 * version 2 of the License, or (at your option) any later version. 9 * 10 * This library is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 * Library General Public License for more details. 14 * 15 * You should have received a copy of the GNU Library General Public License 16 * along with this library; see the file COPYING.LIB. If not, write to 17 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 * Boston, MA 02110-1301, USA. 19 * 20 */ 21 22 #include "config.h" 23 #include "platform/transforms/TransformOperations.h" 24 25 #include "platform/animation/AnimationUtilities.h" 26 #include "platform/geometry/FloatBox.h" 27 #include "platform/transforms/IdentityTransformOperation.h" 28 #include "platform/transforms/InterpolatedTransformOperation.h" 29 #include "platform/transforms/RotateTransformOperation.h" 30 #include <algorithm> 31 32 namespace blink { 33 34 TransformOperations::TransformOperations(bool makeIdentity) 35 { 36 if (makeIdentity) 37 m_operations.append(IdentityTransformOperation::create()); 38 } 39 40 bool TransformOperations::operator==(const TransformOperations& o) const 41 { 42 if (m_operations.size() != o.m_operations.size()) 43 return false; 44 45 unsigned s = m_operations.size(); 46 for (unsigned i = 0; i < s; i++) { 47 if (*m_operations[i] != *o.m_operations[i]) 48 return false; 49 } 50 51 return true; 52 } 53 54 bool TransformOperations::operationsMatch(const TransformOperations& other) const 55 { 56 size_t numOperations = operations().size(); 57 // If the sizes of the function lists don't match, the lists don't match 58 if (numOperations != other.operations().size()) 59 return false; 60 61 // If the types of each function are not the same, the lists don't match 62 for (size_t i = 0; i < numOperations; ++i) { 63 if (!operations()[i]->isSameType(*other.operations()[i])) 64 return false; 65 } 66 return true; 67 } 68 69 TransformOperations TransformOperations::blendByMatchingOperations(const TransformOperations& from, const double& progress) const 70 { 71 TransformOperations result; 72 73 unsigned fromSize = from.operations().size(); 74 unsigned toSize = operations().size(); 75 unsigned size = std::max(fromSize, toSize); 76 for (unsigned i = 0; i < size; i++) { 77 RefPtr<TransformOperation> fromOperation = (i < fromSize) ? from.operations()[i].get() : 0; 78 RefPtr<TransformOperation> toOperation = (i < toSize) ? operations()[i].get() : 0; 79 RefPtr<TransformOperation> blendedOperation = toOperation ? toOperation->blend(fromOperation.get(), progress) : (fromOperation ? fromOperation->blend(0, progress, true) : nullptr); 80 if (blendedOperation) 81 result.operations().append(blendedOperation); 82 else { 83 RefPtr<TransformOperation> identityOperation = IdentityTransformOperation::create(); 84 if (progress > 0.5) 85 result.operations().append(toOperation ? toOperation : identityOperation); 86 else 87 result.operations().append(fromOperation ? fromOperation : identityOperation); 88 } 89 } 90 91 return result; 92 } 93 94 TransformOperations TransformOperations::blendByUsingMatrixInterpolation(const TransformOperations& from, double progress) const 95 { 96 TransformOperations result; 97 result.operations().append(InterpolatedTransformOperation::create(from, *this, progress)); 98 return result; 99 } 100 101 TransformOperations TransformOperations::blend(const TransformOperations& from, double progress) const 102 { 103 if (from == *this || (!from.size() && !size())) 104 return *this; 105 106 // If either list is empty, use blendByMatchingOperations which has special logic for this case. 107 if (!from.size() || !size() || from.operationsMatch(*this)) 108 return blendByMatchingOperations(from, progress); 109 110 return blendByUsingMatrixInterpolation(from, progress); 111 } 112 113 static void findCandidatesInPlane(double px, double py, double nz, double* candidates, int* numCandidates) 114 { 115 // The angle that this point is rotated with respect to the plane nz 116 double phi = atan2(px, py); 117 118 *numCandidates = 4; 119 candidates[0] = phi; // The element at 0deg (maximum x) 120 121 for (int i = 1; i < *numCandidates; ++i) 122 candidates[i] = candidates[i - 1] + M_PI_2; // every 90 deg 123 if (nz < 0.f) { 124 for (int i = 0; i < *numCandidates; ++i) 125 candidates[i] *= -1; 126 } 127 } 128 129 // This method returns the bounding box that contains the starting point, 130 // the ending point, and any of the extrema (in each dimension) found across 131 // the circle described by the arc. These are then filtered to points that 132 // actually reside on the arc. 133 static void boundingBoxForArc(const FloatPoint3D& point, const RotateTransformOperation& fromTransform, const RotateTransformOperation& toTransform, double minProgress, double maxProgress, FloatBox& box) 134 { 135 double candidates[6]; 136 int numCandidates = 0; 137 138 FloatPoint3D axis(fromTransform.axis()); 139 double fromDegrees = fromTransform.angle(); 140 double toDegrees = toTransform.angle(); 141 142 if (axis.dot(toTransform.axis()) < 0) 143 toDegrees *= -1; 144 145 fromDegrees = blend(fromDegrees, toTransform.angle(), minProgress); 146 toDegrees = blend(toDegrees, fromTransform.angle(), 1.0 - maxProgress); 147 if (fromDegrees > toDegrees) 148 std::swap(fromDegrees, toDegrees); 149 150 TransformationMatrix fromMatrix; 151 TransformationMatrix toMatrix; 152 fromMatrix.rotate3d(fromTransform.x(), fromTransform.y(), fromTransform.z(), fromDegrees); 153 toMatrix.rotate3d(fromTransform.x(), fromTransform.y(), fromTransform.z(), toDegrees); 154 155 FloatPoint3D fromPoint = fromMatrix.mapPoint(point); 156 FloatPoint3D toPoint = toMatrix.mapPoint(point); 157 158 if (box.isEmpty()) 159 box.setOrigin(fromPoint); 160 else 161 box.expandTo(fromPoint); 162 163 box.expandTo(toPoint); 164 165 switch (fromTransform.type()) { 166 case TransformOperation::RotateX: 167 findCandidatesInPlane(point.y(), point.z(), fromTransform.x(), candidates, &numCandidates); 168 break; 169 case TransformOperation::RotateY: 170 findCandidatesInPlane(point.z(), point.x(), fromTransform.y(), candidates, &numCandidates); 171 break; 172 case TransformOperation::RotateZ: 173 findCandidatesInPlane(point.x(), point.y(), fromTransform.z(), candidates, &numCandidates); 174 break; 175 default: 176 { 177 FloatPoint3D normal = axis; 178 if (normal.isZero()) 179 return; 180 normal.normalize(); 181 FloatPoint3D origin; 182 FloatPoint3D toPoint = point - origin; 183 FloatPoint3D center = origin + normal * toPoint.dot(normal); 184 FloatPoint3D v1 = point - center; 185 if (v1.isZero()) 186 return; 187 188 v1.normalize(); 189 FloatPoint3D v2 = normal.cross(v1); 190 // v1 is the basis vector in the direction of the point. 191 // i.e. with a rotation of 0, v1 is our +x vector. 192 // v2 is a perpenticular basis vector of our plane (+y). 193 194 // Take the parametric equation of a circle. 195 // (x = r*cos(t); y = r*sin(t); 196 // We can treat that as a circle on the plane v1xv2 197 // From that we get the parametric equations for a circle on the 198 // plane in 3d space of 199 // x(t) = r*cos(t)*v1.x + r*sin(t)*v2.x + cx 200 // y(t) = r*cos(t)*v1.y + r*sin(t)*v2.y + cy 201 // z(t) = r*cos(t)*v1.z + r*sin(t)*v2.z + cz 202 // taking the derivative of (x, y, z) and solving for 0 gives us our 203 // maximum/minimum x, y, z values 204 // x'(t) = r*cos(t)*v2.x - r*sin(t)*v1.x = 0 205 // tan(t) = v2.x/v1.x 206 // t = atan2(v2.x, v1.x) + n*M_PI; 207 208 candidates[0] = atan2(v2.x(), v1.x()); 209 candidates[1] = candidates[0] + M_PI; 210 candidates[2] = atan2(v2.y(), v1.y()); 211 candidates[3] = candidates[2] + M_PI; 212 candidates[4] = atan2(v2.z(), v1.z()); 213 candidates[5] = candidates[4] + M_PI; 214 numCandidates = 6; 215 } 216 break; 217 } 218 219 double minRadians = deg2rad(fromDegrees); 220 double maxRadians = deg2rad(toDegrees); 221 // Once we have the candidates, we now filter them down to ones that 222 // actually live on the arc, rather than the entire circle. 223 for (int i = 0; i < numCandidates; ++i) { 224 double radians = candidates[i]; 225 226 while (radians < minRadians) 227 radians += 2.0 * M_PI; 228 while (radians > maxRadians) 229 radians -= 2.0 * M_PI; 230 if (radians < minRadians) 231 continue; 232 233 TransformationMatrix rotation; 234 rotation.rotate3d(axis.x(), axis.y(), axis.z(), rad2deg(radians)); 235 box.expandTo(rotation.mapPoint(point)); 236 } 237 } 238 239 bool TransformOperations::blendedBoundsForBox(const FloatBox& box, const TransformOperations& from, const double& minProgress, const double& maxProgress, FloatBox* bounds) const 240 { 241 242 int fromSize = from.operations().size(); 243 int toSize = operations().size(); 244 int size = std::max(fromSize, toSize); 245 246 *bounds = box; 247 for (int i = size - 1; i >= 0; i--) { 248 RefPtr<TransformOperation> fromOperation = (i < fromSize) ? from.operations()[i] : nullptr; 249 RefPtr<TransformOperation> toOperation = (i < toSize) ? operations()[i] : nullptr; 250 if (fromOperation && fromOperation->type() == TransformOperation::None) 251 fromOperation = nullptr; 252 253 if (toOperation && toOperation->type() == TransformOperation::None) 254 toOperation = nullptr; 255 256 TransformOperation::OperationType interpolationType = toOperation ? toOperation->type() : 257 fromOperation ? fromOperation->type() : 258 TransformOperation::None; 259 if (fromOperation && toOperation && !fromOperation->canBlendWith(*toOperation.get())) 260 return false; 261 262 switch (interpolationType) { 263 case TransformOperation::Identity: 264 bounds->expandTo(box); 265 continue; 266 case TransformOperation::Translate: 267 case TransformOperation::TranslateX: 268 case TransformOperation::TranslateY: 269 case TransformOperation::TranslateZ: 270 case TransformOperation::Translate3D: 271 case TransformOperation::Scale: 272 case TransformOperation::ScaleX: 273 case TransformOperation::ScaleY: 274 case TransformOperation::ScaleZ: 275 case TransformOperation::Scale3D: 276 case TransformOperation::Skew: 277 case TransformOperation::SkewX: 278 case TransformOperation::SkewY: 279 case TransformOperation::Perspective: 280 { 281 RefPtr<TransformOperation> fromTransform; 282 RefPtr<TransformOperation> toTransform; 283 if (!toOperation) { 284 fromTransform = fromOperation->blend(toOperation.get(), 1-minProgress, false); 285 toTransform = fromOperation->blend(toOperation.get(), 1-maxProgress, false); 286 } else { 287 fromTransform = toOperation->blend(fromOperation.get(), minProgress, false); 288 toTransform = toOperation->blend(fromOperation.get(), maxProgress, false); 289 } 290 if (!fromTransform || !toTransform) 291 continue; 292 TransformationMatrix fromMatrix; 293 TransformationMatrix toMatrix; 294 fromTransform->apply(fromMatrix, FloatSize()); 295 toTransform->apply(toMatrix, FloatSize()); 296 FloatBox fromBox = *bounds; 297 FloatBox toBox = *bounds; 298 fromMatrix.transformBox(fromBox); 299 toMatrix.transformBox(toBox); 300 *bounds = fromBox; 301 bounds->expandTo(toBox); 302 continue; 303 } 304 case TransformOperation::Rotate: // This is also RotateZ 305 case TransformOperation::Rotate3D: 306 case TransformOperation::RotateX: 307 case TransformOperation::RotateY: 308 { 309 RefPtr<RotateTransformOperation> identityRotation; 310 const RotateTransformOperation* fromRotation = nullptr; 311 const RotateTransformOperation* toRotation = nullptr; 312 if (fromOperation) { 313 fromRotation = static_cast<const RotateTransformOperation*>(fromOperation.get()); 314 if (fromRotation->axis().isZero()) 315 fromRotation = nullptr; 316 } 317 318 if (toOperation) { 319 toRotation = static_cast<const RotateTransformOperation*>(toOperation.get()); 320 if (toRotation->axis().isZero()) 321 toRotation = nullptr; 322 } 323 324 double fromAngle; 325 double toAngle; 326 FloatPoint3D axis; 327 if (!RotateTransformOperation::shareSameAxis(fromRotation, toRotation, &axis, &fromAngle, &toAngle)) { 328 return(false); 329 } 330 331 if (!fromRotation) { 332 identityRotation = RotateTransformOperation::create(axis.x(), axis.y(), axis.z(), 0, fromOperation ? fromOperation->type() : toOperation->type()); 333 fromRotation = identityRotation.get(); 334 } 335 336 if (!toRotation) { 337 if (!identityRotation) 338 identityRotation = RotateTransformOperation::create(axis.x(), axis.y(), axis.z(), 0, fromOperation ? fromOperation->type() : toOperation->type()); 339 toRotation = identityRotation.get(); 340 } 341 342 FloatBox fromBox = *bounds; 343 bool first = true; 344 for (size_t i = 0; i < 2; ++i) { 345 for (size_t j = 0; j < 2; ++j) { 346 for (size_t k = 0; k < 2; ++k) { 347 FloatBox boundsForArc; 348 FloatPoint3D corner(fromBox.x(), fromBox.y(), fromBox.z()); 349 corner += FloatPoint3D(i * fromBox.width(), j * fromBox.height(), k * fromBox.depth()); 350 boundingBoxForArc(corner, *fromRotation, *toRotation, minProgress, maxProgress, boundsForArc); 351 if (first) { 352 *bounds = boundsForArc; 353 first = false; 354 } else { 355 bounds->expandTo(boundsForArc); 356 } 357 } 358 } 359 } 360 } 361 continue; 362 case TransformOperation::None: 363 continue; 364 case TransformOperation::Matrix: 365 case TransformOperation::Matrix3D: 366 case TransformOperation::Interpolated: 367 return(false); 368 } 369 } 370 371 return true; 372 } 373 374 TransformOperations TransformOperations::add(const TransformOperations& addend) const 375 { 376 TransformOperations result; 377 result.m_operations = operations(); 378 result.m_operations.appendVector(addend.operations()); 379 return result; 380 } 381 382 } // namespace blink 383