diff --git a/Monofoxe/Monofoxe.Engine/Drawing/BezierCurve.cs b/Monofoxe/Monofoxe.Engine/Drawing/BezierCurve.cs new file mode 100644 index 0000000..25e59f4 --- /dev/null +++ b/Monofoxe/Monofoxe.Engine/Drawing/BezierCurve.cs @@ -0,0 +1,18 @@ +using Microsoft.Xna.Framework; +using Monofoxe.Engine.Utils; + +namespace Monofoxe.Engine.Drawing +{ + public class BezierCurve + { + public static void Draw(Vector2[] controlPoints, float interval = 0.01f) + { + var points = GameMath.BezierCurvePoints(controlPoints, interval); + + for (var i = 0; i < points.Length - 1; i += 1) + { + LineShape.Draw(points[i], points[i + 1]); + } + } + } +} diff --git a/Monofoxe/Monofoxe.Engine/Utils/GameMath.cs b/Monofoxe/Monofoxe.Engine/Utils/GameMath.cs index 8537853..17d2bdf 100644 --- a/Monofoxe/Monofoxe.Engine/Utils/GameMath.cs +++ b/Monofoxe/Monofoxe.Engine/Utils/GameMath.cs @@ -208,24 +208,124 @@ public static float GetArea(List vertices) } + + #region Bezier curve stuff. + // A look up table for factorials. Capped to 16. + private static float[] Factorial = new float[] + { + 1.0f, + 1.0f, + 2.0f, + 6.0f, + 24.0f, + 120.0f, + 720.0f, + 5040.0f, + 40320.0f, + 362880.0f, + 3628800.0f, + 39916800.0f, + 479001600.0f, + 6227020800.0f, + 87178291200.0f, + 1307674368000.0f, + 20922789888000.0f, + }; + + private static float Binomial(int n, int i) + { + float ni; + float a1 = Factorial[n]; + float a2 = Factorial[i]; + float a3 = Factorial[n - i]; + + ni = a1 / (a2 * a3); + + return ni; + } + + private static float Bernstein(int n, int i, float value) + { + float value_i = MathF.Pow(value, i); + float value_n_minus_i = MathF.Pow((1 - value), (n - i)); + + float basis = Binomial(n, i) * value_i * value_n_minus_i; + + return basis; + } + #endregion + + /// - /// Calculates three-point bezier curve. + /// Calculates bezier curve. /// /// Should be in 0..1 range. - public static Vector2 BezierCurve(Vector2 startPoint, Vector2 controlPoint, Vector2 endPoint, float value) + public static Vector2 BezierCurve(Vector2[] controlPoints, float value) { - float u = 1 - value; - float tt = value * value; - float uu = u * u; + int N = controlPoints.Length - 1; + + if (N > 16) + { + throw new Exception("The maximum control points allowed is 16."); + } + + + if (value <= 0) + { + return controlPoints[0]; + } + if (value >= 1) + { + return controlPoints[controlPoints.Length - 1]; + } + - Vector2 pointOnCurve = uu * startPoint; - pointOnCurve += 2 * u * value * controlPoint; - pointOnCurve += tt * endPoint; + Vector2 pointOnCurve = new Vector2(); + + for (int i = 0; i < controlPoints.Length; ++i) + { + Vector2 bn = Bernstein(N, i, value) * controlPoints[i]; + + pointOnCurve += bn; + } return pointOnCurve; } + /// + /// Returns an array of points spaced with passed interval. + /// + public static Vector2[] BezierCurvePoints(Vector2[] controlPoints, float interval = 0.01f) + { + int N = controlPoints.Length - 1; + + if (N > 16) + { + throw new Exception("The maximum control points allowed is 16."); + } + + + List points = new List(); + + for (float t = 0.0f; t <= 1.0f + interval - 0.0001f; t += interval) + { + Vector2 p = new Vector2(); + + for (int i = 0; i < controlPoints.Length; ++i) + { + Vector2 bn = Bernstein(N, i, t) * controlPoints[i]; + + p += bn; + } + + points.Add(p); + } + + return points.ToArray(); + } + + /// /// Returns a projection aligned to the Y axis. /// diff --git a/Samples/Monofoxe.Samples/Demos/UtilsDemo.cs b/Samples/Monofoxe.Samples/Demos/UtilsDemo.cs index 1755d64..e1dc4ac 100644 --- a/Samples/Monofoxe.Samples/Demos/UtilsDemo.cs +++ b/Samples/Monofoxe.Samples/Demos/UtilsDemo.cs @@ -56,7 +56,7 @@ public class UtilsDemo : Entity Sprite _fireSprite; - + Vector2[] _bezierCurvePoints; public UtilsDemo(Layer layer) : base(layer) { @@ -118,6 +118,18 @@ public UtilsDemo(Layer layer) : base(layer) _stateMachine.AddState(TestStates.Blue, Blue); _stateMachine.AddState(TestStates.Red, Red); // State machine. + + + // Bezier curve. + _bezierCurvePoints = new Vector2[] + { + new Vector2(57, 410), + new Vector2(170, 500), + new Vector2(291, 410), + new Vector2(330, 500) + }; + // Bezier curve. + } /// @@ -313,6 +325,8 @@ public override void Draw() DrawDampers(); + + DrawBeizerCurve(); } private FloatDamper _floatDamper1 = new FloatDamper(0, 1, 1, 0); @@ -345,6 +359,25 @@ private void DrawDampers() LineShape.Draw(_baseAnglePos, _baseAnglePos + _angleDamper.Value.ToVector2() * 50); } + private void DrawBeizerCurve() + { + for (var i = 0; i < _bezierCurvePoints.Length - 1; i += 1) + { + CircleShape.Draw(_bezierCurvePoints[i], 3, ShapeFill.Solid); + + LineShape.Draw(_bezierCurvePoints[i], _bezierCurvePoints[i + 1]); + } + CircleShape.Draw(_bezierCurvePoints[_bezierCurvePoints.Length - 1], 3, ShapeFill.Solid); + + + var prevColor = GraphicsMgr.CurrentColor; + GraphicsMgr.CurrentColor = Color.Red; + + BezierCurve.Draw(_bezierCurvePoints); + + GraphicsMgr.CurrentColor = prevColor; + } + public override void Destroy() { base.Destroy();