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));
        }
Exemple #2
0
        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 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 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));
        }
Exemple #5
0
        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);
                }
            }
        }
Exemple #6
0
        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);
        }
Exemple #7
0
        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));
        }
        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();
            }
        }
Exemple #9
0
        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);
Exemple #10
0
        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);
        }
Exemple #12
0
        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 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;
        }
        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)) {
Exemple #15
0
        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();
        }