Exemplo n.º 1
0
        public void SetTransform(float SX, float SY, float Rotate, float TX, float TY)
        {
            EnsureInitialized();

            Matrix2 Transform;

            Transform  = Matrix2.CreateScale(SX, SY);
            Transform *= Matrix2.CreateRotation(Rotate);

            Vector2 Offs = new Vector2(TX, TY);

            int CurrentProgram = GL.GetInteger(GetPName.CurrentProgram);

            GL.UseProgram(Shader.Handle);

            int TransformUniformLocation = GL.GetUniformLocation(Shader.Handle, "transform");

            GL.UniformMatrix2(TransformUniformLocation, false, ref Transform);

            int OffsetUniformLocation = GL.GetUniformLocation(Shader.Handle, "offset");

            GL.Uniform2(OffsetUniformLocation, ref Offs);

            GL.UseProgram(CurrentProgram);
        }
Exemplo n.º 2
0
        private void SetupShader()
        {
            Shader.VpHandle = GL.CreateShader(ShaderType.VertexShader);
            Shader.FpHandle = GL.CreateShader(ShaderType.FragmentShader);

            string VpSource = EmbeddedResource.GetString("GlFbVtxShader");
            string FpSource = EmbeddedResource.GetString("GlFbFragShader");

            GL.ShaderSource(Shader.VpHandle, VpSource);
            GL.ShaderSource(Shader.FpHandle, FpSource);
            GL.CompileShader(Shader.VpHandle);
            GL.CompileShader(Shader.FpHandle);

            Shader.Handle = GL.CreateProgram();

            GL.AttachShader(Shader.Handle, Shader.VpHandle);
            GL.AttachShader(Shader.Handle, Shader.FpHandle);
            GL.LinkProgram(Shader.Handle);
            GL.UseProgram(Shader.Handle);

            Matrix2 Transform = Matrix2.CreateScale(1, -1);

            int TexUniformLocation = GL.GetUniformLocation(Shader.Handle, "tex");

            GL.Uniform1(TexUniformLocation, 0);

            int WindowSizeUniformLocation = GL.GetUniformLocation(Shader.Handle, "window_size");

            GL.Uniform2(WindowSizeUniformLocation, new Vector2(1280.0f, 720.0f));

            int TransformUniformLocation = GL.GetUniformLocation(Shader.Handle, "transform");

            GL.UniformMatrix2(TransformUniformLocation, false, ref Transform);
        }
Exemplo n.º 3
0
        public void SetFrameBufferTransform(float SX, float SY, float Rotate, float TX, float TY)
        {
            Matrix2 Transform;

            Transform  = Matrix2.CreateScale(SX, SY);
            Transform *= Matrix2.CreateRotation(Rotate);

            Vector2 Offs = new Vector2(TX, TY);

            ActionsQueue.Enqueue(() => FrameBuffer.SetTransform(Transform, Offs));
        }
Exemplo n.º 4
0
        public unsafe void SetFrameBuffer(
            byte *Fb,
            int Width,
            int Height,
            float ScaleX,
            float ScaleY,
            float Rotate)
        {
            Matrix2 Transform;

            Transform  = Matrix2.CreateScale(ScaleX, ScaleY);
            Transform *= Matrix2.CreateRotation(Rotate);

            FbRenderer.Set(Fb, Width, Height, Transform);
        }
Exemplo n.º 5
0
        /// <summary>
        /// This method is called every cycle used to update the state of the <see cref="Node"/>.
        /// </summary>
        /// <param name="gameTime">The current game time.</param>
        public virtual void Update(GameTime gameTime)
        {
            bool isDirtyThisFrame = IsDirty;

            if (IsDirty)
            {
                // compute local transform
                LocalTransform = Matrix2.CreateScale(Scale) *
                                 Matrix2.CreateRotationZ(MathHelper.ToRadians(Rotation)) *
                                 Matrix2.CreateTranslation(Position);

                // compute world transform for this node
                if (Parent != null)
                {
                    // compute world transform
                    WorldTransform = LocalTransform * Parent.WorldTransform;
                }
                else
                {
                    // no parent so WorldTransform and LocalTransform are equivalent.
                    WorldTransform = LocalTransform;
                }

                IsDirty = false;
            }

            // update children
            foreach (Node child in Children)
            {
                if (isDirtyThisFrame)
                {
                    child.IsDirty = true;
                }
                child.Update(gameTime);
            }
        }
