using Unity.Mathematics; using Unity.Burst; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // For Bezier formulas reference use great online book: https://pomax.github.io/bezierinfo/ ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// namespace Rukhanka.Toolbox { [BurstCompile] public static class LineTools { public static float2 Get2DLineEquationFromTwoPoints(float2 p0, float2 p1) { var k = (p1.y - p0.y) / (p1.x - p0.x); var b = p1.y - k * p1.x; return new float2(k, b); } ///////////////////////////////////////////////////////////////////////////////// static float3 CalculateBoundingCircle(float2 p0, float2 p1, float2 p2) { var pc1 = (p1 + p0) * 0.5f; var pc2 = (p1 + p2) * 0.5f; var p1p0 = p1 - p0; var p1p2 = p1 - p2; float2 sincosHalfPi = new float2(1, 0); // Normals var n1 = new float2(math.dot(p1p0, sincosHalfPi.yx * new float2(1, -1)), math.dot(p1p0, sincosHalfPi)); var n2 = new float2(math.dot(p1p2, sincosHalfPi.yx * new float2(1, -1)), math.dot(p1p2, sincosHalfPi)); var pn1 = pc1 - n1; var pn2 = pc2 + n2; // Circle center var dx1 = pc1.x - pn1.x; var dx2 = pc2.x - pn2.x; var center = float2.zero; if (math.abs(dx1) < 0.0001f) { center.x = pc1.x; var kb = Get2DLineEquationFromTwoPoints(pc2, pn2); center.y = kb.x * center.x + kb.y; } else if (math.abs(dx2) < 0.0001f) { center.x = pc2.x; var kb = Get2DLineEquationFromTwoPoints(pc1, pn1); center.y = kb.x * center.x + kb.y; } else { var kb1 = Get2DLineEquationFromTwoPoints(pc1, pn1); var kb2 = Get2DLineEquationFromTwoPoints(pc2, pn2); var dk = kb1.x - kb2.x; center.x = dk != 0 ? (kb2.y - kb1.y) / dk : 0; center.y = center.x * kb1.x + kb1.y; } var circleR = math.length(p0 - center); return new float3(center, circleR); } ///////////////////////////////////////////////////////////////////////////////// static float4 BezierGetAC(float2 p0, float2 p1, float2 p2, float t) { var t2 = t * t; var t3 = t2 * t; var oneMinusT = 1 - t; var oneMinusT3 = oneMinusT * oneMinusT * oneMinusT; var ratio = math.abs((t3 + oneMinusT3 - 1) / (t3 + oneMinusT3)); var ut = oneMinusT3 / (t3 + oneMinusT3); var c = math.lerp(p2, p0, ut); var a = p1 + (p1 - c) / ratio; return new float4(a, c); } ///////////////////////////////////////////////////////////////////////////////// public struct CubicBezierCurve { public float2 s, e, c1, c2, e1, e2, a, c; } ///////////////////////////////////////////////////////////////////////////////// [BurstCompile] public static void ConstructBezierApproximation(in float2 p0, in float2 p1, in float2 p2, ref CubicBezierCurve rv) { var p2p0 = p2 - p0; var p1p0 = p1 - p0; var p1p2 = p1 - p2; var d1 = math.length(p1p0); var d2 = math.length(p1p2); var t = d1 / (d1 + d2); var angle = math.atan2(p2p0.y, p2p0.x) - math.atan2(p1p0.y, p1p0.x); var sgn = angle < 0 || angle > math.PI ? -1 : 1; var bc = math.length(p2p0) * sgn * 0.333f; var de1 = t * bc; var de2 = (1 - t) * bc; var boundingCircle = CalculateBoundingCircle(p0, p1, p2); var tanL = new float2(p1.x - (p1.y - boundingCircle.y), p1.y + (p1.x - boundingCircle.x)); var tanR = new float2(p1.x + (p1.y - boundingCircle.y), p1.y - (p1.x - boundingCircle.x)); var tanLen = math.length(new float4(tanL, tanR)); var dx = (tanR.x - tanL.x) / tanLen; var dy = (tanR.y - tanL.y) / tanLen; var dxdy = new float2(dx, dy); var e1 = p1 + de1 * dxdy; var e2 = p1 - de2 * dxdy; var ac = BezierGetAC(p0, p1, p2, t); var a = ac.xy; var v1 = a + (e1 - a) / (1 - t); var v2 = a + (e2 - a) / t; var c1 = p0 + (v1 - p0) / t; var c2 = p2 + (v2 - p2) / (1 - t); rv = new CubicBezierCurve() { c1 = c1, c2 = c2, s = p0, e = p2, e1 = e1, e2 = e2, c = ac.zw, a = ac.xy }; } } }