using CommonUsage.Mathematics; using System.Collections.Generic; using System.Numerics; using System; using System.Linq; namespace CommonUsage.Geometries { public class NurbsCurve : AbstractGeometry { public NurbsCurve(List controlPoints, List weights, List knotVector, int frame = 100) { _controlPoints = controlPoints; _weights = weights; _knotVector = knotVector; _frame = frame; InitializeNurbs(); } public override void Visualize(Action processDot, Action processLine, bool visExtendedPart = false) { if (VisualizeOption.DrawAuxiliary) for (var i = 0; i < _controlPoints.Count - 1; ++i) { processLine(new VisLine(_controlPoints[i], _controlPoints[i + 1], false, false, VisualizeOption.AuxiliaryColor)); if (i == 0) continue; processDot(new VisDot(_controlPoints[i], VisualizeOption.AuxiliaryColor)); } for (var i = 0; i < _nurbsPoints.Count - 1; ++i) { if (Direction == -1) { processLine(new VisLine(_nurbsPoints[i + 1], _nurbsPoints[i], false, i == (int)(_nurbsPoints.Count / 2), VisualizeOption.MainColor, 2)); } else { processLine(new VisLine(_nurbsPoints[i], _nurbsPoints[i + 1], false, i == (int)(_nurbsPoints.Count / 2), VisualizeOption.MainColor, 2)); } } } public (Vector2 Point, int Id) QueryPoint(Vector2 point) { var p = new Vector2(); var id = -1; var bestDistance = float.MaxValue; var hashes = _bias.Select(bb => CalculateHash(point, 100, bb.X, bb.Y)).ToList(); void TryQuery(Dictionary> dict, List hashList) { foreach (var hash in hashList) { if (!dict.TryGetValue(hash, out var ll)) continue; foreach (var (q, qId) in ll) { var d = Vector2.Distance(q, point); if (d < bestDistance) { p = q; id = qId; bestDistance = d; } } } } if (id == -1) { // todo: improve the way to find closest point if mappings fail (p, id) = _nurbsPoints.Select((p, i) => (p, i)) .OrderBy(pair => CommonMath.dist(pair.p.X, pair.p.Y, point.X, point.Y)).First(); } return (p, id); } private uint CalculateHash(Vector2 point, float gridSize, int xBias = 0, int yBias = 0) { return (uint)(((int)((point.X - _minX) / gridSize) + xBias) << 16 + (((int)((point.Y - _minY) / gridSize) + yBias) & 0xffff)); } private readonly List<(int X, int Y)> _bias = new() { new(-1, -1), new(-1, 0), new(-1, 1), new(0, -1), new(0, 0), new(0, 1), new(1, -1), new(1, 0), new(1, 1), }; public override (Vector2 Pt, float Angle, float Bias, float Position) QueryTangentPoint(Vector2 point) { var (p, id) = QueryPoint(point); var tangent = _tangents[id]; var (bias, lp, fd) = CommonMath.Project2DLine(point, p, tangent); var next = fd > 0 ? id + 1 : id - 1; if (next > 0 && next < _tangents.Count)//线性插值 { var (_, _, t) = CommonMath.Project2DLine(point, _nurbsPoints[id], _nurbsPoints[next]); var partial = t / Vector2.Distance(_nurbsPoints[id], _nurbsPoints[next]); if (partial >= 0 && partial <= 1) { tangent = CommonMath.RoundTh(_tangents[id] + partial * CommonMath.RoundTh(_tangents[next] - _tangents[id])); if (CommonMath.RoundTh(_tangents[next] - _tangents[id]) > 5) Console.WriteLine($"Nurbs tangents bug, tanget: {id}:{_tangents[id]} {next}:{_tangents[next]}"); } else Console.WriteLine("Nurbs tangents bug"); } return (lp, tangent, bias, fd + _sumDistances[id]); } public override float QueryCurvature(float position) { int id = _sumDistances.Count - 1; if (position <= 0) id = 0; else { for (int i = 1; i < _sumDistances.Count; i++) { if (position > _sumDistances[i - 1] && position <= _sumDistances[i]) { id = i; break; } } } var result = _curvatures[id]; if (id > 0 && id < _sumDistances.Count - 1)//插值 { var partial = (position - _sumDistances[id - 1]) / (_sumDistances[id] - _sumDistances[id - 1]); if (partial >= 0 && partial <= 1) result = (1 - partial) * _curvatures[id - 1] + partial * _curvatures[id]; else Console.WriteLine("Nurbs curvature bug"); } return result; } public Vector3 QueryNurbsPointsById(int id) { if (id < 0 || id > Frame) { Console.WriteLine($"QueryBezierPointsById out of range, Resolution:{Frame},id:{id}."); return new Vector3(0, 0, 0); } return new Vector3(_nurbsPoints[id].X, _nurbsPoints[id].Y, _tangents[id]); } public override float Length() { return _length; } public int Order => _order; public List ControlPoints => _controlPoints; public List Weights => _weights; public List KnotVector => _knotVector; public int Frame => _frame; public int Direction = 1; public void UpdateControlPoint(int id, Vector2 point) { _controlPoints[id] = point; InitializeNurbs(); } public void UpdateNurbsWeihgts(int id, float weight) { _weights[id] = weight; InitializeNurbs(); } public void AddControlPoint(int id, Vector2 point) { _controlPoints.Insert(id, point); InitializeNurbs(); } public void RemoveControlPoint(int id) { _controlPoints.RemoveAt(id); InitializeNurbs(); } public Vector2 GetMidPoint() { return _nurbsPoints[(int)Math.Ceiling(_frame / 2d)]; } private void InitializeNurbs() { _order = _controlPoints.Count - 1; List> allpoints = new List>(); List nurbsCurvePoints = new List(); float delta = 1.0f / Frame; for (float t = 0; t <= 1; t += delta) { var (point, tangent) = DeBoorAlgorithm(t); var points = new List { point, point + tangent // Tangent endpoint }; allpoints.Add(points); nurbsCurvePoints.Add(point); // Store the curve point separately } _nurbsPoints = nurbsCurvePoints; _tangents = Enumerable.Repeat(0f, _nurbsPoints.Count).ToList(); _curvatures = Enumerable.Repeat(0f, _nurbsPoints.Count).ToList(); for (var id = 0; id < _nurbsPoints.Count - 1; ++id) { Vector2 p1 = _nurbsPoints[id]; Vector2 p2 = _nurbsPoints[id + 1]; float tangentAngle = (float)Math.Atan2(p2.Y - p1.Y, p2.X - p1.X) * 180 / (float)Math.PI; _tangents[id] = tangentAngle; // Calculate curvature using finite differences of tangent (second derivative approximation) if (id > 0) { float previousTangent = _tangents[id - 1]; float curvature = (float)(CommonMath.ThDiff(tangentAngle, previousTangent) * Math.PI / 180) / (Vector2.Distance(p1, p2) / 1000); _curvatures[id] = curvature; } } _curvatures.Insert(0, _curvatures[0]); for (var id = 1; id < _curvatures.Count - 1; ++id) { _curvatures[id] = (_curvatures[id] + _curvatures[id + 1]) / 2; } _remainDistances = Enumerable.Repeat(0f, _nurbsPoints.Count).ToList(); _sumDistances = Enumerable.Repeat(0f, _nurbsPoints.Count).ToList(); _remainDistances[_nurbsPoints.Count - 1] = 0; for (var i = _nurbsPoints.Count - 2; i >= 0; --i) { _remainDistances[i] = _remainDistances[i + 1] + Vector2.Distance(_nurbsPoints[i], _nurbsPoints[i + 1]); } _sumDistances[0] = 0; for (var i = 1; i < _nurbsPoints.Count; ++i) { _sumDistances[i] = _sumDistances[i - 1] + Vector2.Distance(_nurbsPoints[i], _nurbsPoints[i - 1]); } _length = _sumDistances.Last(); _minX = _nurbsPoints.Min(pp => pp.X); _minY = _nurbsPoints.Min(pp => pp.Y); var tmpList = _nurbsPoints.Select((point, index) => (point, index)).ToList(); // GenerateGridMapping(ref _pointsMappingSmall, 100, tmpList); // GenerateGridMapping(ref _pointsMappingBig, 1000, tmpList); } private float CalculateLength() { return _nurbsPoints.Zip(_nurbsPoints.Skip(1), Vector2.Distance).Sum(); } private (Vector2, Vector2) DeBoorAlgorithm(float t) { Vector2 numerator = Vector2.Zero; Vector2 tangentNumerator = Vector2.Zero; float denominator = 0f; // Calculate the point on the curve for (int i = 0; i < ControlPoints.Count; ++i) { float basis = BasisFunction(i, _order, t) * Weights[i]; numerator += basis * ControlPoints[i]; denominator += basis; } Vector2 point = numerator / denominator; // Calculate the tangent vector using the analytical derivative for (int i = 0; i < ControlPoints.Count; ++i) { float basisDerivative = BasisFunctionDerivative(i, _order, t) * Weights[i]; tangentNumerator += basisDerivative * ControlPoints[i]; } Vector2 tangent = tangentNumerator / denominator; return (point, tangent); } private float BasisFunction(int i, int p, float t) { if (p == 0) return (KnotVector[i] <= t && t < KnotVector[i + 1]) ? 1.0f : 0.0f; float denom1 = KnotVector[i + p] - KnotVector[i]; float term1 = denom1 == 0 ? 0 : ((t - KnotVector[i]) / denom1) * BasisFunction(i, p - 1, t); float denom2 = KnotVector[i + p + 1] - KnotVector[i + 1]; float term2 = denom2 == 0 ? 0 : ((KnotVector[i + p + 1] - t) / denom2) * BasisFunction(i + 1, p - 1, t); return term1 + term2; } private float BasisFunctionDerivative(int i, int k, float t) { if (k == 0) return 0; float denom1 = KnotVector[i + k] - KnotVector[i]; float denom2 = KnotVector[i + k + 1] - KnotVector[i + 1]; float term1 = denom1 != 0 ? BasisFunction(i, k - 1, t) / denom1 : 0; float term2 = denom1 != 0 ? (t - KnotVector[i]) * BasisFunctionDerivative(i, k - 1, t) / denom1 : 0; float term3 = denom2 != 0 ? -BasisFunction(i + 1, k - 1, t) / denom2 : 0; float term4 = denom2 != 0 ? (KnotVector[i + k + 1] - t) * BasisFunctionDerivative(i + 1, k - 1, t) / denom2 : 0; return term1 + term2 + term3 + term4; } private int _order; private List _controlPoints; private List _weights; private List _knotVector; private int _frame; private List _nurbsPoints; private List>> _tangentPoints; private List _curvatures; private List _sumDistances; private List _remainDistances; private float _minX, _minY; private float _length; private Dictionary> _pointsMappingSmall; private Dictionary> _pointsMappingBig; private List _tangents; } }