Exemplo n.º 6
0
        /// <summary>
        /// Does a procedure similar to <see cref="MapCleaner"/> which adjusts the pattern so it fits in the beatmap.
        /// It does so according to the options selected in this.
        /// </summary>
        /// <param name="patternBeatmap"></param>
        /// <param name="beatmap"></param>
        /// <param name="parts"></param>
        /// <param name="timingPointsChanges"></param>
        private void PreparePattern(Beatmap patternBeatmap, Beatmap beatmap, out List <Part> parts, out List <TimingPointsChange> timingPointsChanges)
        {
            double patternStartTime = patternBeatmap.GetHitObjectStartTime();

            Timing originalTiming = beatmap.BeatmapTiming;
            Timing patternTiming  = patternBeatmap.BeatmapTiming;

            GameMode targetMode = (GameMode)beatmap.General["Mode"].IntValue;

            double originalCircleSize = beatmap.Difficulty["CircleSize"].DoubleValue;
            double patternCircleSize  = patternBeatmap.Difficulty["CircleSize"].DoubleValue;

            double originalTickRate = beatmap.Difficulty["SliderTickRate"].DoubleValue;
            double patternTickRate  = patternBeatmap.Difficulty["SliderTickRate"].DoubleValue;

            // Don't include SV changes if it is based on nothing
            bool includePatternSliderVelocity = patternTiming.Count > 0;

            // Avoid including hitsounds if there are no timingpoints to get hitsounds from
            bool includeTimingPointHitsounds = IncludeHitsounds && patternTiming.Count > 0;

            // Don't scale to new timing if the pattern has no timing to speak of
            bool scaleToNewTiming = ScaleToNewTiming && patternTiming.Redlines.Count > 0;

            // Avoid overwriting timing if the pattern has no redlines
            TimingOverwriteMode timingOverwriteMode = patternTiming.Redlines.Count > 0
                ? TimingOverwriteMode
                : TimingOverwriteMode.OriginalTimingOnly;

            // Get the scale for custom scale x CS scale
            double csScale = Beatmap.GetHitObjectRadius(originalCircleSize) /
                             Beatmap.GetHitObjectRadius(patternCircleSize);
            double spatialScale = ScaleToNewCircleSize && !double.IsNaN(csScale) ? CustomScale * csScale : CustomScale;

            // Get a BPM multiplier to fix the tick rate
            // This multiplier is not meant to change SV so this is subtracted from the greenline SV later
            double bpmMultiplier = FixTickRate ? patternTickRate / originalTickRate : 1;

            // Dont give new combo to all hit objects which were actually new combo in the pattern,
            // because it leads to unexpected NC's at the start of patterns.

            // Collect Kiai toggles
            List <TimingPoint> kiaiToggles = new List <TimingPoint>();
            bool lastKiai = false;

            // If not including the kiai of the pattern, add the kiai of the original map.
            // This has to be done because this part of the original map might get deleted.
            foreach (TimingPoint tp in IncludeKiai ? patternTiming.TimingPoints : originalTiming.TimingPoints)
            {
                if (tp.Kiai != lastKiai || kiaiToggles.Count == 0)
                {
                    kiaiToggles.Add(tp.Copy());
                    lastKiai = tp.Kiai;
                }
            }

            // Collect SliderVelocity changes for mania/taiko
            List <TimingPoint> svChanges = new List <TimingPoint>();
            double             lastSV    = -100;

            // If not including the SV of the pattern, add the SV of the original map.
            // This has to be done because this part of the original map might get deleted.
            foreach (TimingPoint tp in includePatternSliderVelocity ? patternTiming.TimingPoints : originalTiming.TimingPoints)
            {
                if (tp.Uninherited)
                {
                    lastSV = -100;
                }
                else
                {
                    if (Math.Abs(tp.MpB - lastSV) > Precision.DOUBLE_EPSILON)
                    {
                        svChanges.Add(tp.Copy());
                        lastSV = tp.MpB;
                    }
                }
            }

            // If not including the SV of the pattern, set the SV of sliders to that of the original beatmap,
            // so the pattern will take over the SV of the original beatmap.
            if (!includePatternSliderVelocity)
            {
                foreach (var ho in patternBeatmap.HitObjects.Where(ho => ho.IsSlider))
                {
                    ho.SliderVelocity = originalTiming.GetSvAtTime(ho.Time);
                }
            }

            // Get the timeline before moving all objects so it has the correct hitsounds
            // Make sure that moving the objects in the pattern moves the timeline objects aswell
            // This method is NOT safe to use in beat time
            Timeline patternTimeline         = patternBeatmap.GetTimeline();
            Timing   transformOriginalTiming = originalTiming;
            Timing   transformPatternTiming  = patternTiming;

            if (scaleToNewTiming)
            {
                // Transform everything to beat time relative to pattern start time
                foreach (var ho in patternBeatmap.HitObjects)
                {
                    double oldEndTime = ho.GetEndTime(false);

                    ho.Time    = patternTiming.GetBeatLength(patternStartTime, ho.Time);
                    ho.EndTime = patternTiming.GetBeatLength(patternStartTime, oldEndTime);

                    // The body hitsounds are not copies of timingpoints in patternTiming so they should be copied before changing offset
                    for (int i = 0; i < ho.BodyHitsounds.Count; i++)
                    {
                        TimingPoint tp = ho.BodyHitsounds[i].Copy();
                        tp.Offset           = patternTiming.GetBeatLength(patternStartTime, tp.Offset);
                        ho.BodyHitsounds[i] = tp;
                    }
                }

                foreach (var tp in kiaiToggles.Concat(svChanges))
                {
                    tp.Offset = patternTiming.GetBeatLength(patternStartTime, tp.Offset);
                }

                // Transform the pattern redlines to beat time
                // This will not change the order of redlines (unless negative BPM exists)
                transformPatternTiming = patternTiming.Copy();
                foreach (var tp in transformPatternTiming.Redlines)
                {
                    tp.Offset = patternTiming.GetBeatLength(patternStartTime, tp.Offset);
                }

                // Transform the original timingpoints to beat time
                // This will not change the order of timingpoints (unless negative BPM exists)
                transformOriginalTiming = originalTiming.Copy();
                foreach (var tp in transformOriginalTiming.TimingPoints)
                {
                    tp.Offset = originalTiming.GetBeatLength(patternStartTime, tp.Offset);
                }
            }

            // Fix SV for the new global SV
            var globalSvFactor = transformOriginalTiming.SliderMultiplier / transformPatternTiming.SliderMultiplier;

            if (FixGlobalSv)
            {
                foreach (HitObject ho in patternBeatmap.HitObjects.Where(o => o.IsSlider))
                {
                    ho.SliderVelocity *= globalSvFactor;
                }
                foreach (TimingPoint tp in svChanges)
                {
                    tp.MpB *= globalSvFactor;
                }
            }
            else
            {
                foreach (HitObject ho in patternBeatmap.HitObjects.Where(o => o.IsSlider))
                {
                    ho.TemporalLength /= globalSvFactor;
                }
            }

            // Partition the pattern based on the timing in the pattern
            if (PatternOverwriteMode == PatternOverwriteMode.PartitionedOverwrite)
            {
                parts = PartitionBeatmap(patternBeatmap, scaleToNewTiming);
            }
            else
            {
                parts = new List <Part> {
                    new Part(patternBeatmap.HitObjects[0].Time,
                             patternBeatmap.HitObjects[patternBeatmap.HitObjects.Count - 1].Time,
                             patternBeatmap.HitObjects)
                };
            }

            // Construct a new timing which is a mix of the beatmap and the pattern.
            // If scaleToNewTiming then use beat relative values to determine the duration of timing sections in the pattern.
            // scaleToNewTiming must scale all the partitions, timingpoints, hitobjects, and events (if applicable).
            Timing newTiming = new Timing(transformOriginalTiming.SliderMultiplier);

            var lastEndTime = double.NegativeInfinity;

            foreach (var part in parts)
            {
                var startTime = part.StartTime;
                var endTime   = part.EndTime; // Subtract one to omit BPM changes right on the end of the part.

                // Add the redlines in between patterns
                newTiming.AddRange(transformOriginalTiming.GetRedlinesInRange(lastEndTime, startTime, false));

                var startOriginalRedline = transformOriginalTiming.GetRedlineAtTime(startTime);

                // Minus 1 the offset so its possible to have a custom BPM redline right on the start time if you have
                // the default BPM redline before it.
                var patternDefaultMpb = transformPatternTiming.GetMpBAtTime(startTime - 2 * Precision.DOUBLE_EPSILON);

                TimingPoint[] inPartRedlines;
                TimingPoint   startPartRedline;
                switch (timingOverwriteMode)
                {
                case TimingOverwriteMode.PatternTimingOnly:
                    // Subtract one from the end time to omit BPM changes right on the end of the part.
                    inPartRedlines = transformPatternTiming.GetRedlinesInRange(startTime,
                                                                               Math.Max(startTime, endTime - 2 * Precision.DOUBLE_EPSILON)).ToArray();
                    startPartRedline = transformPatternTiming.GetRedlineAtTime(startTime);
                    break;

                case TimingOverwriteMode.InPatternAbsoluteTiming:
                    var tempInPartRedlines = transformPatternTiming.GetRedlinesInRange(startTime, endTime - 2 * Precision.DOUBLE_EPSILON);

                    // Replace all parts in the pattern which have the default BPM to timing from the target beatmap.
                    inPartRedlines = tempInPartRedlines.Select(tp => {
                        if (Precision.AlmostEquals(tp.MpB, patternDefaultMpb))
                        {
                            var tp2    = transformOriginalTiming.GetRedlineAtTime(tp.Offset).Copy();
                            tp2.Offset = tp2.Offset;
                            return(tp2);
                        }

                        return(tp);
                    }).ToArray();

                    startPartRedline = startOriginalRedline;
                    break;

                case TimingOverwriteMode.InPatternRelativeTiming:
                    // Multiply mix the pattern timing and the original timing together.
                    // The pattern timing divided by the default BPM will be used as a scalar for the original timing.
                    var tempInPartRedlines2    = transformPatternTiming.GetRedlinesInRange(startTime, endTime - 2 * Precision.DOUBLE_EPSILON);
                    var tempInOriginalRedlines = transformOriginalTiming.GetRedlinesInRange(startTime, endTime - 2 * Precision.DOUBLE_EPSILON);

                    // Replace all parts in the pattern which have the default BPM to timing from the target beatmap.
                    inPartRedlines = tempInPartRedlines2.Select(tp => {
                        var tp2  = tp.Copy();
                        tp2.MpB *= transformOriginalTiming.GetMpBAtTime(tp.Offset) / patternDefaultMpb;
                        return(tp2);
                    }).Concat(tempInOriginalRedlines.Select(tp => {
                        var tp2  = tp.Copy();
                        tp2.MpB *= transformPatternTiming.GetMpBAtTime(tp.Offset) / patternDefaultMpb;
                        return(tp2);
                    })).ToArray();

                    startPartRedline      = transformPatternTiming.GetRedlineAtTime(startTime).Copy();
                    startPartRedline.MpB *= transformOriginalTiming.GetMpBAtTime(startTime) / patternDefaultMpb;
                    break;

                default:      // Original timing only
                    // Subtract one from the end time to omit BPM changes right on the end of the part.
                    inPartRedlines = transformOriginalTiming.GetRedlinesInRange(startTime,
                                                                                Math.Max(startTime, endTime - 2 * Precision.DOUBLE_EPSILON)).ToArray();
                    startPartRedline = transformOriginalTiming.GetRedlineAtTime(startTime);
                    break;
                }

                // Add the redlines for inside the part
                newTiming.AddRange(inPartRedlines);

                // If the pattern starts with different BPM than the map add an extra redline at the start of the pattern
                // to make sure it the pattern starts out at the right BPM as we only copy the timingpoints during the pattern itself
                // and the redline may be way before that.
                // This will probably only do something on the PatternTimingOnly mode as the other modes make sure
                // the BPM at the start of the pattern will be the same as the original beatmap anyways.
                if (Math.Abs(startPartRedline.MpB * bpmMultiplier - startOriginalRedline.MpB) > Precision.DOUBLE_EPSILON)
                {
                    // We dont have to add the redline again if its already during the pattern.
                    if (Math.Abs(startPartRedline.Offset - startTime) > Precision.DOUBLE_EPSILON)
                    {
                        var copy = startPartRedline.Copy();
                        copy.Offset = startTime;
                        newTiming.Add(copy);
                    }
                }

                // Fix SV for the new BPM, so the SV effect of the new BPM is cancelled
                if (FixBpmSv)
                {
                    if (scaleToNewTiming)
                    {
                        foreach (HitObject ho in patternBeatmap.HitObjects.Where(o => o.IsSlider))
                        {
                            var bpmSvFactor = SnapToNewTiming ?
                                              transformPatternTiming.GetMpBAtTime(ho.Time) /
                                              newTiming.GetMpBAtTime(newTiming.ResnapBeatTime(ho.Time, BeatDivisors)) :
                                              transformPatternTiming.GetMpBAtTime(ho.Time) /
                                              newTiming.GetMpBAtTime(ho.Time);
                            ho.SliderVelocity *= bpmSvFactor;
                        }
                    }
                    else
                    {
                        foreach (HitObject ho in patternBeatmap.HitObjects.Where(o => o.IsSlider))
                        {
                            var bpmSvFactor = SnapToNewTiming ?
                                              transformPatternTiming.GetMpBAtTime(ho.Time) / newTiming.GetMpBAtTime(newTiming.Resnap(ho.Time, BeatDivisors)) :
                                              transformPatternTiming.GetMpBAtTime(ho.Time) / newTiming.GetMpBAtTime(ho.Time);
                            ho.SliderVelocity *= bpmSvFactor;
                        }
                    }
                }

                // Recalculate temporal length and re-assign redline for the sliderend resnapping later
                foreach (var ho in part.HitObjects)
                {
                    ho.UnInheritedTimingPoint = newTiming.GetRedlineAtTime(ho.Time);
                    if (ho.IsSlider)
                    {
                        // If scaleToNewTiming then the end time is already at the correct beat time
                        // The SV has to be adjusted so the sliderend is really on the end time
                        if (scaleToNewTiming)
                        {
                            var wantedMsDuration = (newTiming.GetMilliseconds(ho.GetEndTime(false), patternStartTime) -
                                                    newTiming.GetMilliseconds(ho.Time, patternStartTime)) / ho.Repeat;
                            var trueMsDuration = newTiming.CalculateSliderTemporalLength(SnapToNewTiming ? newTiming.ResnapBeatTime(ho.Time, BeatDivisors) : ho.Time, ho.PixelLength, ho.SliderVelocity);
                            ho.SliderVelocity /= trueMsDuration / wantedMsDuration;
                        }
                        else
                        {
                            ho.TemporalLength = newTiming.CalculateSliderTemporalLength(SnapToNewTiming ? newTiming.Resnap(ho.Time, BeatDivisors) : ho.Time, ho.PixelLength, ho.SliderVelocity);
                        }
                    }
                }

                // Update the end time because the lengths of sliders changed
                endTime      = part.HitObjects.Max(o => o.GetEndTime(!scaleToNewTiming));
                part.EndTime = endTime;

                // Add a redline at the end of the pattern to make sure the BPM goes back to normal after the pattern.
                var endOriginalRedline = transformOriginalTiming.GetRedlineAtTime(endTime);
                var endPartRedline     = inPartRedlines.LastOrDefault() ?? startPartRedline;
                if (Math.Abs(endPartRedline.MpB * bpmMultiplier - endOriginalRedline.MpB) > Precision.DOUBLE_EPSILON)
                {
                    // We dont have to add the redline again if its already during the parts in between parts.
                    if (Math.Abs(endOriginalRedline.Offset - endTime) > Precision.DOUBLE_EPSILON)
                    {
                        var copy = endOriginalRedline.Copy();
                        copy.Offset = endTime;
                        newTiming.Add(copy);
                    }
                }

                lastEndTime = endTime;
            }

            // Transform the beat time back to millisecond time
            Timing transformNewTiming = newTiming;

            if (scaleToNewTiming)
            {
                // Transform back the timing
                transformNewTiming = newTiming.Copy();
                foreach (var tp in transformNewTiming.TimingPoints)
                {
                    tp.Offset = Math.Floor(newTiming.GetMilliseconds(tp.Offset, patternStartTime) + Precision.DOUBLE_EPSILON);
                }

                // Transform back the parts
                foreach (Part part in parts)
                {
                    part.StartTime = Math.Floor(newTiming.GetMilliseconds(part.StartTime, patternStartTime));
                    part.EndTime   = Math.Floor(newTiming.GetMilliseconds(part.EndTime, patternStartTime));
                }

                // Transform everything to millisecond time relative to pattern start time
                foreach (var ho in patternBeatmap.HitObjects)
                {
                    // Calculate the millisecond end time before changing the start time because the end time getter uses the beat time start time
                    var msEndTime = newTiming.GetMilliseconds(ho.GetEndTime(false), patternStartTime);

                    ho.Time = newTiming.GetMilliseconds(ho.Time, patternStartTime);

                    // End time has to be set after the time because the end time setter uses the millisecond start time
                    ho.EndTime = msEndTime;

                    foreach (var tp in ho.BodyHitsounds)
                    {
                        tp.Offset = newTiming.GetMilliseconds(tp.Offset, patternStartTime);
                    }

                    // It is necessary to resnap early so it can recalculate the duration using the right offset
                    if (SnapToNewTiming)
                    {
                        ho.ResnapSelf(transformNewTiming, BeatDivisors);
                    }

                    if (ho.IsSlider)
                    {
                        ho.CalculateSliderTemporalLength(transformNewTiming, true);
                    }

                    ho.UnInheritedTimingPoint = transformNewTiming.GetRedlineAtTime(ho.Time);
                    ho.UpdateTimelineObjectTimes();
                }

                foreach (var tp in kiaiToggles.Concat(svChanges))
                {
                    tp.Offset = Math.Floor(newTiming.GetMilliseconds(tp.Offset, patternStartTime));
                }
            }

            // Apply custom scale and rotate
            if (Math.Abs(spatialScale - 1) > Precision.DOUBLE_EPSILON ||
                Math.Abs(CustomRotate) > Precision.DOUBLE_EPSILON)
            {
                // Create a transformation matrix for the custom scale and rotate
                // The rotation is inverted because the default osu! rotation goes clockwise
                Matrix2 transform = Matrix2.Mult(Matrix2.CreateScale(spatialScale), Matrix2.CreateRotation(-CustomRotate));
                Vector2 centre    = new Vector2(256, 192);
                foreach (var ho in patternBeatmap.HitObjects)
                {
                    ho.Move(-centre);
                    ho.Transform(transform);
                    ho.Move(centre);

                    // Scale pixel length and SV for sliders aswell
                    if (ho.IsSlider)
                    {
                        ho.PixelLength    *= spatialScale;
                        ho.SliderVelocity /= spatialScale;
                    }
                }

                // osu! clips coordinates to the bounds (0,512), so there is some space downwards to still place the pattern
                // Calculate the new bounds of the pattern and try to place it in the playfield
                var     minX   = patternBeatmap.HitObjects.Min(o => o.Pos.X);
                var     minY   = patternBeatmap.HitObjects.Min(o => o.Pos.Y);
                Vector2 offset = new Vector2(Math.Max(-minX, 0), Math.Max(-minY, 0));
                if (offset.LengthSquared > 0)
                {
                    foreach (var ho in patternBeatmap.HitObjects)
                    {
                        ho.Move(offset);
                    }
                }
            }

            // Manualify stacks
            if (FixStackLeniency)
            {
                // If scale to new timing was used update the circle size of the pattern,
                // so it calculates stacks at the new size of the pattern.
                if (ScaleToNewCircleSize)
                {
                    patternBeatmap.Difficulty["CircleSize"].DoubleValue = originalCircleSize;
                }

                patternBeatmap.CalculateEndPositions();
                patternBeatmap.UpdateStacking(rounded: true);

                // Manualify by setting the base position to the stacked position
                foreach (var ho in patternBeatmap.HitObjects)
                {
                    var offset = ho.StackedPos - ho.Pos;
                    ho.Move(offset);
                }
            }

            // Resnap everything to the new timing.
            if (SnapToNewTiming)
            {
                // Resnap all objects
                foreach (HitObject ho in patternBeatmap.HitObjects)
                {
                    ho.ResnapSelf(transformNewTiming, BeatDivisors);
                    ho.ResnapEnd(transformNewTiming, BeatDivisors);
                    ho.ResnapPosition(targetMode, patternCircleSize);  // Resnap to column X positions for mania only
                }
                // Resnap Kiai toggles
                foreach (TimingPoint tp in kiaiToggles)
                {
                    tp.ResnapSelf(transformNewTiming, BeatDivisors);
                }

                // Resnap SliderVelocity changes
                foreach (TimingPoint tp in svChanges)
                {
                    tp.ResnapSelf(transformNewTiming, BeatDivisors);
                }
            }

            // Multiply BPM and divide SV
            foreach (var part in parts)
            {
                foreach (var tp in transformNewTiming.GetRedlinesInRange(part.StartTime - 2 * Precision.DOUBLE_EPSILON, part.EndTime, false))
                {
                    tp.MpB /= bpmMultiplier;  // MpB is the inverse of the BPM
                }

                foreach (var ho in part.HitObjects)
                {
                    ho.SliderVelocity *= bpmMultiplier;  // SliderVelocity is the inverse of the multiplier
                }
            }

            // Make new timingpoints changes for the hitsounds and other stuff

            // Add redlines
            timingPointsChanges = transformNewTiming.Redlines.Select(tp =>
                                                                     new TimingPointsChange(tp, mpb: true, meter: true, unInherited: true, omitFirstBarLine: true, fuzzyness: Precision.DOUBLE_EPSILON)).ToList();

            // Add SliderVelocity changes for taiko and mania
            if (includePatternSliderVelocity && (targetMode == GameMode.Taiko || targetMode == GameMode.Mania))
            {
                timingPointsChanges.AddRange(svChanges.Select(tp => new TimingPointsChange(tp, mpb: true, fuzzyness: 0.4)));
            }

            // Add Kiai toggles
            timingPointsChanges.AddRange(kiaiToggles.Select(tp => new TimingPointsChange(tp, kiai: true)));

            // Add Hitobject stuff
            foreach (HitObject ho in patternBeatmap.HitObjects)
            {
                if (ho.IsSlider) // SliderVelocity changes
                {
                    TimingPoint tp = ho.TimingPoint.Copy();
                    tp.Offset = ho.Time;
                    tp.MpB    = ho.SliderVelocity;
                    timingPointsChanges.Add(new TimingPointsChange(tp, mpb: true, fuzzyness: 0.4));
                }

                if (!IncludeHitsounds)
                {
                    // Remove hitsounds and skip adding body hitsounds
                    ho.ResetHitsounds();
                    continue;
                }

                if (includeTimingPointHitsounds)
                {
                    // Body hitsounds
                    bool vol = ho.IsSlider || ho.IsSpinner;
                    bool sam = ho.IsSlider && ho.SampleSet == 0;
                    bool ind = ho.IsSlider;
                    timingPointsChanges.AddRange(ho.BodyHitsounds.Select(tp =>
                                                                         new TimingPointsChange(tp, volume: vol, index: ind, sampleset: sam)));
                }
            }

            // Add timeline hitsounds
            if (includeTimingPointHitsounds)
            {
                foreach (TimelineObject tlo in patternTimeline.TimelineObjects)
                {
                    if (tlo.HasHitsound)
                    {
                        // Add greenlines for hitsounds
                        TimingPoint tp = tlo.HitsoundTimingPoint.Copy();
                        tp.Offset = tlo.Time;
                        timingPointsChanges.Add(new TimingPointsChange(tp, sampleset: true, volume: true, index: true));
                    }
                }
            }

            // Replace the old timingpoints
            patternTiming.Clear();
            TimingPointsChange.ApplyChanges(patternTiming, timingPointsChanges);

            patternBeatmap.GiveObjectsGreenlines();
            patternBeatmap.CalculateSliderEndTimes();
        }
