예제 #1
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);
            }
        }
 public static Vector2 CalculateCubicBezier(this RibbonParameters rp, float t)
 {
     return(RibbonMathHelper.CubicBezier(rp, t));
 }