public override float GetNoteY(RuntimeNote note, float now, NoteMetrics noteMetrics, NoteAnimationMetrics animationMetrics) { var timePoints = NoteAnimationHelper.CalculateNoteTimePoints(note, animationMetrics); var onStageStatus = NoteAnimationHelper.GetOnStageStatusOf(note, now, timePoints); float y; switch (onStageStatus) { case OnStageStatus.Incoming: y = animationMetrics.Top; break; case OnStageStatus.Visible: y = GetNoteOnStageY(note, now, timePoints, animationMetrics); break; case OnStageStatus.Passed: y = animationMetrics.Bottom; break; default: throw new ArgumentOutOfRangeException(); } return(y); }
public override RibbonParameters GetHoldRibbonParameters(RuntimeNote startNote, RuntimeNote endNote, float now, NoteMetrics noteMetrics, NoteAnimationMetrics animationMetrics) { var tp1 = NoteAnimationHelper.CalculateNoteTimePoints(startNote, animationMetrics); var tp2 = NoteAnimationHelper.CalculateNoteTimePoints(endNote, animationMetrics); var t1 = GetTransformedTime(startNote, now, tp1); var t2 = GetTransformedTime(endNote, now, tp2); var tperc = startNote.IsHold() ? 0.5 : 0.4; var tm = MathHelperEx.Lerp(t1, t2, tperc); var x1 = GetNoteX(startNote, now, noteMetrics, animationMetrics); var x2 = GetNoteX(endNote, now, noteMetrics, animationMetrics); var startStatus = NoteAnimationHelper.GetOnStageStatusOf(startNote, now, tp1); float y1; if (startStatus == OnStageStatus.Passed) { y1 = animationMetrics.Bottom; } else { y1 = GetNoteOnStageY(t1, animationMetrics); } var endStatus = NoteAnimationHelper.GetOnStageStatusOf(endNote, now, tp2); float y2; if (endStatus == OnStageStatus.Incoming) { y2 = animationMetrics.Top; } else { y2 = GetNoteOnStageY(t2, animationMetrics); } // CGSS-like //var xm = GetNoteOnStageX(endNote.StartX, endNote.EndX, tm, animationMetrics); // Naive guess //var xm = (x1 + x2) / 2; var xm = MathHelperEx.Lerp(x1, x2, 0.5f); if (startNote.IsSlide()) { if (endNote.EndX < startNote.EndX) { xm -= animationMetrics.Width * 0.02f * ((tp2.Leave - now) / (tp2.Leave - tp1.Enter)); } else if (endNote.EndX > startNote.EndX) { xm += animationMetrics.Width * 0.02f * ((tp2.Leave - now) / (tp2.Leave - tp1.Enter)); } } var ym = GetNoteOnStageY(tm, animationMetrics); var(cx1, cx2) = GetBezierFromQuadratic(x1, xm, x2); var(cy1, cy2) = GetBezierFromQuadratic(y1, ym, y2); return(new RibbonParameters(x1, y1, cx1, cy1, cx2, cy2, x2, y2)); }
public override SizeF GetNoteRadius(RuntimeNote note, double now, NoteMetrics noteMetrics, NoteAnimationMetrics animationMetrics) { var timeRemaining = note.HitTime - now; var timePoints = NoteAnimationHelper.CalculateNoteTimePoints(note, animationMetrics); var timeTransformed = NoteTimeTransform((float)timeRemaining / timePoints.Duration); var endRadius = noteMetrics.EndRadius; if (timeTransformed < 0.75f) { if (timeTransformed < 0f) { return(endRadius); } else { var r = 1 - (float)timeTransformed * 0.933333333f; return(new SizeF(endRadius.Width * r, endRadius.Height * r)); } } else { if (timeTransformed < 1f) { var r = (1 - (float)timeTransformed) * 1.2f; return(new SizeF(endRadius.Width * r, endRadius.Height * r)); } else { return(SizeF.Empty); } } }
public override RibbonParameters GetSlideRibbonParameters(RuntimeNote startNote, RuntimeNote endNote, double now, NoteMetrics noteMetrics, NoteAnimationMetrics animationMetrics) { var startStatus = NoteAnimationHelper.GetOnStageStatusOf(startNote, now, animationMetrics); if (startNote.IsSlideEnd() || startStatus < OnStageStatus.Passed) { return(GetHoldRibbonParameters(startNote, endNote, now, noteMetrics, animationMetrics)); } var startX1 = GetEndXByNotePosition(startNote.EndX, animationMetrics); var startX2 = GetEndXByNotePosition(endNote.EndX, animationMetrics); var y1 = animationMetrics.Bottom; var x1 = (float)((now - startNote.HitTime) / (endNote.HitTime - startNote.HitTime)) * (startX2 - startX1) + startX1; var t1 = GetTransformedTime(startNote, now, animationMetrics); var t2 = GetTransformedTime(endNote, now, animationMetrics); var tmid = (t1 + t2) * 0.5f; var x2 = GetNoteXByTransformedTime(endNote, t2, animationMetrics); var xmid = GetNoteXByTransformedTime(endNote, tmid, animationMetrics); var y2 = GetNoteYByTransformedTime(t2, animationMetrics); var ymid = GetNoteYByTransformedTime(tmid, animationMetrics); var(controlX1, controlX2) = GetBezierFromQuadratic(x1, xmid, x2); var(controlY1, controlY2) = GetBezierFromQuadratic(y1, ymid, y2); return(new RibbonParameters(x1, y1, controlX1, controlY1, controlX2, controlY2, x2, y2)); }
public override RibbonParameters GetSlideRibbonParameters(RuntimeNote startNote, RuntimeNote endNote, float now, NoteMetrics noteMetrics, NoteAnimationMetrics animationMetrics) { var tp1 = NoteAnimationHelper.CalculateNoteTimePoints(startNote, animationMetrics); var thisStatus = NoteAnimationHelper.GetOnStageStatusOf(startNote, now, tp1); if (thisStatus < OnStageStatus.Passed) { return(GetHoldRibbonParameters(startNote, endNote, now, noteMetrics, animationMetrics)); } var tp2 = NoteAnimationHelper.CalculateNoteTimePoints(endNote, animationMetrics); var t1 = GetTransformedTime(startNote, now, tp1); var t2 = GetTransformedTime(endNote, now, tp2); var tperc = startNote.IsHold() ? 0.5 : 0.4; var tm = MathHelperEx.Lerp(t1, t2, tperc); var trackCount = animationMetrics.TrackCount; var leftMarginRatio = animationMetrics.NoteEndXRatios[0]; var rightMarginRatio = animationMetrics.NoteEndXRatios[trackCount - 1]; var startXRatio = leftMarginRatio + (rightMarginRatio - leftMarginRatio) * (startNote.EndX / (trackCount - 1)); var endXRatio = leftMarginRatio + (rightMarginRatio - leftMarginRatio) * (endNote.EndX / (trackCount - 1)); var perc = (now - startNote.HitTime) / (endNote.HitTime - startNote.HitTime); var x1Ratio = MathHelperEx.Lerp(startXRatio, endXRatio, perc); var x1 = animationMetrics.Width * x1Ratio; var y1 = animationMetrics.Bottom; var x2 = GetNoteX(endNote, now, noteMetrics, animationMetrics); var y2 = GetNoteOnStageY(t2, animationMetrics); // CGSS-like //var xm = GetNoteOnStageX(endNote.StartX, endNote.EndX, tm, animationMetrics); // Naive guess //var xm = (x1 + x2) / 2; var xm = MathHelperEx.Lerp(x1, x2, 0.5f); if (startNote.IsSlide()) { if (endNote.EndX < startNote.EndX) { xm -= animationMetrics.Width * 0.02f * ((tp2.Leave - now) / (tp2.Leave - tp1.Enter)); } else if (endNote.EndX > startNote.EndX) { xm += animationMetrics.Width * 0.02f * ((tp2.Leave - now) / (tp2.Leave - tp1.Enter)); } } var ym = GetNoteOnStageY(tm, animationMetrics); var(cx1, cx2) = GetBezierFromQuadratic(x1, xm, x2); var(cy1, cy2) = GetBezierFromQuadratic(y1, ym, y2); return(new RibbonParameters(x1, y1, cx1, cy1, cx2, cy2, x2, y2)); }
private static float GetIncomingNoteXRatio([NotNull] RuntimeNote prevNote, RuntimeNote thisNote, double now, NoteMetrics noteMetrics, NoteAnimationMetrics animationMetrics) { var trackCount = animationMetrics.TrackCount; var trackXRatioStart = animationMetrics.NoteEndXRatios[0]; var trackXRatioEnd = animationMetrics.NoteEndXRatios[trackCount - 1]; var thisXRatio = trackXRatioStart + (trackXRatioEnd - trackXRatioStart) * (prevNote.EndX / (trackCount - 1)); var nextXRatio = trackXRatioStart + (trackXRatioEnd - trackXRatioStart) * (thisNote.EndX / (trackCount - 1)); var thisTimePoints = NoteAnimationHelper.CalculateNoteTimePoints(prevNote, animationMetrics); var nextTimePoints = NoteAnimationHelper.CalculateNoteTimePoints(thisNote, animationMetrics); var perc = (float)(now - thisTimePoints.Enter) / (float)(nextTimePoints.Enter - thisTimePoints.Enter); return(MathHelper.Lerp(thisXRatio, nextXRatio, perc)); }
private static double GetTransformedTime(RuntimeNote note, double now, NoteAnimationMetrics animationMetrics) { var timePoints = NoteAnimationHelper.CalculateNoteTimePoints(note, animationMetrics); var timeRemaining = note.HitTime - now; var timeRemainingInWindow = (float)timeRemaining / timePoints.Duration; if (timeRemaining > timePoints.Duration) { timeRemainingInWindow = 1; } if (timeRemaining < 0) { timeRemainingInWindow = 0; } return(NoteTimeTransform(timeRemainingInWindow)); }
public override Vector2 GetNoteRadius(RuntimeNote note, float now, NoteMetrics noteMetrics, NoteAnimationMetrics animationMetrics) { var onStageStatus = NoteAnimationHelper.GetOnStageStatusOf(note, now, animationMetrics); switch (onStageStatus) { case OnStageStatus.Incoming: return(Vector2.Zero); case OnStageStatus.Passed: return(noteMetrics.EndRadius); } var timeRemaining = note.HitTime - now; var timePoints = NoteAnimationHelper.CalculateNoteTimePoints(note, animationMetrics); var timeTransformed = NoteTimeTransform(timeRemaining / timePoints.Duration); var endRadius = noteMetrics.EndRadius; if (timeTransformed < 0.75f) { if (timeTransformed < 0f) { return(endRadius); } else { var r = 1 - timeTransformed * 0.933333333f; return(new Vector2(endRadius.X * r, endRadius.Y * r)); } } else { if (timeTransformed < 1f) { var r = (1 - timeTransformed) * 1.2f; return(new Vector2(endRadius.X * r, endRadius.Y * r)); } else { return(Vector2.Zero); } } }
public override float GetNoteX(RuntimeNote note, double now, NoteMetrics noteMetrics, NoteAnimationMetrics animationMetrics) { var trackCount = animationMetrics.TrackCount; var trackXRatioStart = animationMetrics.NoteEndXRatios[0]; var trackXRatioEnd = animationMetrics.NoteEndXRatios[trackCount - 1]; var endXRatio = trackXRatioStart + (trackXRatioEnd - trackXRatioStart) * (note.EndX / (trackCount - 1)); var onStage = NoteAnimationHelper.GetOnStageStatusOf(note, now, animationMetrics); float xRatio; switch (onStage) { case OnStageStatus.Incoming: if (note.HasPrevHold()) { xRatio = GetIncomingNoteXRatio(note.PrevHold, note, now, noteMetrics, animationMetrics); } else if (note.HasPrevSlide()) { xRatio = GetIncomingNoteXRatio(note.PrevSlide, note, now, noteMetrics, animationMetrics); } else { xRatio = endXRatio; } break; case OnStageStatus.Passed when note.HasNextSlide(): var destXRatio = trackXRatioStart + (trackXRatioEnd - trackXRatioStart) * (note.NextSlide.EndX / (trackCount - 1)); var nextPerc = (float)(now - note.HitTime) / (float)(note.NextSlide.HitTime - note.HitTime); xRatio = MathHelper.Lerp(endXRatio, destXRatio, nextPerc); break; default: xRatio = endXRatio; break; } return(animationMetrics.Width * xRatio); }
public override float GetNoteX(RuntimeNote note, double now, NoteMetrics noteMetrics, NoteAnimationMetrics animationMetrics) { if (note.IsSlide() && note.HasNextSlide()) { var thisStatus = NoteAnimationHelper.GetOnStageStatusOf(note, now, animationMetrics); if (thisStatus >= OnStageStatus.Passed) { var nextSlide = note.NextSlide; var nextStatus = NoteAnimationHelper.GetOnStageStatusOf(nextSlide, now, animationMetrics); if (nextStatus < OnStageStatus.Passed) { var x1 = GetEndXByNotePosition(note.EndX, animationMetrics); var x2 = GetEndXByNotePosition(nextSlide.EndX, animationMetrics); return((float)((now - note.HitTime) / (nextSlide.HitTime - note.HitTime)) * (x2 - x1) + x1); } } } var transformedTime = GetTransformedTime(note, now, animationMetrics); return(GetNoteXByTransformedTime(note, transformedTime, animationMetrics)); }
private static float GetIncomingNoteXRatio(RuntimeNote prevNote, RuntimeNote thisNote, float now, NoteAnimationMetrics animationMetrics) { var trackCount = animationMetrics.TrackCount; var startLeftMarginRatio = animationMetrics.NoteStartXRatios[0]; var startRightMarginRatio = animationMetrics.NoteStartXRatios[trackCount - 1]; float xRatio; if (thisNote.IsSlide()) { var thisXRatio = startLeftMarginRatio + (startRightMarginRatio - startLeftMarginRatio) * (prevNote.EndX / (trackCount - 1)); var nextXRatio = startLeftMarginRatio + (startRightMarginRatio - startLeftMarginRatio) * (thisNote.EndX / (trackCount - 1)); var thisTimePoints = NoteAnimationHelper.CalculateNoteTimePoints(prevNote, animationMetrics); var nextTimePoints = NoteAnimationHelper.CalculateNoteTimePoints(thisNote, animationMetrics); var perc = (now - thisTimePoints.Enter) / (nextTimePoints.Enter - thisTimePoints.Enter); xRatio = MathHelperEx.Lerp(thisXRatio, nextXRatio, perc); } else { float nextStartX; if (thisNote.StartX < 0) { nextStartX = thisNote.StartX * 0.5f; } else if (thisNote.StartX > trackCount - 1) { nextStartX = (trackCount - 1) + (thisNote.StartX - (trackCount - 1)) * 0.5f; } else { nextStartX = thisNote.StartX; } xRatio = startLeftMarginRatio + (startRightMarginRatio - startLeftMarginRatio) * (nextStartX / (trackCount - 1)); } return(xRatio); }
public override Vector2 GetNoteRadius(RuntimeNote note, float now, NoteMetrics noteMetrics, NoteAnimationMetrics animationMetrics) { var timePoints = NoteAnimationHelper.CalculateNoteTimePoints(note, animationMetrics); var onStageStatus = NoteAnimationHelper.GetOnStageStatusOf(note, now, timePoints); switch (onStageStatus) { case OnStageStatus.Incoming: return(noteMetrics.StartRadius); case OnStageStatus.Visible: var passed = now - timePoints.Enter; var perc = passed / timePoints.Duration; var w = MathHelperEx.Lerp(noteMetrics.StartRadius.X, noteMetrics.EndRadius.X, perc); var h = MathHelperEx.Lerp(noteMetrics.StartRadius.Y, noteMetrics.EndRadius.Y, perc); return(new Vector2(w, h)); case OnStageStatus.Passed: return(noteMetrics.EndRadius); default: throw new ArgumentOutOfRangeException(); } }
internal RibbonMesh(RibbonMeshCreateParams createParams) { _vertexStride = 0; _faceCount = 0; _vertices = null; _indices = null; _vertexBuffer = null; _indexBuffer = null; if (createParams.RibbonParameters == null) { return; } if (createParams.NotePairs == null) { return; } if (createParams.RibbonParameters.Length == 0 || createParams.NotePairs.Length == 0 || createParams.RibbonParameters.Length != createParams.NotePairs.Length) { throw new ArgumentException(); } if (!createParams.RibbonParameters.Any(rp => rp.Visible)) { throw new ArgumentException(); } Dispose(); var vertexCount = 0; var indexCount = 0; var now = createParams.Now; var slice = createParams.Slice; var visualNoteMetrics = createParams.VisualNoteMetrics; var animationMetrics = createParams.AnimationMetrics; // First, calculate the total ribbon length. // Since CGSS allows ribbon to "fold", we cannot simply use Y coord value to calculate texture V value. var bezierCount = createParams.RibbonParameters.Count(rp => rp.Visible && !rp.IsLine); var ribbonCache = bezierCount > 0 ? new RibbonPointCache[bezierCount] : null; var ribbonLength = 0f; var bezierIndex = 0; for (var i = 0; i < createParams.RibbonParameters.Length; ++i) { var rp = createParams.RibbonParameters[i]; if (!rp.Visible) { continue; } var notePair = createParams.NotePairs[i]; if (rp.IsLine) { ribbonLength += Math.Abs(rp.Y1 - rp.Y2); } else { float startTime, endTime; var startStatus = NoteAnimationHelper.GetOnStageStatusOf(notePair.Start, now, animationMetrics); if (startStatus == OnStageStatus.Passed) { startTime = now; } else { startTime = notePair.Start.HitTime; } var endStatus = NoteAnimationHelper.GetOnStageStatusOf(notePair.End, now, animationMetrics); if (endStatus == OnStageStatus.Incoming) { var endTimePoints = NoteAnimationHelper.CalculateNoteTimePoints(notePair.End, animationMetrics); endTime = now + endTimePoints.Duration; } else { endTime = notePair.End.HitTime; } var pts = new Vector2[slice + 1]; for (var j = 0; j <= slice; ++j) { var t = (float)j / slice; var pt = RibbonMathHelper.CubicBezier(rp, t); pts[j] = pt; if (j > 0) { ribbonLength += Math.Abs(pt.Y - pts[j - 1].Y); } } var pointCache = new RibbonPointCache { StartTime = startTime, EndTime = endTime, Locations = pts }; if (ribbonCache == null) { throw new InvalidOperationException(); } ribbonCache[bezierIndex] = pointCache; ++bezierIndex; } } if (ribbonLength <= 0) { // An empty ribbon. SetVertices(createParams.Device, (RibbonVertex[])null); SetIndices(createParams.Device, null); } else { // Normal case. foreach (var rp in createParams.RibbonParameters) { if (!rp.Visible) { continue; } if (rp.IsLine) { vertexCount += 4; indexCount += 6; } else { vertexCount += (slice + 1) * 2; indexCount += slice * 6; } } var z = createParams.Z; var foldedZ = z - createParams.LayerDepth / 2; var vertices = new RibbonVertex[vertexCount]; var indices = new ushort[indexCount]; var vertexStart = 0; var indexStart = 0; // Generated vertex order for a non-folded fragment: // 1---2 // | / | // 3---4 // (1,2,3) (4,3,2) var processedRibbonLength = 0f; bezierIndex = 0; var traceCalculator = createParams.AnimationCalculator; var textureTopYRatio = createParams.TextureTopYRatio; var textureBottomYRatio = createParams.TextureBottomYRatio; for (var i = 0; i < createParams.RibbonParameters.Length; ++i) { var rp = createParams.RibbonParameters[i]; if (!rp.Visible) { continue; } var notePair = createParams.NotePairs[i]; // Normal ribbon is flowing from top to bottom (Y1 < Y2); CGSS would have a part from downside to upside. // If this fragment is folded, we must place it under the normal layer (z - layerDepth / 2). bool isFragmentFolded; float zToUse; float perc; float v; if (rp.IsLine) { var startRadius = traceCalculator.GetNoteRadius(notePair.Start, now, visualNoteMetrics, animationMetrics); var endRadius = traceCalculator.GetNoteRadius(notePair.End, now, visualNoteMetrics, animationMetrics); isFragmentFolded = rp.Y1 > rp.Y2; zToUse = isFragmentFolded ? foldedZ : z; perc = processedRibbonLength / ribbonLength; v = MathHelper.Lerp(textureTopYRatio, textureBottomYRatio, perc); var leftTopVertex = new RibbonVertex(rp.X1 - endRadius.X / 2, rp.Y1, zToUse, 0, 0, 1, 0, v); var rightTopVertex = new RibbonVertex(rp.X1 + endRadius.X / 2, rp.Y1, zToUse, 0, 0, 1, 1, v); perc = (processedRibbonLength + Math.Abs(rp.Y1 - rp.Y2)) / ribbonLength; v = MathHelper.Lerp(textureTopYRatio, textureBottomYRatio, perc); var leftBottomVertex = new RibbonVertex(rp.X2 - startRadius.X / 2, rp.Y2, zToUse, 0, 0, 1, 0, v); var rightBottomVertex = new RibbonVertex(rp.X2 + startRadius.X / 2, rp.Y2, zToUse, 0, 0, 1, 1, v); vertices[vertexStart] = leftTopVertex; vertices[vertexStart + 1] = rightTopVertex; vertices[vertexStart + 2] = leftBottomVertex; vertices[vertexStart + 3] = rightBottomVertex; if (isFragmentFolded) { indices[indexStart] = (ushort)(vertexStart + 2); indices[indexStart + 1] = (ushort)(vertexStart + 1); indices[indexStart + 2] = (ushort)vertexStart; indices[indexStart + 3] = (ushort)(vertexStart + 1); indices[indexStart + 4] = (ushort)(vertexStart + 2); indices[indexStart + 5] = (ushort)(vertexStart + 3); } else { indices[indexStart] = (ushort)vertexStart; indices[indexStart + 1] = (ushort)(vertexStart + 1); indices[indexStart + 2] = (ushort)(vertexStart + 2); indices[indexStart + 3] = (ushort)(vertexStart + 3); indices[indexStart + 4] = (ushort)(vertexStart + 2); indices[indexStart + 5] = (ushort)(vertexStart + 1); } vertexStart += 4; indexStart += 6; processedRibbonLength += Math.Abs(rp.Y1 - rp.Y2); } else { if (ribbonCache == null) { throw new InvalidOperationException(); } var cache = ribbonCache[bezierIndex]; var deltaTime = cache.EndTime - cache.StartTime; for (var j = 0; j <= slice; ++j) { var t = (float)j / slice; var ribbonTime = cache.EndTime - deltaTime * t; var pt = cache.Locations[j]; if (j < slice) { isFragmentFolded = pt.Y > cache.Locations[j + 1].Y; } else { // Here, j = slice isFragmentFolded = cache.Locations[slice - 1].Y > pt.Y; } zToUse = isFragmentFolded ? foldedZ : z; var noteRadius = traceCalculator.GetNoteRadius(notePair.Start, ribbonTime, visualNoteMetrics, animationMetrics); perc = processedRibbonLength / ribbonLength; v = MathHelper.Lerp(textureTopYRatio, textureBottomYRatio, perc); var leftVertex = new RibbonVertex(pt.X - noteRadius.X / 2, pt.Y, zToUse, 0, 0, 1, 0, v); var rightVertex = new RibbonVertex(pt.X + noteRadius.X / 2, pt.Y, zToUse, 0, 0, 1, 1, v); vertices[vertexStart + j * 2] = leftVertex; vertices[vertexStart + j * 2 + 1] = rightVertex; if (j < slice) { if (isFragmentFolded) { indices[indexStart + j * 6] = (ushort)(vertexStart + j * 2 + 2); indices[indexStart + j * 6 + 1] = (ushort)(vertexStart + j * 2 + 1); indices[indexStart + j * 6 + 2] = (ushort)(vertexStart + j * 2); indices[indexStart + j * 6 + 3] = (ushort)(vertexStart + j * 2 + 1); indices[indexStart + j * 6 + 4] = (ushort)(vertexStart + j * 2 + 2); indices[indexStart + j * 6 + 5] = (ushort)(vertexStart + j * 2 + 3); } else { indices[indexStart + j * 6] = (ushort)(vertexStart + j * 2); indices[indexStart + j * 6 + 1] = (ushort)(vertexStart + j * 2 + 1); indices[indexStart + j * 6 + 2] = (ushort)(vertexStart + j * 2 + 2); indices[indexStart + j * 6 + 3] = (ushort)(vertexStart + j * 2 + 3); indices[indexStart + j * 6 + 4] = (ushort)(vertexStart + j * 2 + 2); indices[indexStart + j * 6 + 5] = (ushort)(vertexStart + j * 2 + 1); } processedRibbonLength += Math.Abs(cache.Locations[j + 1].Y - cache.Locations[j].Y); } } vertexStart += (slice + 1) * 2; indexStart += slice * 6; ++bezierIndex; } } _vertices = vertices; _indices = indices; SetVertices(createParams.Device, vertices); SetIndices(createParams.Device, indices); } }
protected override void OnDraw(GameTime gameTime, RenderContext context) { base.OnDraw(gameTime, context); var settings = Program.Settings; var motionIcon = settings.Style.SlideMotionIcon; if (motionIcon == SlideMotionIcon.None) { return; } if (_score == null) { return; } var notes = _score.Notes; var theaterDays = Game.AsTheaterDays(); var syncTimer = theaterDays.FindSingleElement <SyncTimer>(); if (syncTimer == null) { return; } var tapPoints = theaterDays.FindSingleElement <TapPoints>(); if (tapPoints == null) { throw new InvalidOperationException(); } var notesLayer = theaterDays.FindSingleElement <NotesLayer>(); if (notesLayer == null) { throw new InvalidOperationException(); } var gamingArea = theaterDays.FindSingleElement <GamingArea>(); if (gamingArea == null) { throw new InvalidOperationException(); } var now = syncTimer.CurrentTime.TotalSeconds; var tapPointsLayout = settings.UI.TapPoints.Layout; var notesLayerLayout = settings.UI.NotesLayer.Layout; var clientSize = context.ClientSize; var traceCalculator = notesLayer.TraceCalculator; var commonNoteMetrics = new NoteMetrics { StartRadius = gamingArea.ScaleResults.Note.Start, EndRadius = gamingArea.ScaleResults.Note.End }; var animationMetrics = new NoteAnimationMetrics { GlobalSpeedScale = notesLayer.GlobalSpeedScale, Width = clientSize.Width, Height = clientSize.Height, Top = notesLayerLayout.Y.IsPercentage ? notesLayerLayout.Y.Value * clientSize.Height : notesLayerLayout.Y.Value, Bottom = tapPointsLayout.Y.IsPercentage ? tapPointsLayout.Y.Value * clientSize.Height : tapPointsLayout.Y.Value, NoteStartXRatios = tapPoints.StartXRatios, NoteEndXRatios = tapPoints.EndXRatios, TrackCount = tapPoints.EndXRatios.Length }; context.Begin2D(); foreach (var note in notes) { if (!note.IsSlide() || !note.HasNextSlide()) { continue; } var thisStatus = NoteAnimationHelper.GetOnStageStatusOf(note, now, animationMetrics); if (thisStatus < OnStageStatus.Passed) { continue; } var nextStatus = NoteAnimationHelper.GetOnStageStatusOf(note.NextSlide, now, animationMetrics); if (nextStatus >= OnStageStatus.Passed) { continue; } var x = traceCalculator.GetNoteX(note, now, commonNoteMetrics, animationMetrics); var y = traceCalculator.GetNoteY(note, now, commonNoteMetrics, animationMetrics); SizeF imageSize; switch (motionIcon) { case SlideMotionIcon.None: // Not possible. throw new InvalidOperationException(); case SlideMotionIcon.Tap: if (_tapPointImage != null) { imageSize = gamingArea.ScaleResults.TapPoint.Start; context.DrawBitmap(_tapPointImage, x - imageSize.Width / 2, y - imageSize.Height / 2, imageSize.Width, imageSize.Height); } break; case SlideMotionIcon.Slide: if (_noteImages?[0] != null) { var(imageIndex, _) = NotesLayer.GetImageIndex(NoteType.Slide, NoteSize.Small, FlickDirection.None, false, false, false, false); imageSize = gamingArea.ScaleResults.Note.End; context.DrawImageStripUnit(_noteImages[0], imageIndex, x - imageSize.Width / 2, y - imageSize.Height / 2, imageSize.Width, imageSize.Height); } break; default: throw new ArgumentOutOfRangeException(); } } context.End2D(); }
protected override void OnDraw(GameTime gameTime) { base.OnDraw(gameTime); var config = ConfigurationStore.Get <SlideMotionConfig>(); var motionIcon = config.Data.Icon; if (motionIcon == SlideMotionConfig.SlideMotionIcon.None) { return; } var theaterDays = Game.ToBaseGame(); var score = theaterDays.FindSingleElement <ScoreLoader>()?.RuntimeScore; if (score == null) { return; } var notes = score.Notes; var syncTimer = theaterDays.FindSingleElement <SyncTimer>(); if (syncTimer == null) { return; } var tapPoints = theaterDays.FindSingleElement <TapPoints>(); if (tapPoints == null) { throw new InvalidOperationException(); } var notesLayer = theaterDays.FindSingleElement <NotesLayer>(); if (notesLayer == null) { throw new InvalidOperationException(); } var scalingResponder = theaterDays.FindSingleElement <MltdStageScalingResponder>(); if (scalingResponder == null) { throw new InvalidOperationException(); } var now = (float)syncTimer.CurrentTime.TotalSeconds; var tapPointsConfig = ConfigurationStore.Get <TapPointsConfig>(); var notesLayerConfig = ConfigurationStore.Get <NotesLayerConfig>(); var tapPointsLayout = tapPointsConfig.Data.Layout; var notesLayerLayout = notesLayerConfig.Data.Layout; var clientSize = theaterDays.GraphicsDevice.Viewport; var animationCalculator = notesLayer.AnimationCalculator; var commonNoteMetrics = new NoteMetrics { StartRadius = scalingResponder.ScaleResults.Note.Start, EndRadius = scalingResponder.ScaleResults.Note.End }; var animationMetrics = new NoteAnimationMetrics { GlobalSpeedScale = notesLayer.GlobalSpeedScale, Width = clientSize.Width, Height = clientSize.Height, Top = notesLayerLayout.Y.IsPercentage ? notesLayerLayout.Y.Value * clientSize.Height : notesLayerLayout.Y.Value, Bottom = tapPointsLayout.Y.IsPercentage ? tapPointsLayout.Y.Value * clientSize.Height : tapPointsLayout.Y.Value, NoteStartXRatios = tapPoints.StartXRatios, NoteEndXRatios = tapPoints.EndXRatios, TrackCount = tapPoints.EndXRatios.Length }; var spriteBatch = theaterDays.SpriteBatch; spriteBatch.Begin(); foreach (var note in notes) { if (!note.IsSlide() || !note.HasNextSlide()) { continue; } var thisStatus = NoteAnimationHelper.GetOnStageStatusOf(note, now, animationMetrics); if (thisStatus < OnStageStatus.Passed) { continue; } var nextStatus = NoteAnimationHelper.GetOnStageStatusOf(note.NextSlide, now, animationMetrics); if (nextStatus >= OnStageStatus.Passed) { continue; } var x = animationCalculator.GetNoteX(note, now, commonNoteMetrics, animationMetrics); var y = animationCalculator.GetNoteY(note, now, commonNoteMetrics, animationMetrics); Vector2 imageSize; switch (motionIcon) { case SlideMotionConfig.SlideMotionIcon.None: // Not possible. throw new InvalidOperationException(); case SlideMotionConfig.SlideMotionIcon.TapPoint: if (_tapPointImage != null) { imageSize = scalingResponder.ScaleResults.TapPoint.Start; spriteBatch.Draw(_tapPointImage, RectHelper.RoundToRectangle(x - imageSize.X / 2, y - imageSize.Y / 2, imageSize.X, imageSize.Y)); } break; case SlideMotionConfig.SlideMotionIcon.SlideStart: case SlideMotionConfig.SlideMotionIcon.SlideMiddle: case SlideMotionConfig.SlideMotionIcon.SlideEnd: var isStart = motionIcon == SlideMotionConfig.SlideMotionIcon.SlideStart; var isEnd = motionIcon == SlideMotionConfig.SlideMotionIcon.SlideEnd; if (_noteImages?[0] != null) { var(imageIndex, _) = NotesLayer.GetImageIndex(NoteType.Slide, NoteSize.Small, FlickDirection.None, false, false, isStart, isEnd); imageSize = scalingResponder.ScaleResults.Note.End; spriteBatch.Draw(_noteImages[0], imageIndex, RectHelper.RoundToRectangle(x - imageSize.X / 2, y - imageSize.Y / 2, imageSize.X, imageSize.Y)); } break; default: throw new ArgumentOutOfRangeException(); } } spriteBatch.End(); }
public override float GetNoteX(RuntimeNote note, float now, NoteMetrics noteMetrics, NoteAnimationMetrics animationMetrics) { var trackCount = animationMetrics.TrackCount; var endLeftMarginRatio = animationMetrics.NoteEndXRatios[0]; var endRightMarginRatio = animationMetrics.NoteEndXRatios[trackCount - 1]; var endXRatio = endLeftMarginRatio + (endRightMarginRatio - endLeftMarginRatio) * (note.EndX / (trackCount - 1)); var onStage = NoteAnimationHelper.GetOnStageStatusOf(note, now, animationMetrics); float xRatio; switch (onStage) { case OnStageStatus.Incoming: if (note.HasPrevHold()) { xRatio = GetIncomingNoteXRatio(note.PrevHold, note, now, animationMetrics); } else if (note.HasPrevSlide()) { xRatio = GetIncomingNoteXRatio(note.PrevSlide, note, now, animationMetrics); } else { xRatio = endXRatio; } break; case OnStageStatus.Passed: if (note.HasNextSlide()) { var destXRatio = endLeftMarginRatio + (endRightMarginRatio - endLeftMarginRatio) * (note.NextSlide.EndX / (trackCount - 1)); var nextPerc = (now - note.HitTime) / (note.NextSlide.HitTime - note.HitTime); xRatio = MathHelperEx.Lerp(endXRatio, destXRatio, nextPerc); } else { xRatio = endXRatio; } break; default: var startLeftMarginRatio = animationMetrics.NoteStartXRatios[0]; var startRightMarginRatio = animationMetrics.NoteStartXRatios[trackCount - 1]; float whichStartToTake; if (note.IsSlide()) { whichStartToTake = note.EndX; } else { if (note.StartX < 0) { whichStartToTake = note.StartX * 0.5f; } else if (note.StartX > trackCount - 1) { whichStartToTake = (trackCount - 1) + (note.StartX - (trackCount - 1)) * 0.5f; } else { whichStartToTake = note.StartX; } } var startXRatio = startLeftMarginRatio + (startRightMarginRatio - startLeftMarginRatio) * (whichStartToTake / (trackCount - 1)); var timePoints = NoteAnimationHelper.CalculateNoteTimePoints(note, animationMetrics); var perc = (now - timePoints.Enter) / timePoints.Duration; xRatio = MathHelperEx.Lerp(startXRatio, endXRatio, perc); break; } return(animationMetrics.Width * xRatio); }
protected override void OnDraw(GameTime gameTime, RenderContext context) { base.OnDraw(gameTime, context); if (_score == null) { return; } var notes = _score.Notes; var theaterDays = Game.AsTheaterDays(); var syncTimer = theaterDays.FindSingleElement <SyncTimer>(); if (syncTimer == null) { return; } var tapPoints = theaterDays.FindSingleElement <TapPoints>(); if (tapPoints == null) { throw new InvalidOperationException(); } var notesLayer = theaterDays.FindSingleElement <NotesLayer>(); if (notesLayer == null) { throw new InvalidOperationException(); } var gamingArea = theaterDays.FindSingleElement <GamingArea>(); if (gamingArea == null) { throw new InvalidOperationException(); } var settings = Program.Settings; var now = syncTimer.CurrentTime.TotalSeconds; var tapPointsLayout = settings.UI.TapPoints.Layout; var notesLayerLayout = settings.UI.NotesLayer.Layout; var clientSize = context.ClientSize; var traceCalculator = notesLayer.TraceCalculator; var animationMetrics = new NoteAnimationMetrics { GlobalSpeedScale = notesLayer.GlobalSpeedScale, Width = clientSize.Width, Height = clientSize.Height, Top = notesLayerLayout.Y.IsPercentage ? notesLayerLayout.Y.Value * clientSize.Height : notesLayerLayout.Y.Value, Bottom = tapPointsLayout.Y.IsPercentage ? tapPointsLayout.Y.Value * clientSize.Height : tapPointsLayout.Y.Value, NoteStartXRatios = tapPoints.StartXRatios, NoteEndXRatios = tapPoints.EndXRatios, TrackCount = tapPoints.EndXRatios.Length }; var topYRatio = _ribbonTopYRatio; var bottomYRatio = _ribbonBottomYRatio; _textureEffect.CurrentTime = (float)now; // Enable depth buffer to allow drawing in layers. // However the depth comparison always passes, and Z always decreases (see below), so the results looks like // "later-drawn-on-top" inside this visual element. // We are using Begin3DFast() here, so the states are specified in effect file (simple_texture.fx). Please // open that file to see which settings we are selecting. context.Begin3DFast(_posTexLayout, PrimitiveTopology.TriangleList); // Z value should be decreasing to achieve this effect: // when two ribbons cross, the one with the start note on the right is above the other. var z = 0f; var smallNoteMetrics = new NoteMetrics { StartRadius = gamingArea.ScaleResults.VisualNoteSmall.Start, EndRadius = gamingArea.ScaleResults.VisualNoteSmall.End }; var largeNoteMetrics = new NoteMetrics { StartRadius = gamingArea.ScaleResults.VisualNoteLarge.Start, EndRadius = gamingArea.ScaleResults.VisualNoteLarge.End }; foreach (var note in notes) { OnStageStatus thisStatus, nextStatus; RuntimeNote nextNote; NoteMetrics visualNoteMetrics; if (note.HasNextHold()) { thisStatus = NoteAnimationHelper.GetOnStageStatusOf(note, now, animationMetrics); if (thisStatus == OnStageStatus.Incoming) { continue; } nextNote = note.NextHold; nextStatus = NoteAnimationHelper.GetOnStageStatusOf(nextNote, now, animationMetrics); if ((int)thisStatus * (int)nextStatus > 0) { continue; } var firstHoldInGroup = note; while (firstHoldInGroup.HasPrevHold()) { firstHoldInGroup = firstHoldInGroup.PrevHold; } visualNoteMetrics = firstHoldInGroup.IsFlick() || firstHoldInGroup.Size == NoteSize.Large ? largeNoteMetrics : smallNoteMetrics; var ribbonParams = traceCalculator.GetHoldRibbonParameters(note, nextNote, now, visualNoteMetrics, animationMetrics); if (ribbonParams.Visible) { using (var mesh = new RibbonMesh(context.Direct3DDevice, SliceCount, topYRatio, bottomYRatio, z, new[] { ribbonParams }, traceCalculator, now, new[] { (note, note.NextHold) }, visualNoteMetrics, animationMetrics)) {
protected override void OnDraw(GameTime gameTime) { base.OnDraw(gameTime); var theaterDays = Game.ToBaseGame(); var scoreLoader = theaterDays.FindSingleElement <ScoreLoader>(); var score = scoreLoader?.RuntimeScore; if (score == null) { return; } var notes = score.Notes; var syncTimer = theaterDays.FindSingleElement <SyncTimer>(); if (syncTimer == null) { return; } var tapPoints = theaterDays.FindSingleElement <TapPoints>(); if (tapPoints == null) { throw new InvalidOperationException(); } var notesLayer = theaterDays.FindSingleElement <NotesLayer>(); if (notesLayer == null) { throw new InvalidOperationException(); } var scalingResponder = theaterDays.FindSingleElement <MltdStageScalingResponder>(); if (scalingResponder == null) { throw new InvalidOperationException(); } var now = (float)syncTimer.CurrentTime.TotalSeconds; var tapPointsConfig = ConfigurationStore.Get <TapPointsConfig>(); var notesLayerConfig = ConfigurationStore.Get <NotesLayerConfig>(); var tapPointsLayout = tapPointsConfig.Data.Layout; var notesLayerLayout = notesLayerConfig.Data.Layout; var clientSize = theaterDays.GraphicsDevice.Viewport; var traceCalculator = notesLayer.AnimationCalculator; var animationMetrics = new NoteAnimationMetrics { GlobalSpeedScale = notesLayer.GlobalSpeedScale, Width = clientSize.Width, Height = clientSize.Height, Top = notesLayerLayout.Y.IsPercentage ? notesLayerLayout.Y.Value * clientSize.Height : notesLayerLayout.Y.Value, Bottom = tapPointsLayout.Y.IsPercentage ? tapPointsLayout.Y.Value * clientSize.Height : tapPointsLayout.Y.Value, NoteStartXRatios = tapPoints.StartXRatios, NoteEndXRatios = tapPoints.EndXRatios, TrackCount = tapPoints.EndXRatios.Length }; var topYRatio = _ribbonTopYRatio; var bottomYRatio = _ribbonBottomYRatio; var ribbonEffect = theaterDays.EffectManager.Get <RibbonEffect>(); ribbonEffect.CurrentTime = now; var graphics = theaterDays.GraphicsDevice; // Enable depth buffer to allow drawing in layers. // However the depth comparison always passes, and Z always decreases (see below), so the results looks like // "later-drawn-on-top" inside this visual element. // We are using Begin3DFast() here, so the states are specified in effect file (simple_texture.fx). Please // open that file to see which settings we are selecting. // Z value should be decreasing to achieve this effect: // when two ribbons cross, the one with the start note on the right is above the other. var z = ZTop; var smallNoteMetrics = new NoteMetrics { StartRadius = scalingResponder.ScaleResults.VisualNoteSmall.Start, EndRadius = scalingResponder.ScaleResults.VisualNoteSmall.End }; var largeNoteMetrics = new NoteMetrics { StartRadius = scalingResponder.ScaleResults.VisualNoteLarge.Start, EndRadius = scalingResponder.ScaleResults.VisualNoteLarge.End }; var viewProjection = _viewMatrix * _projectionMatrix; foreach (var note in notes) { OnStageStatus thisStatus, nextStatus; RuntimeNote nextNote; NoteMetrics visualNoteMetrics; RibbonMeshCreateParams rmcp; if (note.HasNextHold()) { thisStatus = NoteAnimationHelper.GetOnStageStatusOf(note, now, animationMetrics); if (thisStatus == OnStageStatus.Incoming) { continue; } nextNote = note.NextHold; nextStatus = NoteAnimationHelper.GetOnStageStatusOf(nextNote, now, animationMetrics); if (AreTwoNotesOnTheSameSide(thisStatus, nextStatus)) { continue; } var firstHoldInGroup = note; while (firstHoldInGroup.HasPrevHold()) { firstHoldInGroup = firstHoldInGroup.PrevHold; } visualNoteMetrics = firstHoldInGroup.IsFlick() || firstHoldInGroup.Size == NoteSize.Large ? largeNoteMetrics : smallNoteMetrics; var ribbonParams = traceCalculator.GetHoldRibbonParameters(note, nextNote, now, visualNoteMetrics, animationMetrics); if (ribbonParams.Visible) { rmcp = new RibbonMeshCreateParams(graphics, SliceCount, topYRatio, bottomYRatio, z, LayerDepth, new[] { ribbonParams }, traceCalculator, now, new[] { (note, note.NextHold) }, visualNoteMetrics, animationMetrics);
protected override void OnUpdate(GameTime gameTime) { base.OnUpdate(gameTime); if (_notes == null) { return; } var theaterDays = Game.AsTheaterDays(); var notesLayer = theaterDays.FindSingleElement <NotesLayer>(); if (notesLayer == null) { throw new InvalidOperationException(); } var syncTimer = theaterDays.FindSingleElement <SyncTimer>(); if (syncTimer == null) { throw new InvalidOperationException(); } var hitRankAnimation = theaterDays.FindSingleElement <HitRankAnimation>(); var audioFormats = Program.PluginManager.AudioFormats; var sfxPaths = Program.Settings.Sfx; var player = theaterDays.AudioManager.Sfx; var now = syncTimer.CurrentTime.TotalSeconds; var globalSpeedScale = notesLayer.GlobalSpeedScale; var states = _noteStates; if (now < _lastUpdatedSeconds) { // Force updating all states. // TODO: TRICK var refTime = now - double.Epsilon; foreach (var note in _notes) { var forcedOldState = NoteAnimationHelper.GetOnStageStatusOf(note, refTime, globalSpeedScale); states[note] = forcedOldState; } } foreach (var note in _notes) { var oldState = states[note]; var newState = NoteAnimationHelper.GetOnStageStatusOf(note, now, globalSpeedScale); if (oldState == newState) { continue; } var shouldPlayHitRankAnimation = false; switch (note.Type) { case NoteType.Tap: if (newState == OnStageStatus.Passed) { player.Play(sfxPaths.Tap.Perfect, audioFormats); shouldPlayHitRankAnimation = true; } break; case NoteType.Flick: if (newState == OnStageStatus.Passed) { player.Play(sfxPaths.Flick.Perfect, audioFormats); shouldPlayHitRankAnimation = true; } break; case NoteType.Hold: if (note.IsHoldStart()) { if (note.FlickDirection != FlickDirection.None) { if (newState == OnStageStatus.Passed) { player.Play(sfxPaths.Flick.Perfect, audioFormats); shouldPlayHitRankAnimation = true; } } else { if (newState == OnStageStatus.Passed) { player.Play(sfxPaths.Hold.Perfect, audioFormats); player.PlayLooped(sfxPaths.HoldHold, audioFormats, note); shouldPlayHitRankAnimation = true; } } } else if (note.IsHoldEnd()) { if (note.FlickDirection != FlickDirection.None) { if (newState == OnStageStatus.Passed) { player.Play(sfxPaths.Flick.Perfect, audioFormats); } } else { if (newState == OnStageStatus.Passed) { player.Play(sfxPaths.HoldEnd.Perfect, audioFormats); } } if (newState == OnStageStatus.Passed) { player.StopLooped(FindFirstHold(note)); shouldPlayHitRankAnimation = true; } } break; case NoteType.Slide: if (note.IsSlideStart()) { if (note.FlickDirection != FlickDirection.None) { if (newState == OnStageStatus.Passed) { player.Play(sfxPaths.Flick.Perfect, audioFormats); shouldPlayHitRankAnimation = true; } } else { if (newState == OnStageStatus.Passed) { player.Play(sfxPaths.Slide.Perfect, audioFormats); player.PlayLooped(sfxPaths.SlideHold, audioFormats, note); shouldPlayHitRankAnimation = true; } } } else if (note.IsSlideEnd()) { if (note.FlickDirection != FlickDirection.None) { if (newState == OnStageStatus.Passed) { player.Play(sfxPaths.Flick.Perfect, audioFormats); } } else { if (newState == OnStageStatus.Passed) { player.Play(sfxPaths.SlideEnd.Perfect, audioFormats); } } if (newState == OnStageStatus.Passed) { player.StopLooped(FindFirstSlide(note)); shouldPlayHitRankAnimation = true; } } break; case NoteType.Special: if (newState == OnStageStatus.Passed) { player.Play(sfxPaths.Special.Perfect, audioFormats); var shouts = sfxPaths.Shouts; if (shouts != null && shouts.Length > 0) { var shoutIndex = MathHelper.Random.Next(shouts.Length); player.Play(shouts[shoutIndex], audioFormats); } player.PlayLooped(sfxPaths.SpecialHold, audioFormats, note); var avatarDisplay = theaterDays.FindSingleElement <AvatarDisplay>(); if (avatarDisplay != null) { avatarDisplay.Opacity = 0; } shouldPlayHitRankAnimation = true; } break; case NoteType.SpecialEnd: if (newState == OnStageStatus.Passed) { player.Play(sfxPaths.SpecialEnd, audioFormats); var shouts = sfxPaths.Shouts; if (shouts != null && shouts.Length > 0) { var shoutIndex = MathHelper.Random.Next(shouts.Length); player.Play(shouts[shoutIndex], audioFormats); } RuntimeNote specialStart = null; try { specialStart = _notes.SingleOrDefault(n => n.Type == NoteType.Special); } catch (InvalidOperationException) { // Multiple Special Start notes. } Debug.Assert(specialStart != null, "Wrong score format: there must be only exactly one special note and one special end note, if either of them exists."); player.StopLooped(specialStart); var tapPoints = theaterDays.FindSingleElement <TapPoints>(); if (tapPoints != null) { // The animation length must keep the same as the SpecialEnd note's delay. // See Unity3DScoreCompiler.CreateSpecial() for more information. tapPoints.PlaySpecialEndAnimation(); } var avatarDisplay = theaterDays.FindSingleElement <AvatarDisplay>(); if (avatarDisplay != null) { avatarDisplay.PlaySpecialEndAnimation(); } var comboDisplay = theaterDays.FindSingleElement <ComboDisplay>(); if (comboDisplay != null) { if (comboDisplay.Numbers.Value > 0) { comboDisplay.PlaySpecialEndAnimation(); } } } break; case NoteType.SpecialPrepare: if (newState == OnStageStatus.Passed) { var tapPoints = theaterDays.FindSingleElement <TapPoints>(); if (tapPoints != null) { // This shall trigger tap points' "transform" animation. // See Unity3DScoreCompiler.CreateSpecial() for more information. var tapPointsMergingAnimation = theaterDays.FindSingleElement <TapPointsMergingAnimation>(); if (tapPointsMergingAnimation != null) { tapPointsMergingAnimation.StartAnimation(); } tapPoints.Opacity = 0; } } break; case NoteType.ScorePrepare: if (newState == OnStageStatus.Passed) { var tapPoints = theaterDays.FindSingleElement <TapPoints>(); if (tapPoints != null) { tapPoints.PlayScorePrepareAnimation(); } var avatarDisplay = theaterDays.FindSingleElement <AvatarDisplay>(); if (avatarDisplay != null) { avatarDisplay.PlayScorePrepareAnimation(); } var comboDisplay = theaterDays.FindSingleElement <ComboDisplay>(); if (comboDisplay != null) { // Force setting it to 0. Or the number will be wierd if replaying the score. comboDisplay.Numbers.Value = 0; comboDisplay.Opacity = 0; } } break; default: throw new ArgumentOutOfRangeException(); } if (shouldPlayHitRankAnimation) { if (hitRankAnimation != null) { // "Perfect" hitRankAnimation.StartAnimation(0); } } states[note] = newState; if (shouldPlayHitRankAnimation) { var comboDisplay = theaterDays.FindSingleElement <ComboDisplay>(); if (comboDisplay != null) { var combo = states.Count(kv => { if (kv.Value < OnStageStatus.Passed) { return(false); } return(Array.IndexOf(GamingNoteTypes, kv.Key.Type) >= 0); }); comboDisplay.Numbers.Value = (uint)combo; if (combo > 0) { if (Array.IndexOf(ComboAura.ComboCountTriggers, combo) >= 0) { comboDisplay.Aura.StartAnimation(); } // So if it is playing a fade-in animation, we don't interrupt the animation. if (comboDisplay.Opacity <= 0) { comboDisplay.Opacity = 1; } } else { comboDisplay.Opacity = 0; } } } // We have to handle this event after we play ComboDisplay's animation. if (note.Type == NoteType.Special && newState == OnStageStatus.Passed) { var comboDisplay = theaterDays.FindSingleElement <ComboDisplay>(); if (comboDisplay != null) { comboDisplay.Opacity = 0; } } } _lastUpdatedSeconds = now; }