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