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/usd/common.h"
26 #include "pxr/usd/usd/clip.h"
27 #include "pxr/usd/usd/interpolators.h"
28 
29 #include "pxr/usd/ar/resolver.h"
30 #include "pxr/usd/ar/resolverScopedCache.h"
31 #include "pxr/usd/ar/resolverContextBinder.h"
32 
33 #include "pxr/usd/pcp/layerStack.h"
34 
35 #include "pxr/usd/sdf/layer.h"
36 #include "pxr/usd/sdf/layerUtils.h"
37 #include "pxr/usd/sdf/path.h"
38 
39 #include "pxr/usd/usd/tokens.h"
40 #include "pxr/usd/usd/usdaFileFormat.h"
41 
42 #include "pxr/base/gf/interval.h"
43 #include "pxr/base/tf/stringUtils.h"
44 
45 #include <boost/optional.hpp>
46 
47 #include <ostream>
48 #include <string>
49 #include <vector>
50 
51 #include "pxr/base/arch/pragmas.h"
52 ARCH_PRAGMA_MAYBE_UNINITIALIZED
53 
54 PXR_NAMESPACE_OPEN_SCOPE
55 
56 bool
UsdIsClipRelatedField(const TfToken & fieldName)57 UsdIsClipRelatedField(const TfToken& fieldName)
58 {
59     return fieldName == UsdTokens->clips
60         || fieldName == UsdTokens->clipSets;
61 }
62 
63 std::vector<TfToken>
UsdGetClipRelatedFields()64 UsdGetClipRelatedFields()
65 {
66     return std::vector<TfToken>{
67         UsdTokens->clips,
68         UsdTokens->clipSets
69     };
70 }
71 
72 struct Usd_SortByExternalTime
73 {
74     bool
operator ()Usd_SortByExternalTime75     operator()(const Usd_Clip::TimeMapping& x,
76                const Usd_Clip::ExternalTime y) const
77     {
78         return x.externalTime < y;
79     }
80 
81     bool
operator ()Usd_SortByExternalTime82     operator()(const Usd_Clip::TimeMapping& x,
83                const Usd_Clip::TimeMapping& y) const
84     {
85         return x.externalTime < y.externalTime;
86     }
87 };
88 
89 std::ostream&
operator <<(std::ostream & out,const Usd_ClipRefPtr & clip)90 operator<<(std::ostream& out, const Usd_ClipRefPtr& clip)
91 {
92     out << TfStringPrintf(
93         "%s<%s> (start: %s end: %s)",
94         TfStringify(clip->assetPath).c_str(),
95         clip->primPath.GetString().c_str(),
96         (clip->startTime == Usd_ClipTimesEarliest ?
97             "-inf" : TfStringPrintf("%.3f", clip->startTime).c_str()),
98         (clip->endTime == Usd_ClipTimesLatest ?
99             "inf" : TfStringPrintf("%.3f", clip->endTime).c_str()));
100     return out;
101 }
102 
103 // ------------------------------------------------------------
104 
Usd_Clip()105 Usd_Clip::Usd_Clip()
106     : startTime(0)
107     , endTime(0)
108     , _hasLayer(false)
109 {
110 }
111 
Usd_Clip(const PcpLayerStackPtr & clipSourceLayerStack,const SdfPath & clipSourcePrimPath,size_t clipSourceLayerIndex,const SdfAssetPath & clipAssetPath,const SdfPath & clipPrimPath,ExternalTime clipAuthoredStartTime,ExternalTime clipStartTime,ExternalTime clipEndTime,const TimeMappings & timeMapping)112 Usd_Clip::Usd_Clip(
113     const PcpLayerStackPtr& clipSourceLayerStack,
114     const SdfPath& clipSourcePrimPath,
115     size_t clipSourceLayerIndex,
116     const SdfAssetPath& clipAssetPath,
117     const SdfPath& clipPrimPath,
118     ExternalTime clipAuthoredStartTime,
119     ExternalTime clipStartTime,
120     ExternalTime clipEndTime,
121     const TimeMappings& timeMapping)
122     : sourceLayerStack(clipSourceLayerStack)
123     , sourcePrimPath(clipSourcePrimPath)
124     , sourceLayerIndex(clipSourceLayerIndex)
125     , assetPath(clipAssetPath)
126     , primPath(clipPrimPath)
127     , authoredStartTime(clipAuthoredStartTime)
128     , startTime(clipStartTime)
129     , endTime(clipEndTime)
130     , times(timeMapping)
131 {
132     if (!times.empty()) {
133         // Maintain the relative order of entries with the same stage time for
134         // jump discontinuities in case the authored times array was unsorted.
135         std::stable_sort(times.begin(), times.end(), Usd_SortByExternalTime());
136 
137         // Jump discontinuities are represented by consecutive entries in the
138         // times array with the same stage time, e.g. (10, 10), (10, 0).
139         // We represent this internally as (10 - SafeStep(), 10), (10, 0)
140         // because a lot of the desired behavior just falls out from this
141         // representation.
142         for (size_t i = 0; i < times.size() - 1; ++i) {
143             if (times[i].externalTime == times[i + 1].externalTime) {
144                 times[i].externalTime =
145                     times[i].externalTime - UsdTimeCode::SafeStep();
146                 times[i].isJumpDiscontinuity = true;
147             }
148         }
149 
150         // Add sentinel values to the beginning and end for convenience.
151         times.insert(times.begin(), times.front());
152         times.insert(times.end(), times.back());
153     }
154 
155     // For performance reasons, we want to defer the loading of the layer
156     // for this clip until absolutely needed. However, if the layer happens
157     // to already be opened, we can take advantage of that here.
158     //
159     // This is important for change processing. Clip layers will be kept
160     // alive during change processing, so any clips that are reconstructed
161     // will have the opportunity to reuse the already-opened layer.
162     if (TF_VERIFY(sourceLayerIndex < sourceLayerStack->GetLayers().size())) {
163         const ArResolverContextBinder binder(
164             sourceLayerStack->GetIdentifier().pathResolverContext);
165         _layer = SdfLayer::FindRelativeToLayer(
166             sourceLayerStack->GetLayers()[sourceLayerIndex],
167             assetPath.GetAssetPath());
168     }
169 
170     _hasLayer = (bool)_layer;
171 }
172 
173 // Helper function to determine the linear segment in the given
174 // time mapping that applies to the given time.
175 static bool
_GetBracketingTimeSegment(const Usd_Clip::TimeMappings & times,Usd_Clip::ExternalTime time,size_t * m1,size_t * m2)176 _GetBracketingTimeSegment(
177     const Usd_Clip::TimeMappings& times,
178     Usd_Clip::ExternalTime time,
179     size_t* m1, size_t* m2)
180 {
181     if (times.empty()) {
182         return false;
183     }
184 
185     // This relies on the Usd_Clip c'tor inserting sentinel values at the
186     // beginning and end of the TimeMappings object. Consumers rely on this
187     // function never returning m1 == m2.
188     if (time <= times.front().externalTime) {
189         *m1 = 0;
190         *m2 = 1;
191     }
192     else if (time >= times.back().externalTime) {
193         *m1 = times.size() - 2;
194         *m2 = times.size() - 1;
195     }
196     else {
197         *m2 = std::distance(times.begin(),
198                             std::lower_bound(times.begin(), times.end(),
199                                              time, Usd_SortByExternalTime()));
200         *m1 = *m2 - 1;
201     }
202 
203     TF_VERIFY(*m1 < *m2);
204     TF_VERIFY(0 <= *m1 && *m1 < times.size());
205     TF_VERIFY(0 <= *m2 && *m2 < times.size());
206 
207     return true;
208 }
209 
210 static
211 Usd_Clip::ExternalTime
_GetTime(Usd_Clip::ExternalTime d)212 _GetTime(Usd_Clip::ExternalTime d)
213 {
214     return d;
215 }
216 
217 static
218 Usd_Clip::ExternalTime
_GetTime(const Usd_Clip::TimeMapping & t)219 _GetTime(const Usd_Clip::TimeMapping& t)
220 {
221     return t.externalTime;
222 }
223 
224 static
225 Usd_Clip::TimeMappings::const_iterator
_GetLowerBound(Usd_Clip::TimeMappings::const_iterator begin,Usd_Clip::TimeMappings::const_iterator end,Usd_Clip::ExternalTime time)226 _GetLowerBound(
227     Usd_Clip::TimeMappings::const_iterator begin,
228     Usd_Clip::TimeMappings::const_iterator end,
229     Usd_Clip::ExternalTime time)
230 {
231     return std::lower_bound(
232             begin, end, time,
233             [](const Usd_Clip::TimeMapping& t,
234                const Usd_Clip::ExternalTime e) {
235                 return t.externalTime < e;
236             }
237     );
238 }
239 
240 template <typename Iterator>
241 static
242 Iterator
_GetLowerBound(Iterator begin,Iterator end,Usd_Clip::ExternalTime time)243 _GetLowerBound(
244     Iterator begin, Iterator end, Usd_Clip::ExternalTime time)
245 {
246     return std::lower_bound(begin, end, time);
247 }
248 
249 // XXX: This is taken from sdf/data.cpp with slight modification.
250 // We should provide a free function in sdf to expose this behavior.
251 // This function is different in that it works on time mappings instead
252 // of raw doubles.
253 template <typename Iterator>
254 static
255 bool
_GetBracketingTimeSamples(Iterator begin,Iterator end,const Usd_Clip::ExternalTime time,Usd_Clip::ExternalTime * tLower,Usd_Clip::ExternalTime * tUpper)256 _GetBracketingTimeSamples(
257     Iterator begin, Iterator end,
258     const Usd_Clip::ExternalTime time,
259     Usd_Clip::ExternalTime* tLower,
260     Usd_Clip::ExternalTime* tUpper)
261 {
262     if (begin == end) {
263         return false;
264     }
265 
266     if (time <= _GetTime(*begin)) {
267         // Time is at-or-before the first sample.
268         *tLower = *tUpper = _GetTime(*begin);
269     } else if (time >= _GetTime(*(end - 1))) {
270         // Time is at-or-after the last sample.
271         *tLower = *tUpper = _GetTime(*(end - 1));
272     } else {
273         auto iter = _GetLowerBound(begin, end, time);
274         if (_GetTime(*iter) == time) {
275             // Time is exactly on a sample.
276             *tLower = *tUpper = _GetTime(*iter);
277         } else {
278             // Time is in-between two samples; return the bracketing times.
279             *tUpper = _GetTime(*iter);
280             --iter;
281             *tLower = _GetTime(*iter);
282         }
283     }
284     return true;
285 }
286 
287 bool
_GetBracketingTimeSamplesForPathFromClipLayer(const SdfPath & path,ExternalTime time,ExternalTime * tLower,ExternalTime * tUpper) const288 Usd_Clip::_GetBracketingTimeSamplesForPathFromClipLayer(
289     const SdfPath& path, ExternalTime time,
290     ExternalTime* tLower, ExternalTime* tUpper) const
291 {
292     const SdfLayerRefPtr& clip = _GetLayerForClip();
293     const SdfPath clipPath = _TranslatePathToClip(path);
294     const InternalTime timeInClip = _TranslateTimeToInternal(time);
295     InternalTime lowerInClip, upperInClip;
296 
297     if (!clip->GetBracketingTimeSamplesForPath(
298             clipPath, timeInClip, &lowerInClip, &upperInClip)) {
299         return false;
300     }
301 
302     // Need to translate the time samples in the internal time domain
303     // to the external time domain. The external -> internal mapping
304     // is many-to-one; a given internal time could translate to multiple
305     // external times. We need to look for the translation that is closest
306     // to the time we were given.
307     //
308     // An example case:
309     //
310     // int. time
311     //  -
312     //  |
313     //  |                     m3    m1, m2, m3 are mappings in the times vector
314     //  |                    ,*     s1, s2 are time samples in the clip
315     // s2..................,'
316     //  |                ,'.
317     // i0..............,'  .
318     //  |            ,'.   .
319     //  |          ,*  .   .
320     // s1........,' m2 .   .
321     //  |      ,'      .   .
322     //  |    ,' .      .   .
323     //  |   *   .      .   .
324     //  | m1    .      .   .
325     //  |-------.------.---.------| ext. time
326     //          e1     e0  e2
327     //
328     // Suppose we are asked for bracketing samples at external time t0.
329     // We map this into the internal time domain, which gives us i0. The
330     // bracketing samples for i0 in the internal domain are (s1, s2).
331     //
332     // Now we need to map these back to the external domain. The bracketing
333     // time segment for e0 is (m2, m3). s1 is not in the range of this segment,
334     // so we walk backwards to the previous segment (m1, m2). s1 *is* in the
335     // range of this segment, so we use these mappings to map s1 to e1. For
336     // s2, since s2 is in the range of (m2, m3), we use those mappings to map
337     // s2 to e2. So, our final answer is (e1, e2).
338     size_t m1, m2;
339     if (!_GetBracketingTimeSegment(times, time, &m1, &m2)) {
340         *tLower = lowerInClip;
341         *tUpper = upperInClip;
342         return true;
343     }
344 
345     boost::optional<ExternalTime> translatedLower, translatedUpper;
346     auto _CanTranslate = [&time, &upperInClip, &lowerInClip, this,
347                           &translatedLower, &translatedUpper](
348         const TimeMappings& mappings, size_t i1, size_t i2,
349         const bool translatingLower)
350     {
351         const TimeMapping& map1 = mappings[i1];
352         const TimeMapping& map2 = mappings[i2];
353 
354         // If this segment is a jump discontinuity it should not be used
355         // to map any internal times to external times.
356         if (map1.isJumpDiscontinuity) {
357             return false;
358         }
359 
360         const InternalTime timeInClip =
361             translatingLower ? lowerInClip : upperInClip;
362         auto& translated = translatingLower ? translatedLower : translatedUpper;
363 
364         const InternalTime lower =
365             std::min(map1.internalTime, map2.internalTime);
366         const InternalTime upper =
367             std::max(map1.internalTime, map2.internalTime);
368 
369         if (lower <= timeInClip && timeInClip <= upper) {
370             if (map1.internalTime != map2.internalTime) {
371                 translated.reset(
372                     this->_TranslateTimeToExternal(timeInClip, i1, i2));
373             } else {
374                 const bool lowerUpperMatch = (lowerInClip == upperInClip);
375                 if (lowerUpperMatch && time == map1.externalTime) {
376                     translated.reset(map1.externalTime);
377                 } else if (lowerUpperMatch && time == map2.externalTime) {
378                     translated.reset(map2.externalTime);
379                 } else {
380                     if (translatingLower) {
381                         translated.reset(map1.externalTime);
382                     } else {
383                         translated.reset(map2.externalTime);
384                     }
385                 }
386             }
387         }
388         return static_cast<bool>(translated);
389     };
390 
391     for (int i1 = m1, i2 = m2; i1 >= 0 && i2 >= 0; --i1, --i2) {
392         if (_CanTranslate(times, i1, i2, /*lower=*/true)) { break; }
393     }
394 
395     for (size_t i1 = m1, i2 = m2, sz = times.size(); i1 < sz && i2 < sz; ++i1, ++i2) {
396         if (_CanTranslate(times, i1, i2, /*lower=*/false)) { break; }
397     }
398 
399     if (translatedLower && !translatedUpper) {
400         translatedUpper = translatedLower;
401     }
402     else if (!translatedLower && translatedUpper) {
403         translatedLower = translatedUpper;
404     }
405     else if (!translatedLower && !translatedUpper) {
406         // If we haven't been able to translate either internal time, it's
407         // because they are outside the range of the clip time mappings. We
408         // clamp them to the nearest external time to match the behavior of
409         // SdfLayer::GetBracketingTimeSamples.
410         //
411         // The issue here is that the clip may not have a sample at these
412         // times. Usd_Clip::QueryTimeSample does a secondary step of finding
413         // the corresponding time sample if it determines this is the case.
414         //
415         // The 'timingOutsideClip' test case in testUsdModelClips exercises
416         // this behavior.
417         if (lowerInClip < times.front().internalTime) {
418             translatedLower.reset(times.front().externalTime);
419         }
420         else if (lowerInClip > times.back().internalTime) {
421             translatedLower.reset(times.back().externalTime);
422         }
423 
424         if (upperInClip < times.front().internalTime) {
425             translatedUpper.reset(times.front().externalTime);
426         }
427         else if (upperInClip > times.back().internalTime) {
428             translatedUpper.reset(times.back().externalTime);
429         }
430     }
431 
432     *tLower = *translatedLower;
433     *tUpper = *translatedUpper;
434     return true;
435 }
436 
437 bool
GetBracketingTimeSamplesForPath(const SdfPath & path,ExternalTime time,ExternalTime * tLower,ExternalTime * tUpper) const438 Usd_Clip::GetBracketingTimeSamplesForPath(
439     const SdfPath& path, ExternalTime time,
440     ExternalTime* tLower, ExternalTime* tUpper) const
441 {
442     std::array<Usd_Clip::ExternalTime, 5> bracketingTimes = { 0.0 };
443     size_t numTimes = 0;
444 
445     // Add time samples from the clip layer.
446     if (_GetBracketingTimeSamplesForPathFromClipLayer(
447             path, time,
448             &bracketingTimes[numTimes], &bracketingTimes[numTimes + 1])) {
449         numTimes += 2;
450     }
451 
452     // Each external time in the clip times array is considered a time
453     // sample.
454     if (_GetBracketingTimeSamples(
455             times.cbegin(), times.cend(), time,
456             &bracketingTimes[numTimes], &bracketingTimes[numTimes + 1])) {
457         numTimes += 2;
458     }
459 
460     // Clips introduce time samples at their start time even
461     // if time samples don't actually exist. This isolates each
462     // clip from its neighbors and means that value resolution
463     // never has to look at more than one clip to answer a
464     // time sample query.
465     bracketingTimes[numTimes] = authoredStartTime;
466     numTimes++;
467 
468     // Remove bracketing times that are outside the clip's active range.
469     {
470         auto removeIt = std::remove_if(
471             bracketingTimes.begin(), bracketingTimes.begin() + numTimes,
472             [this](ExternalTime t) { return t < startTime || t >= endTime; });
473         numTimes = std::distance(bracketingTimes.begin(), removeIt);
474     }
475 
476     if (numTimes == 0) {
477         return false;
478     }
479     else if (numTimes == 1) {
480         *tLower = *tUpper = bracketingTimes[0];
481         return true;
482     }
483 
484     std::sort(bracketingTimes.begin(), bracketingTimes.begin() + numTimes);
485     auto uniqueIt = std::unique(
486         bracketingTimes.begin(), bracketingTimes.begin() + numTimes);
487     return _GetBracketingTimeSamples(
488         bracketingTimes.begin(), uniqueIt, time, tLower, tUpper);
489 }
490 
491 size_t
GetNumTimeSamplesForPath(const SdfPath & path) const492 Usd_Clip::GetNumTimeSamplesForPath(const SdfPath& path) const
493 {
494     // XXX: This is simple but inefficient. However, this function is
495     // currently only used in one corner case in UsdStage, see
496     // _ValueFromClipsMightBeTimeVarying. So for now, we can just
497     // go with this until it becomes a bigger performance concern.
498     return ListTimeSamplesForPath(path).size();
499 }
500 
501 void
_ListTimeSamplesForPathFromClipLayer(const SdfPath & path,std::set<ExternalTime> * timeSamples) const502 Usd_Clip::_ListTimeSamplesForPathFromClipLayer(
503     const SdfPath& path,
504     std::set<ExternalTime>* timeSamples) const
505 {
506     std::set<InternalTime> timeSamplesInClip =
507         _GetLayerForClip()->ListTimeSamplesForPath(_TranslatePathToClip(path));
508     if (times.empty()) {
509         *timeSamples = std::move(timeSamplesInClip);
510 
511         // Filter out all samples that are outside the clip's active range
512         timeSamples->erase(
513             timeSamples->begin(), timeSamples->lower_bound(startTime));
514         timeSamples->erase(
515             timeSamples->lower_bound(endTime), timeSamples->end());
516         return;
517     }
518 
519     // A clip is active in the time range [startTime, endTime).
520     const GfInterval clipTimeInterval(
521         startTime, endTime, /* minClosed = */ true, /* maxClosed = */ false);
522 
523     // We need to convert the internal time samples to the external
524     // domain using the clip's time mapping. This is tricky because the
525     // mapping is many-to-one: multiple external times may map to the
526     // same internal time, e.g. mapping { 0:5, 5:10, 10:5 }.
527     //
528     // To deal with this, every internal time sample has to be checked
529     // against the entire mapping function.
530     for (InternalTime t: timeSamplesInClip) {
531         for (size_t i = 0; i < times.size() - 1; ++i) {
532             const TimeMapping& m1 = times[i];
533             const TimeMapping& m2 = times[i+1];
534 
535             // Ignore time mappings whose external time domain does not
536             // intersect the times at which this clip is active.
537             const GfInterval mappingInterval(m1.externalTime, m2.externalTime);
538             if (!mappingInterval.Intersects(clipTimeInterval)) {
539                 continue;
540             }
541 
542             // If this segment is a jump discontinuity it should not be used
543             // to map any internal times to external times.
544             if (m1.isJumpDiscontinuity) {
545                 continue;
546             }
547 
548             if (std::min(m1.internalTime, m2.internalTime) <= t
549                 && t <= std::max(m1.internalTime, m2.internalTime)) {
550                 if (m1.internalTime == m2.internalTime) {
551                     if (clipTimeInterval.Contains(m1.externalTime)) {
552                         timeSamples->insert(m1.externalTime);
553                     }
554                     if (clipTimeInterval.Contains(m2.externalTime)) {
555                         timeSamples->insert(m2.externalTime);
556                     }
557                 }
558                 else {
559                     const ExternalTime extTime =
560                         _TranslateTimeToExternal(t, i, i+1);
561                     if (clipTimeInterval.Contains(extTime)) {
562                         timeSamples->insert(extTime);
563                     }
564                 }
565             }
566         }
567     }
568 }
569 
570 std::set<Usd_Clip::ExternalTime>
ListTimeSamplesForPath(const SdfPath & path) const571 Usd_Clip::ListTimeSamplesForPath(const SdfPath& path) const
572 {
573     // Retrieve time samples from the clip layer mapped to external times.
574     std::set<ExternalTime> timeSamples;
575     _ListTimeSamplesForPathFromClipLayer(path, &timeSamples);
576 
577     // Each entry in the clip's time mapping is considered a time sample,
578     // so add them in here.
579     for (const TimeMapping& t : times) {
580         if (startTime <= t.externalTime && t.externalTime < endTime) {
581             timeSamples.insert(t.externalTime);
582         }
583     }
584 
585     // Clips introduce time samples at their start time to
586     // isolate them from surrounding clips.
587     //
588     // See GetBracketingTimeSamplesForPath for more details.
589     timeSamples.insert(authoredStartTime);
590 
591     return timeSamples;
592 }
593 
594 bool
HasField(const SdfPath & path,const TfToken & field) const595 Usd_Clip::HasField(const SdfPath& path, const TfToken& field) const
596 {
597     return _GetLayerForClip()->HasField(_TranslatePathToClip(path), field);
598 }
599 
600 bool
HasAuthoredTimeSamples(const SdfPath & path) const601 Usd_Clip::HasAuthoredTimeSamples(const SdfPath& path) const
602 {
603     return _GetLayerForClip()->GetNumTimeSamplesForPath(
604         _TranslatePathToClip(path)) > 0;
605 }
606 
607 bool
IsBlocked(const SdfPath & path,ExternalTime time) const608 Usd_Clip::IsBlocked(const SdfPath& path, ExternalTime time) const
609 {
610     SdfAbstractDataTypedValue<SdfValueBlock> blockValue(nullptr);
611     if (_GetLayerForClip()->QueryTimeSample(
612             path, _TranslateTimeToInternal(time),
613             (SdfAbstractDataValue*)&blockValue)
614         && blockValue.isValueBlock) {
615         return true;
616     }
617     return false;
618 }
619 
620 SdfPath
_TranslatePathToClip(const SdfPath & path) const621 Usd_Clip::_TranslatePathToClip(const SdfPath& path) const
622 {
623     return path.ReplacePrefix(sourcePrimPath, primPath);
624 }
625 
626 static Usd_Clip::InternalTime
_TranslateTimeToInternalHelper(Usd_Clip::ExternalTime extTime,const Usd_Clip::TimeMapping & m1,const Usd_Clip::TimeMapping & m2)627 _TranslateTimeToInternalHelper(
628     Usd_Clip::ExternalTime extTime,
629     const Usd_Clip::TimeMapping& m1,
630     const Usd_Clip::TimeMapping& m2)
631 {
632     // Early out in some special cases to avoid unnecessary
633     // math operations that could introduce precision issues.
634     if (m1.externalTime == m2.externalTime) {
635         return m1.internalTime;
636     }
637     else if (extTime == m1.externalTime) {
638         return m1.internalTime;
639     }
640     else if (extTime == m2.externalTime) {
641         return m2.internalTime;
642     }
643 
644     return (m2.internalTime - m1.internalTime) /
645            (m2.externalTime - m1.externalTime)
646         * (extTime - m1.externalTime)
647         + m1.internalTime;
648 }
649 
650 Usd_Clip::InternalTime
_TranslateTimeToInternal(ExternalTime extTime) const651 Usd_Clip::_TranslateTimeToInternal(ExternalTime extTime) const
652 {
653     size_t i1, i2;
654     if (!_GetBracketingTimeSegment(times, extTime, &i1, &i2)) {
655         return extTime;
656     }
657 
658     const TimeMapping& m1 = times[i1];
659     const TimeMapping& m2 = times[i2];
660 
661     // If the time segment ends on the left side of a jump discontinuity
662     // we use the authored external time for the translation.
663     //
664     // For example, if the authored times metadata looked like:
665     //   [(0, 0), (10, 10), (10, 0), ...]
666     //
667     // Our time mappings would be:
668     //   [(0, 0), (9.99..., 10), (10, 0), ...]
669     //
670     // Let's say we had a clip with a time sample at t = 3. If we were
671     // to query the attribute at extTime = 3, using the time mappings as-is
672     // would lead us to use the mappings (0, 0) and (9.99..., 10) to
673     // translate to an internal time. This would give a translated internal
674     // time like 3.00000001. Since the clip doesn't have a time sample at
675     // that exact time, QueryTimeSample would wind up performing additional
676     // interpolation, which decreases performance and also introduces
677     // precision errors.
678     //
679     // With this code, we wind up translating using the mappings
680     // (0, 0) and (10, 10), which gives a translated internal time of 3.
681     // This avoids all of the issues above and more closely matches the intent
682     // expressed in the authored times metadata.
683     if (m2.isJumpDiscontinuity) {
684         TF_VERIFY(i2 + 1 < times.size());
685         const TimeMapping& m3 = times[i2 + 1];
686         return _TranslateTimeToInternalHelper(
687             extTime, m1, TimeMapping(m3.externalTime, m2.internalTime));
688     }
689 
690     return _TranslateTimeToInternalHelper(extTime, m1, m2);
691 }
692 
693 static Usd_Clip::ExternalTime
_TranslateTimeToExternalHelper(Usd_Clip::InternalTime intTime,const Usd_Clip::TimeMapping & m1,const Usd_Clip::TimeMapping & m2)694 _TranslateTimeToExternalHelper(
695     Usd_Clip::InternalTime intTime,
696     const Usd_Clip::TimeMapping& m1,
697     const Usd_Clip::TimeMapping& m2)
698 {
699     // Early out in some special cases to avoid unnecessary
700     // math operations that could introduce precision issues.
701     if (m1.internalTime == m2.internalTime) {
702         return m1.externalTime;
703     }
704     else if (intTime == m1.internalTime) {
705         return m1.externalTime;
706     }
707     else if (intTime == m2.internalTime) {
708         return m2.externalTime;
709     }
710 
711     return (m2.externalTime - m1.externalTime) /
712            (m2.internalTime - m1.internalTime)
713         * (intTime - m1.internalTime)
714         + m1.externalTime;
715 }
716 
717 Usd_Clip::ExternalTime
_TranslateTimeToExternal(InternalTime intTime,size_t i1,size_t i2) const718 Usd_Clip::_TranslateTimeToExternal(
719     InternalTime intTime, size_t i1, size_t i2) const
720 {
721     const TimeMapping& m1 = times[i1];
722     const TimeMapping& m2 = times[i2];
723 
724     // Clients should never be trying to map an internal time through a jump
725     // discontinuity.
726     TF_VERIFY(!m1.isJumpDiscontinuity);
727 
728     // If the time segment ends on the left side of a jump discontinuity,
729     // we use the authored external time for the translation.
730     //
731     // For example, if the authored times metadata looked like:
732     //   [(0, 0), (10, 10), (10, 0), ...]
733     //
734     // Our time mappings would be:
735     //   [(0, 0), (9.99..., 10), (10, 0), ...]
736     //
737     // Let's say we had a clip with a time sample at t = 3. If we were to
738     // query the attribute's time samples, using the time mappings as-is
739     // would lead us to use the mappings (0, 0) and (9.99..., 10) to translate
740     // to an external time. This would give us a translated external time like
741     // 2.999999, which is unexpected. If this value was used to query for
742     // attribute values, we would run into the same issues described in
743     // _TranslateTimeToInternal.
744     //
745     // With this code, we wind up translating using the mappings
746     // (0, 0) and (10, 10), which gives a translated external time of 3.
747     // This avoids all of the issues above and more closely matches the intent
748     // expressed in the authored times metadata.
749     if (m2.isJumpDiscontinuity) {
750         TF_VERIFY(i2 + 1 < times.size());
751         const TimeMapping& m3 = times[i2 + 1];
752         return _TranslateTimeToExternalHelper(
753             intTime, m1, TimeMapping(m3.externalTime, m2.internalTime));
754     }
755 
756     return _TranslateTimeToExternalHelper(intTime, m1, m2);
757 }
758 
759 SdfPropertySpecHandle
GetPropertyAtPath(const SdfPath & path) const760 Usd_Clip::GetPropertyAtPath(const SdfPath &path) const
761 {
762     return _GetLayerForClip()->GetPropertyAtPath(_TranslatePathToClip(path));
763 }
764 
765 TF_DEFINE_PRIVATE_TOKENS(
766     _tokens,
767     (dummy_clip)
768     ((dummy_clipFormat, "dummy_clip.%s"))
769     );
770 
771 SdfLayerRefPtr
_GetLayerForClip() const772 Usd_Clip::_GetLayerForClip() const
773 {
774     if (_hasLayer) {
775         return _layer;
776     }
777 
778     SdfLayerRefPtr layer;
779 
780     if (TF_VERIFY(sourceLayerIndex < sourceLayerStack->GetLayers().size())) {
781         const ArResolverContextBinder binder(
782             sourceLayerStack->GetIdentifier().pathResolverContext);
783         layer = SdfLayer::FindOrOpenRelativeToLayer(
784             sourceLayerStack->GetLayers()[sourceLayerIndex],
785             assetPath.GetAssetPath());
786     }
787 
788     if (!layer) {
789         // If we failed to open the specified layer, report an error
790         // and use a dummy anonymous layer instead, to avoid having
791         // to check layer validity everywhere and to avoid reissuing
792         // this error.
793         // XXX: Better way to report this error?
794         TF_WARN("Unable to open clip layer @%s@",
795                 assetPath.GetAssetPath().c_str());
796         layer = SdfLayer::CreateAnonymous(TfStringPrintf(
797                      _tokens->dummy_clipFormat.GetText(),
798                      UsdUsdaFileFormatTokens->Id.GetText()));
799     }
800 
801     std::lock_guard<std::mutex> lock(_layerMutex);
802     if (!_layer) {
803         _layer = layer;
804         _hasLayer = true;
805     }
806 
807     return _layer;
808 }
809 
810 SdfLayerHandle
GetLayer() const811 Usd_Clip::GetLayer() const
812 {
813     const SdfLayerRefPtr& layer = _GetLayerForClip();
814     return TfStringStartsWith(layer->GetIdentifier(),
815                               _tokens->dummy_clip.GetString()) ?
816         SdfLayerHandle() : SdfLayerHandle(layer);
817 }
818 
819 SdfLayerHandle
GetLayerIfOpen() const820 Usd_Clip::GetLayerIfOpen() const
821 {
822     if (!_hasLayer) {
823         return SdfLayerHandle();
824     }
825     return GetLayer();
826 }
827 
828 namespace { // Anonymous namespace
829 
830 // SdfTimeCode values from clips need to be converted from internal time to
831 // external time. We treat time code values as relative to the internal time
832 // to convert to external.
833 inline
834 void
_ConvertValueForTime(const Usd_Clip::ExternalTime & extTime,const Usd_Clip::InternalTime & intTime,SdfTimeCode * value)835 _ConvertValueForTime(const Usd_Clip::ExternalTime &extTime,
836                      const Usd_Clip::InternalTime &intTime,
837                      SdfTimeCode *value)
838 {
839     *value = *value + (extTime - intTime);
840 }
841 
842 // Similarly we convert arrays of SdfTimeCodes.
843 inline
844 void
_ConvertValueForTime(const Usd_Clip::ExternalTime & extTime,const Usd_Clip::InternalTime & intTime,VtArray<SdfTimeCode> * value)845 _ConvertValueForTime(const Usd_Clip::ExternalTime &extTime,
846                      const Usd_Clip::InternalTime &intTime,
847                      VtArray<SdfTimeCode> *value)
848 {
849     for (size_t i = 0; i < value->size(); ++i) {
850         _ConvertValueForTime(extTime, intTime, &(*value)[i]);
851     }
852 }
853 
854 // Helpers for accessing the typed value from type erased values, needed for
855 // converting SdfTimeCodes.
856 template <class T>
857 inline
_UncheckedSwap(SdfAbstractDataValue * value,T & val)858 void _UncheckedSwap(SdfAbstractDataValue *value, T& val) {
859     std::swap(*static_cast<T*>(value->value), val);
860 }
861 
862 template <class T>
863 inline
_UncheckedSwap(VtValue * value,T & val)864 void _UncheckedSwap(VtValue *value, T& val) {
865     value->UncheckedSwap(val);
866 }
867 
868 template <class T>
869 inline
_IsHolding(const SdfAbstractDataValue & value)870 bool _IsHolding(const SdfAbstractDataValue &value) {
871     return TfSafeTypeCompare(typeid(T), value.valueType);
872 }
873 
874 template <class T>
875 inline
_IsHolding(const VtValue & value)876 bool _IsHolding(const VtValue &value) {
877     return value.IsHolding<T>();
878 }
879 
880 // For type erased values, we need to convert them if they hold SdfTimeCode
881 // based types.
882 template <class Storage>
883 inline
884 void
_ConvertTypeErasedValueForTime(const Usd_Clip::ExternalTime & extTime,const Usd_Clip::InternalTime & intTime,Storage * value)885 _ConvertTypeErasedValueForTime(const Usd_Clip::ExternalTime &extTime,
886                                const Usd_Clip::InternalTime &intTime,
887                                Storage *value)
888 {
889     if (_IsHolding<SdfTimeCode>(*value)) {
890         SdfTimeCode rawVal;
891         _UncheckedSwap(value, rawVal);
892         _ConvertValueForTime(extTime, intTime, &rawVal);
893         _UncheckedSwap(value, rawVal);
894     } else if (_IsHolding<VtArray<SdfTimeCode>>(*value)) {
895         VtArray<SdfTimeCode> rawVal;
896         _UncheckedSwap(value, rawVal);
897         _ConvertValueForTime(extTime, intTime, &rawVal);
898         _UncheckedSwap(value, rawVal);
899     }
900 }
901 
902 void
_ConvertValueForTime(const Usd_Clip::ExternalTime & extTime,const Usd_Clip::InternalTime & intTime,VtValue * value)903 _ConvertValueForTime(const Usd_Clip::ExternalTime &extTime,
904                      const Usd_Clip::InternalTime &intTime,
905                      VtValue *value)
906 {
907     _ConvertTypeErasedValueForTime(extTime, intTime, value);
908 }
909 
910 void
_ConvertValueForTime(const Usd_Clip::ExternalTime & extTime,const Usd_Clip::InternalTime & intTime,SdfAbstractDataValue * value)911 _ConvertValueForTime(const Usd_Clip::ExternalTime &extTime,
912                      const Usd_Clip::InternalTime &intTime,
913                      SdfAbstractDataValue *value)
914 {
915     _ConvertTypeErasedValueForTime(extTime, intTime, value);
916 }
917 
918 // Fallback no-op default for the rest of the value types; there is no time
919 // conversion necessary for non-timecode types.
920 template <class T>
921 inline
_ConvertValueForTime(const Usd_Clip::ExternalTime & extTime,const Usd_Clip::InternalTime & intTime,T * value)922 void _ConvertValueForTime(const Usd_Clip::ExternalTime &extTime,
923                           const Usd_Clip::InternalTime &intTime,
924                           T *value)
925 {
926 }
927 
928 template <class T>
929 static bool
_Interpolate(const SdfLayerRefPtr & clip,const SdfPath & clipPath,Usd_Clip::InternalTime clipTime,Usd_InterpolatorBase * interpolator,T * value)930 _Interpolate(
931     const SdfLayerRefPtr& clip, const SdfPath &clipPath,
932     Usd_Clip::InternalTime clipTime, Usd_InterpolatorBase* interpolator,
933     T* value)
934 {
935     double lowerInClip, upperInClip;
936     if (clip->GetBracketingTimeSamplesForPath(
937             clipPath, clipTime, &lowerInClip, &upperInClip)) {
938 
939         return Usd_GetOrInterpolateValue(
940             clip, clipPath, clipTime, lowerInClip, upperInClip,
941             interpolator, value);
942     }
943 
944     return false;
945 }
946 
947 }; // End anonymous namespace
948 
949 template <class T>
950 bool
QueryTimeSample(const SdfPath & path,ExternalTime time,Usd_InterpolatorBase * interpolator,T * value) const951 Usd_Clip::QueryTimeSample(
952     const SdfPath& path, ExternalTime time,
953     Usd_InterpolatorBase* interpolator, T* value) const
954 {
955     const SdfPath clipPath = _TranslatePathToClip(path);
956     const InternalTime clipTime = _TranslateTimeToInternal(time);
957     const SdfLayerRefPtr& clip = _GetLayerForClip();
958 
959     if (!clip->QueryTimeSample(clipPath, clipTime, value)) {
960         // See comment in Usd_Clip::GetBracketingTimeSamples.
961         if (!_Interpolate(clip, clipPath, clipTime, interpolator, value)) {
962             return false;
963         }
964     }
965 
966     // Convert values containing SdfTimeCodes if necessary.
967     _ConvertValueForTime(time, clipTime, value);
968     return true;
969 }
970 
971 #define _INSTANTIATE_QUERY_TIME_SAMPLE(r, unused, elem)         \
972     template bool Usd_Clip::QueryTimeSample(                    \
973         const SdfPath&, Usd_Clip::ExternalTime,                 \
974         Usd_InterpolatorBase*,                                  \
975         SDF_VALUE_CPP_TYPE(elem)*) const;                       \
976     template bool Usd_Clip::QueryTimeSample(                    \
977         const SdfPath&, Usd_Clip::ExternalTime,                 \
978         Usd_InterpolatorBase*,                                  \
979         SDF_VALUE_CPP_ARRAY_TYPE(elem)*) const;
980 
981 BOOST_PP_SEQ_FOR_EACH(_INSTANTIATE_QUERY_TIME_SAMPLE, ~, SDF_VALUE_TYPES)
982 #undef _INSTANTIATE_QUERY_TIME_SAMPLE
983 
984 template bool Usd_Clip::QueryTimeSample(
985     const SdfPath&, Usd_Clip::ExternalTime,
986     Usd_InterpolatorBase*,
987     SdfAbstractDataValue*) const;
988 
989 template bool Usd_Clip::QueryTimeSample(
990     const SdfPath&, Usd_Clip::ExternalTime,
991     Usd_InterpolatorBase*,
992     VtValue*) const;
993 
994 PXR_NAMESPACE_CLOSE_SCOPE
995 
996