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/pxr.h"
25 #include "pxr/usd/usdGeom/primvar.h"
26 #include "pxr/usd/usd/prim.h"
27 #include "pxr/usd/usd/relationship.h"
28 
29 #include "pxr/base/tf/registryManager.h"
30 #include "pxr/base/tf/staticTokens.h"
31 
32 #include <vector>
33 
34 PXR_NAMESPACE_OPEN_SCOPE
35 
36 
37 TF_DEFINE_PRIVATE_TOKENS(
38     _tokens,
39     ((primvarsPrefix, "primvars:"))
40     ((idFrom, ":idFrom"))
41     ((indicesSuffix, ":indices"))
42 );
43 
UsdGeomPrimvar(const UsdAttribute & attr)44 UsdGeomPrimvar::UsdGeomPrimvar(const UsdAttribute &attr)
45     : _attr(attr)
46 {
47     _SetIdTargetRelName();
48 }
49 
50 /* static */
51 bool
IsPrimvar(const UsdAttribute & attr)52 UsdGeomPrimvar::IsPrimvar(const UsdAttribute &attr)
53 {
54     if (!attr)
55         return false;
56 
57     return IsValidPrimvarName(attr.GetName());
58 }
59 
60 /* static */
61 bool
IsValidPrimvarName(const TfToken & name)62 UsdGeomPrimvar::IsValidPrimvarName(const TfToken& name)
63 {
64     // All properly namespaced attributes are legal primvars, *except*
65     // the "sidecar" attributes we create as part of the schema, like
66     // "primvars:foo:indices".  We do not need to worry about the idFrom
67     // suffix because it only appears on relationships.
68     return (TfStringStartsWith(name, _tokens->primvarsPrefix) &&
69             !TfStringEndsWith(name, _tokens->indicesSuffix));
70 }
71 
72 /* static */
73 TfToken
StripPrimvarsName(const TfToken & name)74 UsdGeomPrimvar::StripPrimvarsName(const TfToken& name)
75 {
76     std::string const & fullName = name.GetString();
77 
78     std::pair<std::string, bool> res =
79         SdfPath::StripPrefixNamespace(fullName, _tokens->primvarsPrefix);
80 
81     return res.second ? TfToken(res.first) : name;
82 }
83 
84 /* static */
85 bool
_IsNamespaced(const TfToken & name)86 UsdGeomPrimvar::_IsNamespaced(const TfToken& name)
87 {
88     return TfStringStartsWith(name, _tokens->primvarsPrefix);
89 }
90 
91 /* static */
92 TfToken
_MakeNamespaced(const TfToken & name,bool quiet)93 UsdGeomPrimvar::_MakeNamespaced(const TfToken& name, bool quiet)
94 {
95     TfToken  result;
96     if (_IsNamespaced(name)){
97         result = name;
98     }
99     else {
100         result = TfToken(_tokens->primvarsPrefix.GetString() + name.GetString());
101     }
102 
103     if (!IsValidPrimvarName(result)){
104         result = TfToken();
105         if (!quiet){
106             // XXX if we add more reserved keywords we'll need to extract
107             // the offending keyword rather than assume it is "indices".
108             TF_CODING_ERROR("%s is not a valid name for a Primvar, because"
109                             " it contains the reserved name \"indices\"",
110                             name.GetText());
111         }
112     }
113 
114     return result;
115 }
116 
117 /* static */
118 TfToken const&
_GetNamespacePrefix()119 UsdGeomPrimvar::_GetNamespacePrefix()
120 {
121     return _tokens->primvarsPrefix;
122 }
123 
124 TfToken
GetInterpolation() const125 UsdGeomPrimvar::GetInterpolation() const
126 {
127     TfToken interpolation;
128 
129     if (!_attr.GetMetadata(UsdGeomTokens->interpolation, &interpolation)){
130         interpolation = UsdGeomTokens->constant;
131     }
132 
133     return interpolation;
134 }
135 
136 bool
HasAuthoredInterpolation() const137 UsdGeomPrimvar::HasAuthoredInterpolation() const
138 {
139     return _attr.HasAuthoredMetadata(UsdGeomTokens->interpolation);
140 }
141 
142 bool
IsValidInterpolation(const TfToken & interpolation)143 UsdGeomPrimvar::IsValidInterpolation(const TfToken &interpolation)
144 {
145     return ((interpolation == UsdGeomTokens->constant) ||
146             (interpolation == UsdGeomTokens->uniform)  ||
147             (interpolation == UsdGeomTokens->vertex)   ||
148             (interpolation == UsdGeomTokens->varying)  ||
149             (interpolation == UsdGeomTokens->faceVarying));
150 }
151 
152 bool
SetInterpolation(const TfToken & interpolation)153 UsdGeomPrimvar::SetInterpolation(const TfToken &interpolation)
154 {
155     if (!IsValidInterpolation(interpolation)){
156         TF_CODING_ERROR("Attempt to set invalid primvar interpolation "
157                          "\"%s\" for attribute %s",
158                          interpolation.GetText(),
159                          _attr.GetPath().GetString().c_str());
160         return false;
161     }
162     return _attr.SetMetadata(UsdGeomTokens->interpolation, interpolation);
163 }
164 
165 int
GetElementSize() const166 UsdGeomPrimvar::GetElementSize() const
167 {
168     int eltSize = 1;
169     _attr.GetMetadata(UsdGeomTokens->elementSize, &eltSize);
170 
171     return eltSize;
172 }
173 
174 bool
SetElementSize(int eltSize)175 UsdGeomPrimvar::SetElementSize(int eltSize)
176 {
177     if (eltSize < 1){
178         TF_CODING_ERROR("Attempt to set elementSize to %d for attribute "
179                          "%s (must be a positive, non-zero value)",
180                          eltSize,
181                          _attr.GetPath().GetString().c_str());
182         return false;
183     }
184     return _attr.SetMetadata(UsdGeomTokens->elementSize, eltSize);
185 }
186 
187 bool
HasAuthoredElementSize() const188 UsdGeomPrimvar::HasAuthoredElementSize() const
189 {
190     return _attr.HasAuthoredMetadata(UsdGeomTokens->elementSize);
191 }
192 
193 
194 void
GetDeclarationInfo(TfToken * name,SdfValueTypeName * typeName,TfToken * interpolation,int * elementSize) const195 UsdGeomPrimvar::GetDeclarationInfo(TfToken *name, SdfValueTypeName *typeName,
196                                  TfToken *interpolation,
197                                  int *elementSize) const
198 {
199     TF_VERIFY(name && typeName && interpolation && elementSize);
200 
201     // We don't have any more efficient access pattern yet, but at least
202     // we're still saving client some code
203     *name = GetPrimvarName();
204     *typeName = GetTypeName();
205     *interpolation = GetInterpolation();
206     *elementSize = GetElementSize();
207 }
208 
209 UsdAttribute
_GetIndicesAttr(bool create) const210 UsdGeomPrimvar::_GetIndicesAttr(bool create) const
211 {
212     TfToken indicesAttrName(GetName().GetString() +
213         _tokens->indicesSuffix.GetString());
214 
215     if (create) {
216         return _attr.GetPrim().CreateAttribute(indicesAttrName,
217             SdfValueTypeNames->IntArray, /*custom*/ false,
218             SdfVariabilityVarying);
219     }
220     else {
221         return _attr.GetPrim().GetAttribute(indicesAttrName);
222     }
223 }
224 
225 UsdAttribute
GetIndicesAttr() const226 UsdGeomPrimvar::GetIndicesAttr() const
227 {
228     return _GetIndicesAttr(/*create*/ false);
229 }
230 
231 UsdAttribute
CreateIndicesAttr() const232 UsdGeomPrimvar::CreateIndicesAttr() const
233 {
234     return _GetIndicesAttr(/*create*/ true);
235 }
236 
237 bool
SetIndices(const VtIntArray & indices,UsdTimeCode time) const238 UsdGeomPrimvar::SetIndices(const VtIntArray &indices,
239                            UsdTimeCode time) const
240 {
241     // Check if the typeName is array valued here and issue a warning
242     // if it's not.
243     SdfValueTypeName typeName = GetTypeName();
244     if (!typeName.IsArray()) {
245         TF_CODING_ERROR("Setting indices on non-array valued primvar of type "
246             "'%s'.", typeName.GetAsToken().GetText());
247         return false;
248     }
249     return _GetIndicesAttr(/*create*/ true).Set(indices, time);
250 
251 }
252 
253 void
BlockIndices() const254 UsdGeomPrimvar::BlockIndices() const
255 {
256     // Check if the typeName is array valued here and issue a warning
257     // if it's not.
258     SdfValueTypeName typeName = GetTypeName();
259     if (!typeName.IsArray()) {
260         TF_CODING_ERROR("Setting indices on non-array valued primvar of type "
261             "'%s'.", typeName.GetAsToken().GetText());
262         return;
263     }
264     _GetIndicesAttr(/*create*/ true).Block();
265 }
266 
267 bool
GetIndices(VtIntArray * indices,UsdTimeCode time) const268 UsdGeomPrimvar::GetIndices(VtIntArray *indices,
269                            UsdTimeCode time) const
270 {
271     UsdAttribute indicesAttr = _GetIndicesAttr(/*create*/ false);
272     if (indicesAttr)
273         return indicesAttr.Get(indices, time);
274 
275     return false;
276 }
277 
278 bool
IsIndexed() const279 UsdGeomPrimvar::IsIndexed() const
280 {
281     return _GetIndicesAttr(/*create*/ false).HasAuthoredValue();
282 }
283 
284 bool
SetUnauthoredValuesIndex(int unauthoredValuesIndex) const285 UsdGeomPrimvar::SetUnauthoredValuesIndex(int unauthoredValuesIndex) const
286 {
287     return _attr.SetMetadata(UsdGeomTokens->unauthoredValuesIndex,
288                              unauthoredValuesIndex);
289 }
290 
291 int
GetUnauthoredValuesIndex() const292 UsdGeomPrimvar::GetUnauthoredValuesIndex() const
293 {
294     int unauthoredValuesIndex = -1;
295     _attr.GetMetadata(UsdGeomTokens->unauthoredValuesIndex,
296                       &unauthoredValuesIndex);
297 
298     return unauthoredValuesIndex;
299 }
300 
301 // Helper function to evaluate the flattened array value of a primvar given
302 // the attribute value and the indices array.
303 template <typename ArrayType>
304 bool
_ComputeFlattenedArray(const VtValue & attrVal,const VtIntArray & indices,VtValue * value,std::string * errorString)305 UsdGeomPrimvar::_ComputeFlattenedArray(const VtValue &attrVal,
306                        const VtIntArray &indices,
307                        VtValue *value,
308                        std::string *errorString)
309 {
310     if (!attrVal.IsHolding<ArrayType>())
311         return false;
312 
313     ArrayType result;
314     if (_ComputeFlattenedHelper(attrVal.UncheckedGet<ArrayType>(), indices,
315                                 &result, errorString)) {
316         *value = VtValue::Take(result);
317     }
318 
319     return true;
320 }
321 
322 bool
ComputeFlattened(VtValue * value,UsdTimeCode time) const323 UsdGeomPrimvar::ComputeFlattened(VtValue *value, UsdTimeCode time) const
324 {
325     VtValue attrVal;
326     if (!Get(&attrVal, time)) {
327         return false;
328     }
329 
330     // If the primvar attr value is not an array or if the primvar isn't
331     // indexed, simply return the attribute value.
332     if (!attrVal.IsArrayValued() || !IsIndexed()) {
333         *value = VtValue::Take(attrVal);
334         return true;
335     }
336 
337     VtIntArray indices;
338     if (!GetIndices(&indices, time)) {
339         TF_CODING_ERROR("No indices authored for indexed primvar <%s>.",
340                         _attr.GetPath().GetText());
341         return false;
342     }
343 
344     std::string errStr;
345     bool res = ComputeFlattened(value, attrVal, indices, &errStr);
346     if (!errStr.empty()) {
347         TF_WARN("For primvar %s: %s",
348                 UsdDescribe(_attr).c_str(), errStr.c_str());
349     }
350     return res;
351 }
352 
353 bool
ComputeFlattened(VtValue * value,const VtValue & attrVal,const VtIntArray & indices,std::string * errStr)354 UsdGeomPrimvar::ComputeFlattened(
355     VtValue *value,
356     const VtValue &attrVal,
357     const VtIntArray &indices,
358     std::string *errStr)
359 {
360     // If the primvar attr value is not an array simply return the
361     // attribute value.
362     if (!attrVal.IsArrayValued()) {
363         *value = attrVal;
364         return true;
365     }
366 
367     // Handle all known supported array value types.
368     bool foundSupportedType =
369         _ComputeFlattenedArray<VtVec2fArray>(attrVal, indices, value, errStr) ||
370         _ComputeFlattenedArray<VtVec2dArray>(attrVal, indices, value, errStr) ||
371         _ComputeFlattenedArray<VtVec2iArray>(attrVal, indices, value, errStr) ||
372         _ComputeFlattenedArray<VtVec2hArray>(attrVal, indices, value, errStr) ||
373         _ComputeFlattenedArray<VtVec3fArray>(attrVal, indices, value, errStr) ||
374         _ComputeFlattenedArray<VtVec3dArray>(attrVal, indices, value, errStr) ||
375         _ComputeFlattenedArray<VtVec3iArray>(attrVal, indices, value, errStr) ||
376         _ComputeFlattenedArray<VtVec3hArray>(attrVal, indices, value, errStr) ||
377         _ComputeFlattenedArray<VtVec4fArray>(attrVal, indices, value, errStr) ||
378         _ComputeFlattenedArray<VtVec4dArray>(attrVal, indices, value, errStr) ||
379         _ComputeFlattenedArray<VtVec4iArray>(attrVal, indices, value, errStr) ||
380         _ComputeFlattenedArray<VtVec4hArray>(attrVal, indices, value, errStr) ||
381         _ComputeFlattenedArray<VtMatrix3dArray>(attrVal, indices, value,
382                 errStr)    ||
383         _ComputeFlattenedArray<VtMatrix4dArray>(attrVal, indices, value,
384                 errStr)    ||
385         _ComputeFlattenedArray<VtStringArray>(attrVal, indices, value, errStr)||
386         _ComputeFlattenedArray<VtDoubleArray>(attrVal, indices, value, errStr)||
387         _ComputeFlattenedArray<VtIntArray>(attrVal, indices, value, errStr)   ||
388         _ComputeFlattenedArray<VtUIntArray>(attrVal, indices, value, errStr)  ||
389         _ComputeFlattenedArray<VtFloatArray>(attrVal, indices, value, errStr) ||
390         _ComputeFlattenedArray<VtHalfArray>(attrVal, indices, value, errStr);
391 
392     if (!foundSupportedType && errStr) {
393         std::string thisErr = TfStringPrintf(
394             "Unsupported indexed primvar value type %s.",
395             attrVal.GetTypeName().c_str());
396         *errStr = errStr->empty() ? thisErr : *errStr + "\n" + thisErr;
397     }
398 
399     return !value->IsEmpty();
400 }
401 
UsdGeomPrimvar(const UsdPrim & prim,const TfToken & primvarName,const SdfValueTypeName & typeName)402 UsdGeomPrimvar::UsdGeomPrimvar(const UsdPrim& prim,
403                                const TfToken& primvarName,
404                                const SdfValueTypeName &typeName)
405 {
406     TF_VERIFY(prim);
407 
408     TfToken attrName = _MakeNamespaced(primvarName);
409 
410     if (!attrName.IsEmpty()){
411         _attr = prim.CreateAttribute(attrName, typeName, /* custom = */ false);
412     }
413     // If a problem occurred, an error should already have been issued,
414     // and _attr will be invalid, which is what we want
415 
416     _SetIdTargetRelName();
417 }
418 
419 void
_SetIdTargetRelName()420 UsdGeomPrimvar::_SetIdTargetRelName()
421 {
422     if (!_attr) {
423         return;
424     }
425 
426     const SdfValueTypeName& typeName = _attr.GetTypeName();
427     if (typeName == SdfValueTypeNames->String ||
428             typeName == SdfValueTypeNames->StringArray) {
429         std::string name(_attr.GetName().GetString());
430         _idTargetRelName = TfToken(name.append(_tokens->idFrom.GetText()));
431     }
432 }
433 
434 UsdRelationship
_GetIdTargetRel(bool create) const435 UsdGeomPrimvar::_GetIdTargetRel(bool create) const
436 {
437     if (create) {
438         return _attr.GetPrim().CreateRelationship(_idTargetRelName);
439     }
440     else {
441         return _attr.GetPrim().GetRelationship(_idTargetRelName);
442     }
443 }
444 
445 bool
IsIdTarget() const446 UsdGeomPrimvar::IsIdTarget() const
447 {
448     return !_idTargetRelName.IsEmpty() && _GetIdTargetRel(false);
449 }
450 
451 bool
SetIdTarget(const SdfPath & path) const452 UsdGeomPrimvar::SetIdTarget(
453         const SdfPath& path) const
454 {
455     if (_idTargetRelName.IsEmpty()) {
456         TF_CODING_ERROR("Can only set ID Target for string or string[] typed"
457                         " primvars (primvar type is '%s')",
458                         _attr.GetTypeName().GetAsToken().GetText());
459         return false;
460     }
461 
462     if (UsdRelationship rel = _GetIdTargetRel(true)) {
463         SdfPathVector targets;
464         targets.push_back(path.IsEmpty() ? _attr.GetPrimPath() :
465                 path);
466         return rel.SetTargets(targets);
467     }
468     return false;
469 }
470 
471 template <>
472 bool
Get(std::string * value,UsdTimeCode time) const473 UsdGeomPrimvar::Get(
474         std::string* value,
475         UsdTimeCode time) const
476 {
477     // check if there is a relationship and if so use the target path string to
478     // get the string value.
479     if (!_idTargetRelName.IsEmpty()) {
480         if (UsdRelationship rel = _GetIdTargetRel(false)) {
481             SdfPathVector targets;
482             if (rel.GetForwardedTargets(&targets) &&
483                     targets.size() == 1) {
484                 *value = targets[0].GetString();
485                 return true;
486             }
487             return false;
488         }
489     }
490 
491     return _attr.Get(value, time);
492 }
493 
494 // XXX: for now we just take the first.  here's an idea for how
495 // it'd work for multiple targets:
496 //   string[] primvars:handleids (interpolation = "uniform")
497 //   int[]    primvars:handleids:indices = [0, 1, 1, 1, 0, ...., 1]
498 //   rel      primvars:handleids:idFrom = [</a/t1>, </a/t2>]
499 template <>
500 bool
Get(VtStringArray * value,UsdTimeCode time) const501 UsdGeomPrimvar::Get(
502         VtStringArray* value,
503         UsdTimeCode time) const
504 {
505     // check if there is a relationship and if so use the target path string to
506     // get the string value... Just take the first target, for now.
507     if (!_idTargetRelName.IsEmpty()) {
508         if (UsdRelationship rel = _GetIdTargetRel(false)) {
509             value->clear();
510             SdfPathVector targets;
511             if (rel.GetForwardedTargets(&targets) &&
512                     targets.size() > 1) {
513                 value->push_back(targets[0].GetString());
514                 return true;
515             }
516             return false;
517         }
518     }
519 
520     return _attr.Get(value, time);
521 }
522 
523 template <>
524 bool
Get(VtValue * value,UsdTimeCode time) const525 UsdGeomPrimvar::Get(
526         VtValue* value,
527         UsdTimeCode time) const
528 {
529     if (!_idTargetRelName.IsEmpty()) {
530         const SdfValueTypeName& typeName = _attr.GetTypeName();
531         if (typeName == SdfValueTypeNames->String) {
532             std::string s;
533             bool ret = Get(&s, time);
534             if (ret) {
535                 *value = VtValue(s);
536             }
537             return ret;
538         }
539         else if (typeName == SdfValueTypeNames->StringArray) {
540             VtStringArray s;
541             bool ret = Get(&s, time);
542             if (ret) {
543                 *value = VtValue(s);
544             }
545             return ret;
546         }
547     }
548 
549     return _attr.Get(value, time);
550 }
551 
552 bool
GetTimeSamples(std::vector<double> * times) const553 UsdGeomPrimvar::GetTimeSamples(std::vector<double>* times) const
554 {
555     return GetTimeSamplesInInterval(GfInterval::GetFullInterval(), times);
556 }
557 
558 bool
GetTimeSamplesInInterval(const GfInterval & interval,std::vector<double> * times) const559 UsdGeomPrimvar::GetTimeSamplesInInterval(
560     const GfInterval& interval,
561     std::vector<double>* times) const
562 {
563     if (IsIndexed()) {
564         if (UsdAttribute indicesAttr = _GetIndicesAttr(false)) {
565             return UsdAttribute::GetUnionedTimeSamplesInInterval(
566                     {_attr, indicesAttr}, interval, times);
567         }
568     }
569 
570     return _attr.GetTimeSamplesInInterval(interval, times);
571 }
572 
573 bool
ValueMightBeTimeVarying() const574 UsdGeomPrimvar::ValueMightBeTimeVarying() const
575 {
576     if (IsIndexed()) {
577         if (UsdAttribute indicesAttr = _GetIndicesAttr(false)) {
578             if (indicesAttr.ValueMightBeTimeVarying()) {
579                 return true;
580             }
581         }
582     }
583 
584     return _attr.ValueMightBeTimeVarying();
585 }
586 
587 TfToken
GetPrimvarName() const588 UsdGeomPrimvar::GetPrimvarName() const
589 {
590     std::string const & fullName = _attr.GetName().GetString();
591 
592     std::pair<std::string, bool> res =
593         SdfPath::StripPrefixNamespace(fullName, _tokens->primvarsPrefix);
594 
595     return res.second ? TfToken(res.first) : TfToken();
596 }
597 
598 bool
NameContainsNamespaces() const599 UsdGeomPrimvar::NameContainsNamespaces() const
600 {
601     static const size_t primvarsPrefixLen = _tokens->primvarsPrefix.GetString().size();
602     return (_attr.GetName().GetString().find(':', primvarsPrefixLen)
603             != std::string::npos);
604 }
605 
606 
607 PXR_NAMESPACE_CLOSE_SCOPE
608 
609