/// <summary> /// Generates a bezier curve for the segment using a least-squares approximation. for the derivation of this and why it works, /// see http://read.pudn.com/downloads141/ebook/610086/Graphics_Gems_I.pdf page 626 and beyond. tl;dr: math. /// </summary> protected CubicBezier GenerateBezier(int first, int last, VECTOR tanL, VECTOR tanR) { #if OPTIMIZED_GENERATEBEZIERSIMD using var _ = GenerateBezierMarker.Auto(); unsafe { using (_pts.ViewAsNativeArray(out var pts)) using (_u.ViewAsNativeArray(out var u)) { OptimizedHelpers.GenerateBezier(first, last, tanL, tanR, (VECTOR *)pts.GetUnsafePtr(), (FLOAT *)u.GetUnsafePtr(), out var bezier); return(bezier); } } #elif OPTIMIZED_GENERATEBEZIER using var _ = GenerateBezierMarker.Auto(); unsafe { using (_pts.ViewAsNativeArray(out var pts)) using (_u.ViewAsNativeArray(out var u)) { OptimizedHelpers.OptimizedGenerateBezier(first, last, tanL, tanR, (VECTOR *)pts.GetUnsafePtr(), (FLOAT *)u.GetUnsafePtr(), out var bezier); return(bezier); } } #else using var _ = GenerateBezierMarker.Auto(); List <VECTOR> pts = _pts; List <FLOAT> u = _u; int nPts = last - first + 1; VECTOR p0 = pts[first], p3 = pts[last]; // first and last points of curve are actual points on data FLOAT c00 = 0, c01 = 0, c11 = 0, x0 = 0, x1 = 0; // matrix members -- both C[0,1] and C[1,0] are the same, stored in c01 for (int i = 1; i < nPts; i++) { // Calculate cubic bezier multipliers FLOAT t = u[i]; FLOAT ti = 1 - t; FLOAT t0 = ti * ti * ti; FLOAT t1 = 3 * ti * ti * t; FLOAT t2 = 3 * ti * t * t; FLOAT t3 = t * t * t; // For X matrix; moving this up here since profiling shows it's better up here (maybe a0/a1 not in registers vs only v not in regs) VECTOR s = (p0 * t0) + (p0 * t1) + (p3 * t2) + (p3 * t3); // NOTE: this would be Q(t) if p1=p0 and p2=p3 VECTOR v = pts[first + i] - s; // C matrix VECTOR a0 = tanL * t1; VECTOR a1 = tanR * t2; c00 += VectorHelper.Dot(a0, a0); c01 += VectorHelper.Dot(a0, a1); c11 += VectorHelper.Dot(a1, a1); // X matrix x0 += VectorHelper.Dot(a0, v); x1 += VectorHelper.Dot(a1, v); } // determinents of X and C matrices FLOAT det_C0_C1 = c00 * c11 - c01 * c01; FLOAT det_C0_X = c00 * x1 - c01 * x0; FLOAT det_X_C1 = x0 * c11 - x1 * c01; FLOAT alphaL = det_X_C1 / det_C0_C1; FLOAT alphaR = det_C0_X / det_C0_C1; // if alpha is negative, zero, or very small (or we can't trust it since C matrix is small), fall back to Wu/Barsky heuristic FLOAT linDist = VectorHelper.Distance(p0, p3); FLOAT epsilon2 = EPSILON * linDist; if (Math.Abs(det_C0_C1) < EPSILON || alphaL < epsilon2 || alphaR < epsilon2) { FLOAT alpha = linDist / 3; VECTOR p1 = (tanL * alpha) + p0; VECTOR p2 = (tanR * alpha) + p3; return(new CubicBezier(p0, p1, p2, p3)); } else { VECTOR p1 = (tanL * alphaL) + p0; VECTOR p2 = (tanR * alphaR) + p3; return(new CubicBezier(p0, p1, p2, p3)); } #endif }