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