1 //
2 // Copyright 2016 Pixar
3 //
4 // Licensed under the Apache License, Version 2.0 (the "Apache License")
5 // with the following modification; you may not use this file except in
6 // compliance with the Apache License and the following modification to it:
7 // Section 6. Trademarks. is deleted and replaced with:
8 //
9 // 6. Trademarks. This License does not grant permission to use the trade
10 // names, trademarks, service marks, or product names of the Licensor
11 // and its affiliates, except as required to comply with Section 4(c) of
12 // the License and to reproduce the content of the NOTICE file.
13 //
14 // You may obtain a copy of the Apache License at
15 //
16 // http://www.apache.org/licenses/LICENSE-2.0
17 //
18 // Unless required by applicable law or agreed to in writing, software
19 // distributed under the Apache License with the above modification is
20 // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
21 // KIND, either express or implied. See the Apache License for the specific
22 // language governing permissions and limitations under the Apache License.
23 //
24 #include "pxr/usd/usdGeom/xformCommonAPI.h"
25 #include "pxr/usd/usd/schemaRegistry.h"
26 #include "pxr/usd/usd/typed.h"
27 #include "pxr/usd/usd/tokens.h"
28
29 #include "pxr/usd/sdf/types.h"
30 #include "pxr/usd/sdf/assetPath.h"
31
32 PXR_NAMESPACE_OPEN_SCOPE
33
34 // Register the schema with the TfType system.
TF_REGISTRY_FUNCTION(TfType)35 TF_REGISTRY_FUNCTION(TfType)
36 {
37 TfType::Define<UsdGeomXformCommonAPI,
38 TfType::Bases< UsdAPISchemaBase > >();
39
40 }
41
42 TF_DEFINE_PRIVATE_TOKENS(
43 _schemaTokens,
44 (XformCommonAPI)
45 );
46
47 /* virtual */
~UsdGeomXformCommonAPI()48 UsdGeomXformCommonAPI::~UsdGeomXformCommonAPI()
49 {
50 }
51
52 /* static */
53 UsdGeomXformCommonAPI
Get(const UsdStagePtr & stage,const SdfPath & path)54 UsdGeomXformCommonAPI::Get(const UsdStagePtr &stage, const SdfPath &path)
55 {
56 if (!stage) {
57 TF_CODING_ERROR("Invalid stage");
58 return UsdGeomXformCommonAPI();
59 }
60 return UsdGeomXformCommonAPI(stage->GetPrimAtPath(path));
61 }
62
63
64 /* virtual */
_GetSchemaKind() const65 UsdSchemaKind UsdGeomXformCommonAPI::_GetSchemaKind() const
66 {
67 return UsdGeomXformCommonAPI::schemaKind;
68 }
69
70 /* static */
71 const TfType &
_GetStaticTfType()72 UsdGeomXformCommonAPI::_GetStaticTfType()
73 {
74 static TfType tfType = TfType::Find<UsdGeomXformCommonAPI>();
75 return tfType;
76 }
77
78 /* static */
79 bool
_IsTypedSchema()80 UsdGeomXformCommonAPI::_IsTypedSchema()
81 {
82 static bool isTyped = _GetStaticTfType().IsA<UsdTyped>();
83 return isTyped;
84 }
85
86 /* virtual */
87 const TfType &
_GetTfType() const88 UsdGeomXformCommonAPI::_GetTfType() const
89 {
90 return _GetStaticTfType();
91 }
92
93 /*static*/
94 const TfTokenVector&
GetSchemaAttributeNames(bool includeInherited)95 UsdGeomXformCommonAPI::GetSchemaAttributeNames(bool includeInherited)
96 {
97 static TfTokenVector localNames;
98 static TfTokenVector allNames =
99 UsdAPISchemaBase::GetSchemaAttributeNames(true);
100
101 if (includeInherited)
102 return allNames;
103 else
104 return localNames;
105 }
106
107 PXR_NAMESPACE_CLOSE_SCOPE
108
109 // ===================================================================== //
110 // Feel free to add custom code below this line. It will be preserved by
111 // the code generator.
112 //
113 // Just remember to wrap code in the appropriate delimiters:
114 // 'PXR_NAMESPACE_OPEN_SCOPE', 'PXR_NAMESPACE_CLOSE_SCOPE'.
115 // ===================================================================== //
116 // --(BEGIN CUSTOM CODE)--
117
118 #include "pxr/base/gf/rotation.h"
119 #include "pxr/base/trace/trace.h"
120
121 #include <map>
122
123 using std::vector;
124
125 PXR_NAMESPACE_OPEN_SCOPE
126
TF_REGISTRY_FUNCTION(TfEnum)127 TF_REGISTRY_FUNCTION(TfEnum)
128 {
129 TF_ADD_ENUM_NAME(UsdGeomXformCommonAPI::RotationOrderXYZ, "XYZ");
130 TF_ADD_ENUM_NAME(UsdGeomXformCommonAPI::RotationOrderXZY, "XZY");
131 TF_ADD_ENUM_NAME(UsdGeomXformCommonAPI::RotationOrderYXZ, "YXZ");
132 TF_ADD_ENUM_NAME(UsdGeomXformCommonAPI::RotationOrderYZX, "YZX");
133 TF_ADD_ENUM_NAME(UsdGeomXformCommonAPI::RotationOrderZXY, "ZXY");
134 TF_ADD_ENUM_NAME(UsdGeomXformCommonAPI::RotationOrderZYX, "ZYX");
135
136 TF_ADD_ENUM_NAME(UsdGeomXformCommonAPI::OpTranslate);
137 TF_ADD_ENUM_NAME(UsdGeomXformCommonAPI::OpRotate);
138 TF_ADD_ENUM_NAME(UsdGeomXformCommonAPI::OpScale);
139 TF_ADD_ENUM_NAME(UsdGeomXformCommonAPI::OpPivot);
140 };
141
142 static
143 bool
144 _GetCommonXformOps(
145 const UsdGeomXformable& xformable,
146 UsdGeomXformOp* translateOp=nullptr,
147 UsdGeomXformOp* pivotOp=nullptr,
148 UsdGeomXformOp* rotateOp=nullptr,
149 UsdGeomXformOp* scaleOp=nullptr,
150 UsdGeomXformOp* pivotInvOp=nullptr,
151 bool* resetXformStack=nullptr);
152
153 static
154 UsdGeomXformCommonAPI::Ops
155 _GetOrAddCommonXformOps(
156 const UsdGeomXformable& xformable,
157 const UsdGeomXformCommonAPI::RotationOrder* rotOrder,
158 bool createTranslate,
159 bool createPivot,
160 bool createRotate,
161 bool createScale);
162
163 /* virtual */
164 bool
_IsCompatible() const165 UsdGeomXformCommonAPI::_IsCompatible() const
166 {
167 if (!UsdAPISchemaBase::_IsCompatible()) {
168 return false;
169 }
170
171 UsdGeomXformable xformable(GetPrim());
172 if (!xformable) {
173 return false;
174 }
175
176 return _GetCommonXformOps(xformable);
177 }
178
179 // Assumes rotationOrder is XYZ.
180 static void
_RotMatToRotXYZ(const GfMatrix4d & rotMat,GfVec3f * rotXYZ)181 _RotMatToRotXYZ(
182 const GfMatrix4d &rotMat,
183 GfVec3f *rotXYZ)
184 {
185 GfRotation rot = rotMat.ExtractRotation();
186 GfVec3d angles = rot.Decompose(GfVec3d::ZAxis(),
187 GfVec3d::YAxis(),
188 GfVec3d::XAxis());
189 *rotXYZ = GfVec3f(angles[2], angles[1], angles[0]);
190 }
191
192 static void
_ConvertMatrixToComponents(const GfMatrix4d & matrix,GfVec3d * translation,GfVec3f * rotation,GfVec3f * scale)193 _ConvertMatrixToComponents(const GfMatrix4d &matrix,
194 GfVec3d *translation,
195 GfVec3f *rotation,
196 GfVec3f *scale)
197 {
198 GfMatrix4d rotMat(1.0);
199 GfVec3d doubleScale(1.0);
200 GfMatrix4d scaleOrientMatUnused, perspMatUnused;
201 matrix.Factor(&scaleOrientMatUnused, &doubleScale, &rotMat,
202 translation, &perspMatUnused);
203
204 *scale = GfVec3f(doubleScale[0], doubleScale[1], doubleScale[2]);
205
206 if (!rotMat.Orthonormalize(/* issueWarning */ false))
207 TF_WARN("Failed to orthonormalize rotation matrix.");
208
209 _RotMatToRotXYZ(rotMat, rotation);
210 }
211
212 bool
SetXformVectors(const GfVec3d & translation,const GfVec3f & rotation,const GfVec3f & scale,const GfVec3f & pivot,RotationOrder rotOrder,const UsdTimeCode time) const213 UsdGeomXformCommonAPI::SetXformVectors(
214 const GfVec3d &translation,
215 const GfVec3f &rotation,
216 const GfVec3f &scale,
217 const GfVec3f &pivot,
218 RotationOrder rotOrder,
219 const UsdTimeCode time) const
220 {
221 // The call below will check rotation order compatibility before any data
222 // is authored.
223 const Ops ops = CreateXformOps(
224 rotOrder,
225 OpTranslate, OpRotate, OpScale, OpPivot);
226 if (!ops.translateOp || !ops.rotateOp || !ops.scaleOp || !ops.pivotOp) {
227 return false;
228 }
229
230 return ops.translateOp.Set(translation, time) &&
231 ops.rotateOp.Set(rotation, time) &&
232 ops.scaleOp.Set(scale, time) &&
233 ops.pivotOp.Set(pivot, time);
234 }
235
236 static
237 bool
_IsMatrixIdentity(const GfMatrix4d & matrix)238 _IsMatrixIdentity(const GfMatrix4d& matrix)
239 {
240 const GfMatrix4d IDENTITY(1.0);
241 const double TOLERANCE = 1e-6;
242
243 if (GfIsClose(matrix.GetRow(0), IDENTITY.GetRow(0), TOLERANCE) &&
244 GfIsClose(matrix.GetRow(1), IDENTITY.GetRow(1), TOLERANCE) &&
245 GfIsClose(matrix.GetRow(2), IDENTITY.GetRow(2), TOLERANCE) &&
246 GfIsClose(matrix.GetRow(3), IDENTITY.GetRow(3), TOLERANCE)) {
247 return true;
248 }
249
250 return false;
251 }
252
253 static
254 bool
_MatricesAreInverses(const GfMatrix4d & matrix1,const GfMatrix4d & matrix2)255 _MatricesAreInverses(const GfMatrix4d& matrix1, const GfMatrix4d& matrix2)
256 {
257 GfMatrix4d mult = matrix1 * matrix2;
258 return _IsMatrixIdentity(mult);
259 }
260
261 static constexpr
262 bool
_IsThreeAxisRotateOpType(UsdGeomXformOp::Type opType)263 _IsThreeAxisRotateOpType(UsdGeomXformOp::Type opType)
264 {
265 static_assert(
266 UsdGeomXformOp::TypeRotateZYX - UsdGeomXformOp::TypeRotateXYZ == 5,
267 "Exactly six three-axis rotate op types");
268 return opType >= UsdGeomXformOp::TypeRotateXYZ &&
269 opType <= UsdGeomXformOp::TypeRotateZYX;
270 }
271
272 static constexpr
273 bool
_IsRotateOpType(UsdGeomXformOp::Type opType)274 _IsRotateOpType(UsdGeomXformOp::Type opType)
275 {
276 static_assert(
277 UsdGeomXformOp::TypeRotateZYX - UsdGeomXformOp::TypeRotateX == 8,
278 "Exactly nine rotate op types");
279 return opType >= UsdGeomXformOp::TypeRotateX &&
280 opType <= UsdGeomXformOp::TypeRotateZYX;
281 }
282
283 static_assert(_IsRotateOpType(UsdGeomXformOp::TypeRotateX) &&
284 !_IsThreeAxisRotateOpType(UsdGeomXformOp::TypeRotateX), "");
285 static_assert(_IsRotateOpType(UsdGeomXformOp::TypeRotateY) &&
286 !_IsThreeAxisRotateOpType(UsdGeomXformOp::TypeRotateY), "");
287 static_assert(_IsRotateOpType(UsdGeomXformOp::TypeRotateZ) &&
288 !_IsThreeAxisRotateOpType(UsdGeomXformOp::TypeRotateZ), "");
289 static_assert(_IsRotateOpType(UsdGeomXformOp::TypeRotateXYZ) &&
290 _IsThreeAxisRotateOpType(UsdGeomXformOp::TypeRotateXYZ), "");
291 static_assert(_IsRotateOpType(UsdGeomXformOp::TypeRotateXZY) &&
292 _IsThreeAxisRotateOpType(UsdGeomXformOp::TypeRotateXZY), "");
293 static_assert(_IsRotateOpType(UsdGeomXformOp::TypeRotateYXZ) &&
294 _IsThreeAxisRotateOpType(UsdGeomXformOp::TypeRotateYXZ), "");
295 static_assert(_IsRotateOpType(UsdGeomXformOp::TypeRotateYZX) &&
296 _IsThreeAxisRotateOpType(UsdGeomXformOp::TypeRotateYZX), "");
297 static_assert(_IsRotateOpType(UsdGeomXformOp::TypeRotateZXY) &&
298 _IsThreeAxisRotateOpType(UsdGeomXformOp::TypeRotateZXY), "");
299 static_assert(_IsRotateOpType(UsdGeomXformOp::TypeRotateZYX) &&
300 _IsThreeAxisRotateOpType(UsdGeomXformOp::TypeRotateZYX), "");
301
302 static_assert(!_IsRotateOpType(UsdGeomXformOp::TypeTranslate) &&
303 !_IsThreeAxisRotateOpType(UsdGeomXformOp::TypeTranslate), "");
304 static_assert(!_IsRotateOpType(UsdGeomXformOp::TypeScale) &&
305 !_IsThreeAxisRotateOpType(UsdGeomXformOp::TypeScale), "");
306
307 static
308 UsdGeomXformOp::Type
_GetRotateOpType(const vector<UsdGeomXformOp> & ops)309 _GetRotateOpType(const vector<UsdGeomXformOp>& ops)
310 {
311 for (const UsdGeomXformOp& op : ops) {
312 if (_IsRotateOpType(op.GetOpType())) {
313 return op.GetOpType();
314 }
315 }
316 return UsdGeomXformOp::TypeRotateXYZ;
317 }
318
319 /* static */
320 UsdGeomXformOp::Type
ConvertRotationOrderToOpType(RotationOrder rotOrder)321 UsdGeomXformCommonAPI::ConvertRotationOrderToOpType(
322 RotationOrder rotOrder)
323 {
324 switch (rotOrder) {
325 case UsdGeomXformCommonAPI::RotationOrderXYZ:
326 return UsdGeomXformOp::TypeRotateXYZ;
327 case UsdGeomXformCommonAPI::RotationOrderXZY:
328 return UsdGeomXformOp::TypeRotateXZY;
329 case UsdGeomXformCommonAPI::RotationOrderYXZ:
330 return UsdGeomXformOp::TypeRotateYXZ;
331 case UsdGeomXformCommonAPI::RotationOrderYZX:
332 return UsdGeomXformOp::TypeRotateYZX;
333 case UsdGeomXformCommonAPI::RotationOrderZXY:
334 return UsdGeomXformOp::TypeRotateZXY;
335 case UsdGeomXformCommonAPI::RotationOrderZYX:
336 return UsdGeomXformOp::TypeRotateZYX;
337 default:
338 // Should never hit this.
339 TF_CODING_ERROR("Invalid rotation order <%s>.",
340 TfEnum::GetName(rotOrder).c_str());
341 // Default rotation order is XYZ.
342 return UsdGeomXformOp::TypeRotateXYZ;
343 }
344 }
345
346 /* static */
347 UsdGeomXformCommonAPI::RotationOrder
ConvertOpTypeToRotationOrder(UsdGeomXformOp::Type opType)348 UsdGeomXformCommonAPI::ConvertOpTypeToRotationOrder(
349 UsdGeomXformOp::Type opType)
350 {
351 switch (opType) {
352 case UsdGeomXformOp::TypeRotateXYZ:
353 return UsdGeomXformCommonAPI::RotationOrderXYZ;
354 case UsdGeomXformOp::TypeRotateXZY:
355 return UsdGeomXformCommonAPI::RotationOrderXZY;
356 case UsdGeomXformOp::TypeRotateYXZ:
357 return UsdGeomXformCommonAPI::RotationOrderYXZ;
358 case UsdGeomXformOp::TypeRotateYZX:
359 return UsdGeomXformCommonAPI::RotationOrderYZX;
360 case UsdGeomXformOp::TypeRotateZXY:
361 return UsdGeomXformCommonAPI::RotationOrderZXY;
362 case UsdGeomXformOp::TypeRotateZYX:
363 return UsdGeomXformCommonAPI::RotationOrderZYX;
364 default:
365 TF_CODING_ERROR("'%s' is not a three-axis rotate op type",
366 TfEnum::GetName(opType).c_str());
367 // Default rotation order is XYZ.
368 return UsdGeomXformCommonAPI::RotationOrderXYZ;
369 }
370 }
371
372 /* static */
373 bool
CanConvertOpTypeToRotationOrder(UsdGeomXformOp::Type opType)374 UsdGeomXformCommonAPI::CanConvertOpTypeToRotationOrder(
375 UsdGeomXformOp::Type opType)
376 {
377 // _IsThreeAxisRotateOpType must be a separate function because it is
378 // constexpr (so that we can static_assert) but we want to keep the
379 // definition out of the header (constexpr implies inline, so it needs to be
380 // defined where it's declared).
381 return _IsThreeAxisRotateOpType(opType);
382 }
383
384 // This helper method looks through the given xformOps and returns a vector of
385 // common op types that the xformOps could possibly be reduced to by
386 // accumulation.
387 static
388 vector<UsdGeomXformOp::Type>
_GetCommonOpTypesForOpOrder(const vector<UsdGeomXformOp> & xformOps,int * translateIndex,int * translatePivotIndex,int * rotateIndex,int * translateIdentityIndex,int * scaleIndex,int * translatePivotInvertIndex)389 _GetCommonOpTypesForOpOrder(const vector<UsdGeomXformOp>& xformOps,
390 int* translateIndex,
391 int* translatePivotIndex,
392 int* rotateIndex,
393 int* translateIdentityIndex,
394 int* scaleIndex,
395 int* translatePivotInvertIndex) {
396 UsdGeomXformOp::Type rotateOpType = UsdGeomXformOp::TypeRotateXYZ;
397 bool hasRotateOp = false;
398 bool hasScaleOp = false;
399 size_t numInverseTranslateOps = 0;
400
401 TF_FOR_ALL(it, xformOps) {
402 if (_IsRotateOpType(it->GetOpType())) {
403 hasRotateOp = true;
404 rotateOpType = it->GetOpType();
405 } else if (it->GetOpType() == UsdGeomXformOp::TypeScale) {
406 hasScaleOp = true;
407 } else if (it->GetOpType() == UsdGeomXformOp::TypeTranslate &&
408 it->IsInverseOp()) {
409 ++numInverseTranslateOps;
410 }
411 }
412
413 vector<UsdGeomXformOp::Type> commonOpTypes;
414 size_t currentIndex = 0;
415
416 // The translate and translatePivot will always be present, and so will
417 // translatePivotInvert below. Initialize the rest to invalid.
418 commonOpTypes.push_back(UsdGeomXformOp::TypeTranslate);
419 commonOpTypes.push_back(UsdGeomXformOp::TypeTranslate);
420 *translateIndex = currentIndex++;
421 *translatePivotIndex = currentIndex++;
422 *rotateIndex = -1;
423 *translateIdentityIndex = -1;
424 *scaleIndex = -1;
425
426 if (hasRotateOp) {
427 commonOpTypes.push_back(rotateOpType);
428 *rotateIndex = currentIndex++;
429 }
430
431 if (numInverseTranslateOps > 1) {
432 // If more than one inverse translate is present, assume that means
433 // that both a rotate pivot and a scale pivot are specified. For it to
434 // be reducible, they must be at the same location in space, in which
435 // case they'll accumulate to identity in the translateIdentityIndex
436 // position.
437 commonOpTypes.push_back(UsdGeomXformOp::TypeTranslate);
438 *translateIdentityIndex = currentIndex++;
439 }
440
441 if (hasScaleOp) {
442 commonOpTypes.push_back(UsdGeomXformOp::TypeScale);
443 *scaleIndex = currentIndex++;
444 }
445
446 commonOpTypes.push_back(UsdGeomXformOp::TypeTranslate);
447 *translatePivotInvertIndex = currentIndex++;
448
449 return commonOpTypes;
450 }
451
452 bool
GetXformVectors(GfVec3d * translation,GfVec3f * rotation,GfVec3f * scale,GfVec3f * pivot,RotationOrder * rotOrder,const UsdTimeCode time) const453 UsdGeomXformCommonAPI::GetXformVectors(
454 GfVec3d *translation,
455 GfVec3f *rotation,
456 GfVec3f *scale,
457 GfVec3f *pivot,
458 RotationOrder *rotOrder,
459 const UsdTimeCode time) const
460 {
461 if (!TF_VERIFY(translation && rotation && scale && pivot && rotOrder)) {
462 return false;
463 }
464
465 UsdGeomXformable xformable(GetPrim());
466
467 // Handle incompatible xform case first.
468 // It's ok for an xform to be incompatible when extracting xform vectors.
469 UsdGeomXformOp t, p, r, s;
470 if (!_GetCommonXformOps(xformable, &t, &p, &r, &s)) {
471 GfMatrix4d localXform(1.);
472
473 // Do we want to be able to use a UsdGeomXformCache for this?
474 bool resetsXformStack = false;
475 xformable.GetLocalTransformation(&localXform, &resetsXformStack, time);
476
477 // We don't process (or return) resetsXformStack here. It is up to the
478 // clients to call GetResetXformStack() and process it suitably.
479 _ConvertMatrixToComponents(localXform, translation, rotation, scale);
480
481 *pivot = GfVec3f(0.,0.,0.);
482
483 *rotOrder = RotationOrderXYZ;
484
485 return true;
486 }
487
488 // If any of the ops don't exist or if no value is authored, then returning
489 // identity values.
490
491 if (!t || !t.Get(translation, time)) {
492 *translation = GfVec3d(0.);
493 }
494
495 if (!r || !r.Get(rotation, time)) {
496 *rotation = GfVec3f(0.);
497 }
498
499 if (!s || !s.Get(scale, time)) {
500 *scale = GfVec3f(1.);
501 }
502
503 if (!p || !p.Get(pivot, time)) {
504 *pivot = GfVec3f(0.);
505 }
506
507 *rotOrder = r ? ConvertOpTypeToRotationOrder(r.GetOpType())
508 : RotationOrderXYZ;
509
510 return true;
511 }
512
513 bool
GetXformVectorsByAccumulation(GfVec3d * translation,GfVec3f * rotation,GfVec3f * scale,GfVec3f * pivot,UsdGeomXformCommonAPI::RotationOrder * rotOrder,const UsdTimeCode time) const514 UsdGeomXformCommonAPI::GetXformVectorsByAccumulation(
515 GfVec3d* translation,
516 GfVec3f* rotation,
517 GfVec3f* scale,
518 GfVec3f* pivot,
519 UsdGeomXformCommonAPI::RotationOrder* rotOrder,
520 const UsdTimeCode time) const
521 {
522 // If the xformOps are compatible as authored, then just use the usual
523 // component extraction method.
524 if (_IsCompatible()) {
525 return GetXformVectors(translation, rotation, scale, pivot,
526 rotOrder, time);
527 }
528
529 UsdGeomXformable xformable(GetPrim());
530 bool unusedResetXformStack;
531 const std::vector<UsdGeomXformOp> xformOps =
532 xformable.GetOrderedXformOps(&unusedResetXformStack);
533
534 // Note that we don't currently accumulate rotate ops, so we'll be looking
535 // for one xformOp of a particular rotation type. Any xformOp order with
536 // multiple rotates will be considered not to conform.
537 const UsdGeomXformOp::Type rotateOpType = _GetRotateOpType(xformOps);
538
539 // The xformOp order expected by the common API is:
540 // {Translate, Translate (pivot), Rotate, Scale, Translate (invert pivot)}
541 // Depending on what we find in the xformOps (presence/absence of rotate,
542 // scale(s), and number of inverse translates), we come up with an order
543 // of common op types that we might be able to reduce the xformOps to.
544 // We also maintain some named indices into that order which may be -1
545 // (invalid) if that op is not present.
546 int translateIndex, translatePivotIndex, rotateIndex,
547 translateIdentityIndex, scaleIndex, translatePivotInvertIndex;
548 vector<UsdGeomXformOp::Type> commonOpTypes =
549 _GetCommonOpTypesForOpOrder(xformOps,
550 &translateIndex, &translatePivotIndex, &rotateIndex,
551 &translateIdentityIndex, &scaleIndex, &translatePivotInvertIndex);
552
553 // Keep a set of matrices that we'll accumulate the xformOp transforms into.
554 vector<GfMatrix4d> commonOpMatrices(commonOpTypes.size(), GfMatrix4d(1.0));
555
556 // Scan backwards through the xformOps and list of commonOpTypes
557 // accumulating transforms as we go. We scan backwards so that we
558 // accumulate the inverse pivot first and can then use that to determine
559 // where to split the translates at the front between pivot and non-pivot.
560 int xformOpIndex = xformOps.size() - 1;
561 int commonOpTypeIndex = commonOpTypes.size() - 1;
562
563 while (xformOpIndex >= 0 && commonOpTypeIndex >= translateIndex) {
564 const UsdGeomXformOp xformOp = xformOps[xformOpIndex];
565 UsdGeomXformOp::Type commonOpType = commonOpTypes[commonOpTypeIndex];
566
567 if (xformOp.GetOpType() != commonOpType) {
568 --commonOpTypeIndex;
569 continue;
570 }
571
572 // The current op has the type we expect. Multiply its transform
573 // into the results.
574 commonOpMatrices[commonOpTypeIndex] *= xformOp.GetOpTransform(time);
575 --xformOpIndex;
576
577 if (commonOpType == rotateOpType) {
578 // We currently do not allow rotate ops to accumulate, so as
579 // soon as we match one, advance to the next commonOpType.
580 --commonOpTypeIndex;
581 } else if (commonOpType == UsdGeomXformOp::TypeTranslate) {
582 if (xformOp.IsInverseOp()) {
583 // We use the inverse-ness of translate ops to know when we
584 // should move on to the next common op type. When we see an
585 // inverse translate, we can assume that a valid order will
586 // have its pair farther towards the front.
587 --commonOpTypeIndex;
588 } else if (commonOpTypeIndex == translatePivotIndex &&
589 _MatricesAreInverses(
590 commonOpMatrices[translatePivotIndex],
591 commonOpMatrices[translatePivotInvertIndex])) {
592 // We've found a pair of pivot transforms, so we'll accumulate
593 // the rest of the translates into regular translation.
594 --commonOpTypeIndex;
595 }
596 }
597 }
598
599 bool reducible = true;
600
601 if (xformOpIndex >= translateIndex) {
602 // We didn't make it all the way through the xformOps, so there must
603 // have been something in there that does not conform.
604 reducible = false;
605 }
606
607 // Make sure that any translates between the rotate and scale ops
608 // accumulated to identity.
609 if (translateIdentityIndex >= 0 &&
610 !_IsMatrixIdentity(commonOpMatrices[translateIdentityIndex])) {
611 reducible = false;
612 }
613
614 // If all we saw while scanning were translates, then swap the accumulated
615 // translation matrix from the "Translate (invert pivot)" position into the
616 // "Translate" position.
617 if (commonOpTypeIndex == translatePivotInvertIndex) {
618 commonOpMatrices[translateIndex] = commonOpMatrices[commonOpTypeIndex];
619 commonOpMatrices[commonOpTypeIndex] = GfMatrix4d(1.0);
620 }
621
622 // Verify that the translate pivot and inverse translate pivot are inverses
623 // of each other. If there is no pivot, these should both still be identity.
624 if (!_MatricesAreInverses(commonOpMatrices[translatePivotIndex],
625 commonOpMatrices[translatePivotInvertIndex])) {
626 reducible = false;
627 }
628
629 if (!reducible) {
630 return GetXformVectors(translation, rotation, scale, pivot,
631 rotOrder, time);
632 }
633
634 if (translation) {
635 *translation = commonOpMatrices[translateIndex].ExtractTranslation();
636 }
637
638 if (pivot) {
639 GfVec3d result =
640 commonOpMatrices[translatePivotIndex].ExtractTranslation();
641 *pivot = GfVec3f(result[0], result[1], result[2]);
642 }
643
644 if (rotation) {
645 if (rotateIndex >= 0) {
646 GfRotation accumRot =
647 commonOpMatrices[rotateIndex].ExtractRotation();
648 GfVec3d result = accumRot.Decompose(
649 GfVec3d::XAxis(), GfVec3d::YAxis(), GfVec3d::ZAxis());
650 *rotation = GfVec3f(result[0], result[1], result[2]);
651 } else {
652 *rotation = GfVec3f(0.0, 0.0, 0.0);
653 }
654 }
655
656 if (scale) {
657 if (scaleIndex >= 0) {
658 (*scale)[0] = commonOpMatrices[scaleIndex][0][0];
659 (*scale)[1] = commonOpMatrices[scaleIndex][1][1];
660 (*scale)[2] = commonOpMatrices[scaleIndex][2][2];
661 } else {
662 *scale = GfVec3f(1.0, 1.0, 1.0);
663 }
664 }
665
666 if (rotOrder) {
667 *rotOrder = CanConvertOpTypeToRotationOrder(rotateOpType)
668 ? ConvertOpTypeToRotationOrder(rotateOpType)
669 : UsdGeomXformCommonAPI::RotationOrderXYZ;
670 }
671
672 return true;
673 }
674
675 bool
GetResetXformStack() const676 UsdGeomXformCommonAPI::GetResetXformStack() const
677 {
678 return UsdGeomXformable(GetPrim()).GetResetXformStack();
679 }
680
681 bool
SetResetXformStack(bool resetXformStack) const682 UsdGeomXformCommonAPI::SetResetXformStack(bool resetXformStack) const
683 {
684 return UsdGeomXformable(GetPrim()).SetResetXformStack(resetXformStack);
685 }
686
687 // Retrieves the XformCommonAPI-compatible component ops for the given xformable
688 // prim. Returns true if the ops are in a compatible order or false if they're
689 // in an incompatible order. Populates the non-null out-parameters with the
690 // requested ops. (If an op does not exist, the corresponding out-parameter is
691 // populated with an invalid UsdGeomXformOp.) If resetXformStack is non-null,
692 // populates it with the value of UsdGeomXformable::GetResetXformStack().
693 static
694 bool
_GetCommonXformOps(const UsdGeomXformable & xformable,UsdGeomXformOp * translateOp,UsdGeomXformOp * pivotOp,UsdGeomXformOp * rotateOp,UsdGeomXformOp * scaleOp,UsdGeomXformOp * pivotInvOp,bool * resetXformStack)695 _GetCommonXformOps(
696 const UsdGeomXformable& xformable,
697 UsdGeomXformOp* translateOp,
698 UsdGeomXformOp* pivotOp,
699 UsdGeomXformOp* rotateOp,
700 UsdGeomXformOp* scaleOp,
701 UsdGeomXformOp* pivotInvOp,
702 bool* resetXformStack)
703 {
704 TRACE_FUNCTION();
705
706 bool tempResetXformStack;
707 std::vector<UsdGeomXformOp> xformOps =
708 xformable.GetOrderedXformOps(&tempResetXformStack);
709 if (xformOps.size() > 5)
710 return false;
711
712 // The expected order is:
713 // ["xformOp:translate", "xformOp:translate:pivot", "xformOp:rotateABC",
714 // "xformOp:scale", "!invert!xformOp:translate:pivot"]
715 auto it = xformOps.begin();
716
717 // This holds the computed attribute name tokens so that we can avoid
718 // hard-coding them.
719 // The name for the rotate op is not computed here because it can vary.
720 static const struct {
721 TfToken translate = UsdGeomXformOp::GetOpName(
722 UsdGeomXformOp::TypeTranslate);
723 TfToken pivot = UsdGeomXformOp::GetOpName(
724 UsdGeomXformOp::TypeTranslate, UsdGeomTokens->pivot);
725 TfToken scale = UsdGeomXformOp::GetOpName(
726 UsdGeomXformOp::TypeScale);
727 } attrNames;
728
729 // Search one-by-one for the ops in the correct order.
730 // We can skip ops in the "expected" order (that is, all the common ops are
731 // optional) but we can't skip ops in the "actual" order (that is, extra ops
732 // aren't allowed).
733 //
734 // Note, in checks below, avoid using UsdGeomXformOp::GetOpName() because
735 // it will construct strings in the case of an inverted op.
736 UsdGeomXformOp t;
737 if (it != xformOps.end() &&
738 it->GetName() == attrNames.translate &&
739 !it->IsInverseOp()) {
740 t = std::move(*it);
741 ++it;
742 }
743
744 UsdGeomXformOp p;
745 if (it != xformOps.end() &&
746 it->GetName() == attrNames.pivot &&
747 !it->IsInverseOp()) {
748 p = std::move(*it);
749 ++it;
750 }
751
752 UsdGeomXformOp r;
753 if (it != xformOps.end() &&
754 UsdGeomXformCommonAPI::CanConvertOpTypeToRotationOrder(
755 it->GetOpType()) &&
756 !it->IsInverseOp()) {
757 r = std::move(*it);
758 ++it;
759 }
760
761 UsdGeomXformOp s;
762 if (it != xformOps.end() &&
763 it->GetName() == attrNames.scale &&
764 !it->IsInverseOp()) {
765 s = std::move(*it);
766 ++it;
767 }
768
769 UsdGeomXformOp pInv;
770 if (it != xformOps.end() &&
771 it->GetName() == attrNames.pivot &&
772 it->IsInverseOp()) {
773 pInv = std::move(*it);
774 ++it;
775 }
776
777 // If we did not reach the end of the xformOps vector, then there were
778 // extra ops that did not match any of the expected ops.
779 // This means that the xformOps vector isn't XformCommonAPI-compatible.
780 if (it != xformOps.end()) {
781 return false;
782 }
783
784 // Verify that translate pivot and inverse translate pivot are either both
785 // present or both absent.
786 if ((bool) p != (bool) pInv) {
787 return false;
788 }
789
790 if (translateOp) {
791 *translateOp = std::move(t);
792 }
793
794 if (pivotOp) {
795 *pivotOp = std::move(p);
796 }
797
798 if (rotateOp) {
799 *rotateOp = std::move(r);
800 }
801
802 if (scaleOp) {
803 *scaleOp = std::move(s);
804 }
805
806 if (pivotInvOp) {
807 *pivotInvOp = std::move(pInv);
808 }
809
810 if (resetXformStack) {
811 *resetXformStack = tempResetXformStack;
812 }
813
814 return true;
815 }
816
817 // Similar to _GetCommonXformOps, except also adds ops for any non-null out
818 // parameter whose op does not yet exist. If this returns true, then it
819 // guarantees that every op returned in an out-parameter is valid.
820 //
821 // When creating a rotate op and rotOrder is specified, then it will be used
822 // to choose the rotate op type (or to validate the existing rotate op type).
823 // If rotOrder is not specified, then a rotateXYZ op will be created (or
824 // any existing three-axis rotate returned).
825 static
826 UsdGeomXformCommonAPI::Ops
_GetOrAddCommonXformOps(const UsdGeomXformable & xformable,const UsdGeomXformCommonAPI::RotationOrder * rotOrder,bool createTranslate,bool createPivot,bool createRotate,bool createScale)827 _GetOrAddCommonXformOps(
828 const UsdGeomXformable& xformable,
829 const UsdGeomXformCommonAPI::RotationOrder* rotOrder,
830 bool createTranslate,
831 bool createPivot,
832 bool createRotate,
833 bool createScale)
834 {
835 TRACE_FUNCTION();
836
837 // Can't get or add ops on an xformable with incompatible schema.
838 UsdGeomXformOp t, p, r, s, pInv;
839 bool resetXformStack = false;
840 if (!_GetCommonXformOps(
841 xformable, &t, &p, &r, &s, &pInv, &resetXformStack)) {
842 TF_WARN("Could not determine xform ops for incompatible xformable <%s>",
843 xformable.GetPath().GetText());
844 return UsdGeomXformCommonAPI::Ops();
845 }
846
847 // If creating the rotate op and the rotate op already exists, we must check
848 // that the existing rotation order matches the requested rotation order.
849 // We do this first so that we can early-exit without modifying the xform
850 // op order if we encounter an error.
851 if (createRotate && rotOrder && r) {
852 const UsdGeomXformCommonAPI::RotationOrder existingRotOrder =
853 UsdGeomXformCommonAPI::ConvertOpTypeToRotationOrder(r.GetOpType());
854 if (existingRotOrder != *rotOrder) {
855 TF_CODING_ERROR("Rotation order mismatch on prim <%s> (%s != %s)",
856 xformable.GetPath().GetText(),
857 TfEnum::GetName(*rotOrder).c_str(),
858 TfEnum::GetName(existingRotOrder).c_str());
859 return UsdGeomXformCommonAPI::Ops();
860 }
861 }
862
863 // Add ops if they were requested but the ops do not yet exist.
864 bool addedOps = false;
865 if (createTranslate && !t) {
866 addedOps = true;
867 t = xformable.AddTranslateOp();
868 if (!TF_VERIFY(t)) {
869 return UsdGeomXformCommonAPI::Ops();
870 }
871 }
872 if (createPivot && !p) {
873 addedOps = true;
874 p = xformable.AddTranslateOp(
875 UsdGeomXformOp::PrecisionFloat, UsdGeomTokens->pivot);
876 pInv = xformable.AddTranslateOp(
877 UsdGeomXformOp::PrecisionFloat, UsdGeomTokens->pivot,
878 /* isInverseOp */ true);
879 if (!TF_VERIFY(p && pInv)) {
880 return UsdGeomXformCommonAPI::Ops();
881 }
882 }
883 if (createRotate && !r) {
884 addedOps = true;
885 const UsdGeomXformOp::Type rotateOpType = rotOrder
886 ? UsdGeomXformCommonAPI::ConvertRotationOrderToOpType(*rotOrder)
887 : UsdGeomXformOp::TypeRotateXYZ;
888 r = xformable.AddXformOp(
889 rotateOpType,
890 UsdGeomXformOp::PrecisionFloat);
891 if (!TF_VERIFY(r)) {
892 return UsdGeomXformCommonAPI::Ops();
893 }
894 }
895 if (createScale && !s) {
896 addedOps = true;
897 s = xformable.AddScaleOp();
898 if (!TF_VERIFY(s)) {
899 return UsdGeomXformCommonAPI::Ops();
900 }
901 }
902
903 // Only update the xform op order if we had to add new ops.
904 if (addedOps) {
905 std::vector<UsdGeomXformOp> newXformOps;
906 if (t) newXformOps.push_back(t);
907 if (p) newXformOps.push_back(p);
908 if (r) newXformOps.push_back(r);
909 if (s) newXformOps.push_back(s);
910 if (pInv) newXformOps.push_back(pInv);
911 xformable.SetXformOpOrder(newXformOps, resetXformStack);
912 }
913
914 return UsdGeomXformCommonAPI::Ops {
915 std::move(t),
916 std::move(p),
917 std::move(r),
918 std::move(s),
919 std::move(pInv),
920 };
921 }
922
923 bool
SetTranslate(const GfVec3d & translation,const UsdTimeCode time) const924 UsdGeomXformCommonAPI::SetTranslate(
925 const GfVec3d &translation,
926 const UsdTimeCode time/*=UsdTimeCode::Default()*/) const
927 {
928 // Can't set translate on an xformable with incompatible schema.
929 Ops ops = CreateXformOps(OpTranslate);
930 if (!ops.translateOp) {
931 return false;
932 }
933
934 return ops.translateOp.Set(translation, time);
935 }
936
937 bool
SetPivot(const GfVec3f & pivot,const UsdTimeCode time) const938 UsdGeomXformCommonAPI::SetPivot(
939 const GfVec3f &pivot,
940 const UsdTimeCode time/*=UsdTimeCode::Default()*/) const
941 {
942 // Can't set pivot on an xformable with incompatible schema.
943 Ops ops = CreateXformOps(OpPivot);
944 if (!ops.pivotOp) {
945 return false;
946 }
947
948 return ops.pivotOp.Set(pivot, time);
949 }
950
951 bool
SetRotate(const GfVec3f & rotation,UsdGeomXformCommonAPI::RotationOrder rotOrder,const UsdTimeCode time) const952 UsdGeomXformCommonAPI::SetRotate(
953 const GfVec3f &rotation,
954 UsdGeomXformCommonAPI::RotationOrder rotOrder/*=RotationOrderXYZ*/,
955 const UsdTimeCode time/*=UsdTimeCode::Default()*/) const
956 {
957 // Can't set rotate on an xformable with incompatible schema.
958 Ops ops = CreateXformOps(rotOrder, OpRotate);
959 if (!ops.rotateOp) {
960 return false;
961 }
962
963 return ops.rotateOp.Set(rotation, time);
964 }
965
966 bool
SetScale(const GfVec3f & scale,const UsdTimeCode time) const967 UsdGeomXformCommonAPI::SetScale(
968 const GfVec3f &scale,
969 const UsdTimeCode time/*=UsdTimeCode::Default()*/) const
970 {
971 // Can't set scale on an xformable with incompatible schema.
972 Ops ops = CreateXformOps(OpScale);
973 if (!ops.scaleOp) {
974 return false;
975 }
976
977 return ops.scaleOp.Set(scale, time);
978 }
979
980 UsdGeomXformCommonAPI::Ops
CreateXformOps(RotationOrder rotOrder,OpFlags op1,OpFlags op2,OpFlags op3,OpFlags op4) const981 UsdGeomXformCommonAPI::CreateXformOps(
982 RotationOrder rotOrder,
983 OpFlags op1,
984 OpFlags op2,
985 OpFlags op3,
986 OpFlags op4) const
987 {
988 UsdGeomXformable xformable(GetPrim());
989 if (!xformable) {
990 return UsdGeomXformCommonAPI::Ops();
991 }
992
993 const auto flags = op1 | op2 | op3 | op4;
994 return _GetOrAddCommonXformOps(
995 xformable,
996 &rotOrder,
997 flags & OpTranslate,
998 flags & OpPivot,
999 flags & OpRotate,
1000 flags & OpScale);
1001 }
1002
1003 UsdGeomXformCommonAPI::Ops
CreateXformOps(OpFlags op1,OpFlags op2,OpFlags op3,OpFlags op4) const1004 UsdGeomXformCommonAPI::CreateXformOps(
1005 OpFlags op1,
1006 OpFlags op2,
1007 OpFlags op3,
1008 OpFlags op4) const
1009 {
1010 UsdGeomXformable xformable(GetPrim());
1011 if (!xformable) {
1012 return UsdGeomXformCommonAPI::Ops();
1013 }
1014
1015 const auto flags = op1 | op2 | op3 | op4;
1016 return _GetOrAddCommonXformOps(
1017 xformable,
1018 nullptr,
1019 flags & OpTranslate,
1020 flags & OpPivot,
1021 flags & OpRotate,
1022 flags & OpScale);
1023 }
1024
1025 /* static */
1026 GfMatrix4d
GetRotationTransform(const GfVec3f & rotation,const UsdGeomXformCommonAPI::RotationOrder rotationOrder)1027 UsdGeomXformCommonAPI::GetRotationTransform(
1028 const GfVec3f &rotation,
1029 const UsdGeomXformCommonAPI::RotationOrder rotationOrder)
1030 {
1031 const UsdGeomXformOp::Type rotateOpType =
1032 UsdGeomXformCommonAPI::ConvertRotationOrderToOpType(rotationOrder);
1033 return UsdGeomXformOp::GetOpTransform(rotateOpType, VtValue(rotation));
1034 }
1035
1036 PXR_NAMESPACE_CLOSE_SCOPE
1037
1038