Exemplo n.º 7
0
        /// <summary>
        /// Computes per-frame information necessary for the constraint.
        /// </summary>
        /// <param name="dt">Time step duration.</param>
        public override void Update(float dt)
        {
            bool isTryingToMove = movementDirection3d.LengthSquared > 0;

            if (!isTryingToMove)
            {
                TargetSpeed = 0;
            }

            maxForce = MaximumForce * dt;


            //Compute the jacobians.  This is basically a PointOnLineJoint with motorized degrees of freedom.
            Vector3 downDirection = characterBody.orientationMatrix4.Down;

            if (MovementMode != MovementMode.Floating)
            {
                //Compute the linear jacobians first.
                if (isTryingToMove)
                {
                    Vector3 velocityDirection;
                    Vector3 offVelocityDirection;
                    //Project the movement direction onto the support plane defined by the support normal.
                    //This projection is NOT along the support normal to the plane; that would cause the character to veer off course when moving on slopes.
                    //Instead, project along the sweep direction to the plane.
                    //For a 6DOF character controller, the lineStart would be different; it must be perpendicular to the local up.
                    Vector3 lineStart = movementDirection3d;

                    Vector3 lineEnd;
                    Vector3.Add(ref lineStart, ref downDirection, out lineEnd);
                    Plane plane = new Plane(supportData.Normal, 0);
                    float t;
                    //This method can return false when the line is parallel to the plane, but previous tests and the slope limit guarantee that it won't happen.
                    Toolbox.GetLinePlaneIntersection(ref lineStart, ref lineEnd, ref plane, out t, out velocityDirection);

                    //The origin->intersection line direction defines the horizontal velocity direction in 3d space.
                    velocityDirection.Normalize();


                    //The normal and velocity direction are perpendicular and normal, so the off velocity direction doesn't need to be normalized.
                    Vector3.Cross(ref velocityDirection, ref supportData.Normal, out offVelocityDirection);

                    linearJacobianA1 = velocityDirection;
                    linearJacobianA2 = offVelocityDirection;
                    linearJacobianB1 = -velocityDirection;
                    linearJacobianB2 = -offVelocityDirection;
                }
                else
                {
                    //If the character isn't trying to move, then the velocity directions are not well defined.
                    //Instead, pick two arbitrary vectors on the support plane.
                    //First guess will be based on the previous jacobian.
                    //Project the old linear jacobian onto the support normal plane.
                    float dot;
                    Vector3.Dot(ref linearJacobianA1, ref supportData.Normal, out dot);
                    Vector3 toRemove;
                    Vector3.Multiply(ref supportData.Normal, dot, out toRemove);
                    Vector3.Subtract(ref linearJacobianA1, ref toRemove, out linearJacobianA1);

                    //Vector3.Cross(ref linearJacobianA2, ref supportData.Normal, out linearJacobianA1);
                    float length = linearJacobianA1.LengthSquared;
                    if (length < Toolbox.Epsilon)
                    {
                        //First guess failed.  Try the right vector.
                        Vector3.Cross(ref Toolbox.RightVector, ref supportData.Normal, out linearJacobianA1);
                        length = linearJacobianA1.LengthSquared;
                        if (length < Toolbox.Epsilon)
                        {
                            //Okay that failed too! try the forward vector.
                            Vector3.Cross(ref Toolbox.ForwardVector, ref supportData.Normal, out linearJacobianA1);
                            length = linearJacobianA1.LengthSquared;
                            //Unless something really weird is happening, we do not need to test any more axes.
                        }
                    }
                    Vector3.Divide(ref linearJacobianA1, (float)Math.Sqrt(length), out linearJacobianA1);
                    //Pick another perpendicular vector.  Don't need to normalize it since the normal and A1 are already normalized and perpendicular.
                    Vector3.Cross(ref linearJacobianA1, ref supportData.Normal, out linearJacobianA2);

                    //B's linear jacobians are just -A's.
                    linearJacobianB1 = -linearJacobianA1;
                    linearJacobianB2 = -linearJacobianA2;
                }

                if (supportEntity != null)
                {
                    //Compute the angular jacobians.
                    Vector3 supportToContact = supportData.Position - supportEntity.Position;
                    //Since we treat the character to have infinite inertia, we're only concerned with the support's angular jacobians.
                    //Note the order of the cross product- it is reversed to negate the result.
                    Vector3.Cross(ref linearJacobianA1, ref supportToContact, out angularJacobianB1);
                    Vector3.Cross(ref linearJacobianA2, ref supportToContact, out angularJacobianB2);
                }
                else
                {
                    //If we're not standing on an entity, there are no angular jacobians.
                    angularJacobianB1 = new Vector3();
                    angularJacobianB2 = new Vector3();
                }
            }
            else
            {
                //If the character is floating, then the jacobians are simply the 3d movement direction and the perpendicular direction on the character's horizontal plane.
                linearJacobianA1 = movementDirection3d;
                linearJacobianA2 = Vector3.Cross(linearJacobianA1, characterBody.orientationMatrix4.Down);
            }


            //Compute the target velocity (in constraint space) for this frame.  The hard work has already been done.
            targetVelocity.X = TargetSpeed;
            targetVelocity.Y = 0;

            //Compute the effective mass Matrix4.
            if (supportEntity != null && supportEntity.IsDynamic)
            {
                float   m11, m22, m1221 = 0;
                float   inverseMass;
                Vector3 intermediate;

                inverseMass = characterBody.InverseMass;
                m11         = inverseMass;
                m22         = inverseMass;


                //Scale the inertia and mass of the support.  This will make the solver view the object as 'heavier' with respect to horizontal motion.
                Matrix3x3 inertiaInverse = supportEntity.InertiaTensorInverse;
                Matrix3x3.Multiply(ref inertiaInverse, supportForceFactor, out inertiaInverse);
                float extra;
                inverseMass = supportForceFactor * supportEntity.InverseMass;
                Matrix3x3.Transform(ref angularJacobianB1, ref inertiaInverse, out intermediate);
                Vector3.Dot(ref intermediate, ref angularJacobianB1, out extra);
                m11 += inverseMass + extra;
                Vector3.Dot(ref intermediate, ref angularJacobianB2, out extra);
                m1221 += extra;
                Matrix3x3.Transform(ref angularJacobianB2, ref inertiaInverse, out intermediate);
                Vector3.Dot(ref intermediate, ref angularJacobianB2, out extra);
                m22 += inverseMass + extra;


                massMatrix4.M11 = m11;
                massMatrix4.M12 = m1221;
                massMatrix4.M21 = m1221;
                massMatrix4.M22 = m22;
                Matrix2.Invert(ref massMatrix4, out massMatrix4);
            }
            else
            {
                //If we're not standing on a dynamic entity, then the mass Matrix4 is defined entirely by the character.
                Matrix2.CreateScale(characterBody.Mass, out massMatrix4);
            }

            //If we're trying to stand still on an object that's moving, use a position correction term to keep the character
            //from drifting due to accelerations.
            //First thing to do is to check to see if we're moving into a traction/trying to stand still state from a
            //non-traction || trying to move state.  Either that, or we've switched supports and need to update the offset.
            if (supportEntity != null && ((wasTryingToMove && !isTryingToMove) || (!hadTraction && supportFinder.HasTraction) || supportEntity != previousSupportEntity))
            {
                //We're transitioning into a new 'use position correction' state.
                //Force a recomputation of the local offset.
                //The time since transition is used as a flag.
                timeSinceTransition = 0;
            }

            //The state is now up to date.  Compute an error and velocity bias, if needed.
            if (!isTryingToMove && MovementMode == MovementMode.Traction && supportEntity != null)
            {
                var distanceToBottomOfCharacter = supportFinder.BottomDistance;

                if (timeSinceTransition >= 0 && timeSinceTransition < timeUntilPositionAnchor)
                {
                    timeSinceTransition += dt;
                }
                if (timeSinceTransition >= timeUntilPositionAnchor)
                {
                    Vector3.Multiply(ref downDirection, distanceToBottomOfCharacter, out positionLocalOffset);
                    positionLocalOffset = (positionLocalOffset + characterBody.Position) - supportEntity.Position;
                    positionLocalOffset = Matrix3x3.TransformTranspose(positionLocalOffset, supportEntity.OrientationMatrix4);
                    timeSinceTransition = -1; //Negative 1 means that the offset has been computed.
                }
                if (timeSinceTransition < 0)
                {
                    Vector3 targetPosition;
                    Vector3.Multiply(ref downDirection, distanceToBottomOfCharacter, out targetPosition);
                    targetPosition += characterBody.Position;
                    Vector3 worldSupportLocation = Matrix3x3.Transform(positionLocalOffset, supportEntity.OrientationMatrix4) + supportEntity.Position;
                    Vector3 error;
                    Vector3.Subtract(ref targetPosition, ref worldSupportLocation, out error);
                    //If the error is too large, then recompute the offset.  We don't want the character rubber banding around.
                    if (error.LengthSquared > PositionAnchorDistanceThreshold * PositionAnchorDistanceThreshold)
                    {
                        Vector3.Multiply(ref downDirection, distanceToBottomOfCharacter, out positionLocalOffset);
                        positionLocalOffset    = (positionLocalOffset + characterBody.Position) - supportEntity.Position;
                        positionLocalOffset    = Matrix3x3.TransformTranspose(positionLocalOffset, supportEntity.OrientationMatrix4);
                        positionCorrectionBias = new Vector2();
                    }
                    else
                    {
                        //The error in world space is now available.  We can't use this error to directly create a velocity bias, though.
                        //It needs to be transformed into constraint space where the constraint operates.
                        //Use the jacobians!
                        Vector3.Dot(ref error, ref linearJacobianA1, out positionCorrectionBias.X);
                        Vector3.Dot(ref error, ref linearJacobianA2, out positionCorrectionBias.Y);
                        //Scale the error so that a portion of the error is resolved each frame.
                        Vector2.Multiply(ref positionCorrectionBias, .2f / dt, out positionCorrectionBias);
                    }
                }
            }
            else
            {
                timeSinceTransition    = 0;
                positionCorrectionBias = new Vector2();
            }

            wasTryingToMove       = isTryingToMove;
            hadTraction           = supportFinder.HasTraction;
            previousSupportEntity = supportEntity;
        }