/// <summary> /// Take a single projection segment [CurrentNodeTime, NextNodeTime], break it into parts and merge the /// folded parts into this collection. /// NOTE: the TIC is normalized so beginTime = TimeSpan.Zero and we are already clipped. /// NOTE: projection should have allocated arrays. /// </summary> /// <param name="projection">The output projection.</param> /// <param name="activeDuration">The duration of the active period.</param> /// <param name="periodInTicks">The length of a simple duration in ticks.</param> /// <param name="isAutoReversed">Whether autoreversing is enabled</param> /// <param name="includeMaxPoint">Whether the fill zone forces the max point to be included.</param> private bool ProjectionFoldInterval(ref TimeIntervalCollection projection, Nullable<TimeSpan> activeDuration, long periodInTicks, bool isAutoReversed, bool includeMaxPoint) { // Project the begin point for the segment, then look if we are autoreversing or not. long intervalLength = (NextNodeTime - CurrentNodeTime).Ticks; long timeBeforeNextPeriod, currentProjection; // Now see how the segment falls across periodic boundaries: // Case 1: segment stretches across a full period (we can exit early, since we cover the entire range of values) // Case 2: NON-AUTEREVERSED: segment stretches across two partial periods (we need to split into two segments and insert them into the projection) // Case 2: AUTOREVERSED: we need to pick the larger half of the partial period and project only that half, since it fully overlaps the other. // Case 3: segment is fully contained within a single period (just add the segment into the projection) // These cases are handled very differently for AutoReversing and non-AutoReversing timelines. if (isAutoReversed) // In the autoreversed case, we "fold" the segment onto itself and eliminate the redundant parts { bool beginOnReversingArc; long doublePeriod = periodInTicks << 1; currentProjection = CurrentNodeTime.Ticks % doublePeriod; if (currentProjection < periodInTicks) // We are on a forward-moving segment { beginOnReversingArc = false; timeBeforeNextPeriod = periodInTicks - currentProjection; } else // We are on a reversing segment, adjust the values accordingly { beginOnReversingArc = true; currentProjection = doublePeriod - currentProjection; timeBeforeNextPeriod = currentProjection; } Debug.Assert(timeBeforeNextPeriod > 0); long timeAfterNextPeriod = intervalLength - timeBeforeNextPeriod; // How much of our interval protrudes into the next period(s); this may be negative if we don't reach it. // See which part of the segment -- before or after part -- "dominates" when we fold them unto each other. if (timeAfterNextPeriod > 0) // Case 1 or 2: we reach into the next period but don't know if we completely cover it { bool collectionIsSaturated; if (timeBeforeNextPeriod >= timeAfterNextPeriod) // Before "dominates" { bool includeTime = CurrentNodeIsPoint; if (timeBeforeNextPeriod == timeAfterNextPeriod) // Corner case where before and after overlap exactly, find the IsPoint union { includeTime = includeTime || NextNodeIsPoint; } if (beginOnReversingArc) { projection.MergeInterval(TimeSpan.Zero, true, TimeSpan.FromTicks(currentProjection), includeTime); collectionIsSaturated = includeTime && (currentProjection == periodInTicks); } else { projection.MergeInterval(TimeSpan.FromTicks(currentProjection), includeTime, TimeSpan.FromTicks(periodInTicks), true); collectionIsSaturated = includeTime && (currentProjection == 0); } } else // After "dominates" { if (beginOnReversingArc) { long clippedTime = timeAfterNextPeriod < periodInTicks ? timeAfterNextPeriod : periodInTicks; projection.MergeInterval(TimeSpan.Zero, true, TimeSpan.FromTicks(clippedTime), NextNodeIsPoint); collectionIsSaturated = NextNodeIsPoint && (clippedTime == periodInTicks); } else { long clippedTime = timeAfterNextPeriod < periodInTicks ? periodInTicks - timeAfterNextPeriod : 0; projection.MergeInterval(TimeSpan.FromTicks(clippedTime), NextNodeIsPoint, TimeSpan.FromTicks(periodInTicks), true); collectionIsSaturated = NextNodeIsPoint && (clippedTime == 0); } } return collectionIsSaturated; // See if we just saturated the collection } else // Case 3: timeAfterNextPeriod < 0, we are fully contained in the current period { // No need to split anything, insert the interval directly if (beginOnReversingArc) // Here the nodes are reversed { projection.MergeInterval(TimeSpan.FromTicks(currentProjection - intervalLength), NextNodeIsPoint, TimeSpan.FromTicks(currentProjection), CurrentNodeIsPoint); } else { projection.MergeInterval(TimeSpan.FromTicks(currentProjection), CurrentNodeIsPoint, TimeSpan.FromTicks(currentProjection + intervalLength), NextNodeIsPoint); } return false; // Keep computing the projection } } else // No AutoReversing { currentProjection = CurrentNodeTime.Ticks % periodInTicks; timeBeforeNextPeriod = periodInTicks - currentProjection; // The only way to get 0 is if we clipped by endTime which equals CurrentNodeTime, which should not have been allowed Debug.Assert(intervalLength > 0); if (intervalLength > periodInTicks) // Case 1. We may stretch across a whole arc, even if we start from the end and wrap back around { // Quickly transform the collection into a saturated collection projection._nodeTime[0] = TimeSpan.Zero; projection._nodeIsPoint[0] = true; projection._nodeIsInterval[0] = true; projection._nodeTime[1] = TimeSpan.FromTicks(periodInTicks); projection._nodeIsPoint[1] = includeMaxPoint; projection._nodeIsInterval[1] = false; _count = 2; return true; // Bail early, we have the result ready } else if (intervalLength >= timeBeforeNextPeriod) // Case 2. We stretch until the next period begins (but not long enough to cover the length of a full period) { // Split the segment into two projected segments by wrapping around the period boundary projection.MergeInterval(TimeSpan.FromTicks(currentProjection), CurrentNodeIsPoint, TimeSpan.FromTicks(periodInTicks), false); if (intervalLength > timeBeforeNextPeriod) // See if we have a legitimate interval in the second clipped part { projection.MergeInterval(TimeSpan.Zero, true, TimeSpan.FromTicks(intervalLength - timeBeforeNextPeriod), NextNodeIsPoint); } else if (NextNodeIsPoint) // We only seem to have a point, wrapped around at zero (or in the exceptional case, at the max) { if (includeMaxPoint && activeDuration.HasValue && NextNodeTime == activeDuration) // Exceptional end case: we are exactly at the last point { projection.MergePoint(TimeSpan.FromTicks(periodInTicks)); } else { projection.MergePoint(TimeSpan.Zero); } } return false; // Keep computing the projection } else // Case 3: We fall within a single period { // No need to split anything, insert the interval directly projection.MergeInterval(TimeSpan.FromTicks(currentProjection), CurrentNodeIsPoint, TimeSpan.FromTicks(currentProjection + intervalLength), NextNodeIsPoint); return false; // Keep computing the projection } } }
/// <summary> /// Take a single projection point and insert into the output collection. /// NOTE: projection should have allocated arrays. /// </summary> /// <param name="projection">The output collection.</param> /// <param name="activeDuration">The duration of the active period.</param> /// <param name="periodInTicks">The length of a simple duration in ticks.</param> /// <param name="isAutoReversed">Whether autoreversing is enabled</param> /// <param name="includeMaxPoint">Whether the fill zone forces the max point to be included.</param> private void ProjectionFoldPoint(ref TimeIntervalCollection projection, Nullable<TimeSpan> activeDuration, long periodInTicks, bool isAutoReversed, bool includeMaxPoint) { Debug.Assert(CurrentNodeIsPoint); // We should only call this method when we project a legitimate point Debug.Assert(!CurrentNodeIsInterval); long currentProjection; if (isAutoReversed) // Take autoreversing into account { long doublePeriod = periodInTicks << 1; currentProjection = CurrentNodeTime.Ticks % doublePeriod; if (currentProjection > periodInTicks) { currentProjection = doublePeriod - currentProjection; } } else // No autoReversing { if (includeMaxPoint && activeDuration.HasValue && CurrentNodeTime == activeDuration) { currentProjection = periodInTicks; // Exceptional end case: we are exactly at the last point } else { currentProjection = CurrentNodeTime.Ticks % periodInTicks; } } projection.MergePoint(TimeSpan.FromTicks(currentProjection)); }