using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Numerics; using System.Reflection; using CommonUsage.Mathematics; namespace CommonUsage.Geometries { public class BezierCurve : AbstractGeometry { public BezierCurve(List controlPoints, int resolution = 100) { // Console.WriteLine($"BezierCurve1"); // Console.WriteLine(string.Join(" ",controlPoints.Select(p=>$"{p.X:f2},{p.Y:f2}"))); _controlPoints = controlPoints; _resolution = resolution; InitializeBezier(); } 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 < _bezierPoints.Count - 1; ++i) { if (Direction == -1) { processLine(new VisLine(_bezierPoints[i + 1], _bezierPoints[i], false, i == (int)(_bezierPoints.Count / 2), VisualizeOption.MainColor, 2)); } else { processLine(new VisLine(_bezierPoints[i], _bezierPoints[i + 1], false, i == (int)(_bezierPoints.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; } } } } //map目前有bug,取消cpu占用也不严重,必要时候在优化 // TryQuery(_pointsMappingSmall, hashes); // // if (id == -1) // { // hashes = _bias.Select(bb => CalculateHash(point, 1000, bb.X, bb.Y)).ToList(); // TryQuery(_pointsMappingBig, hashes); // } if (id == -1) { // todo: improve the way to find closest point if mappings fail (p, id) = _bezierPoints.Select((p, i) => (p, i)) .OrderBy(pair => CommonMath.dist(pair.p.X, pair.p.Y, point.X, point.Y)).First(); } return (p, id); } 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 (id == 0) next = 1; // Console.WriteLine($"id:{id} next:{next} tangent:{tangent} _tangents.Count:{_tangents.Count}"); if (next > 0 && next < _tangents.Count)//线性插值 { var (_, _, t) = CommonMath.Project2DLine(point, _bezierPoints[id], _bezierPoints[next]); var partial = t / Vector2.Distance(_bezierPoints[id], _bezierPoints[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($"bezier tangents bug, tanget: {id}:{_tangents[id]} {next}:{_tangents[next]}"); } // else Console.WriteLine("bezier tangents bug"); } return (lp, tangent, bias, fd + _sumDistances[id]); } public override float Length() { return _length; } 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("bezier curvature bug"); } return result; } public Vector3 QueryBezierPointsById(int id) { if (id < 0 || id > Resolution) { Console.WriteLine($"QueryBezierPointsById out of range, Resolution:{Resolution},id:{id}."); return new Vector3(0, 0, 0); } return new Vector3(_bezierPoints[id].X, _bezierPoints[id].Y, _tangents[id]); } public List ControlPoints => _controlPoints; public int Resolution => _resolution; /// /// 仅用于simple显示路径方向 /// public int Direction = 1; public int Order => _order; // public List Tangents => _tangents; public void UpdateControlPoint(int id, Vector2 point) { _controlPoints[id] = point; InitializeBezier(); } public void AddControlPoint(int id, Vector2 point) { _controlPoints.Insert(id, point); InitializeBezier(); } public void RemoveControlPoint(int id) { _controlPoints.RemoveAt(id); InitializeBezier(); } public Vector2 GetMidPoint() { return _bezierPoints[(int)Math.Ceiling(_resolution / 2d)]; } private void InitializeBezier() { _order = _controlPoints.Count - 1; // _bezierPoints = new List(); var delta = 1.0f / _resolution; // for (int t = 0; t <= _resolution; t += 1)//下面循环算了,没必要先递归算一遍 // _bezierPoints.Add(new Vector2(DeCasteljauX(_order, 0, t*delta), DeCasteljauY(_order, 0, t*delta))); var allPoints = new List>>(); for (var i = 0; i < _order; i++) { var size = allPoints.Count; var morePoints = new List>(); for (var j = 0; j < _order - i; j++) { var points = new List(); for (int t = 0; t <= _resolution; t += 1) { float p0x; float p1x; float p0y; float p1y; var z = t; if (size > 0) { p0x = allPoints[i - 1][j][z].X; p1x = allPoints[i - 1][j + 1][z].X; p0y = allPoints[i - 1][j][z].Y; p1y = allPoints[i - 1][j + 1][z].Y; } else { p0x = _controlPoints[j].X; p1x = _controlPoints[j + 1].X; p0y = _controlPoints[j].Y; p1y = _controlPoints[j + 1].Y; } var part = t * delta; points.Add(new Vector2((1 - part) * p0x + part * p1x, (1 - part) * p0y + part * p1y)); } morePoints.Add(points); } allPoints.Add(morePoints); } _bezierPoints = allPoints.Last().Last(); _tangentInfo = allPoints; _tangents = Enumerable.Repeat(0f, _bezierPoints.Count).ToList(); _curvatures = Enumerable.Repeat(0f, _bezierPoints.Count).ToList(); var p2 = allPoints[Order - 2]; for (var id = 0; id < _bezierPoints.Count; ++id) { _tangents[id] = (float)(Math.Atan2(p2[1][id].Y - p2[0][id].Y, p2[1][id].X - p2[0][id].X) / Math.PI * 180); if (id != 0) _curvatures[id] = (float)((CommonMath.ThDiff(_tangents[id], _tangents[id - 1]) / 180 * Math.PI) / (Vector2.Distance(_bezierPoints[id], _bezierPoints[id - 1]) / 1000)); } // Console.WriteLine($"{string.Join("\n", _tangents.Select((val, i) => $"{i}: {val}"))}"); _tangents[0] = _tangents[1]; // todo: here is temporary fix _curvatures[0] = _curvatures[1]; for (var id = 1; id < _bezierPoints.Count - 1; ++id)//前移0.5 _curvatures[id] = (_curvatures[id] + _curvatures[id + 1]) / 2; _remainDistances = Enumerable.Repeat(0f, _bezierPoints.Count).ToList(); _sumDistances = Enumerable.Repeat(0f, _bezierPoints.Count).ToList(); for (var i = _bezierPoints.Count - 2; i >= 0; --i) { _remainDistances[i] = _remainDistances[i + 1] + Vector2.Distance(_bezierPoints[i], _bezierPoints[i + 1]); } for (var i = 1; i < _bezierPoints.Count; ++i) { _sumDistances[i] = _sumDistances[i - 1] + Vector2.Distance(_bezierPoints[i], _bezierPoints[i - 1]); } _length = _sumDistances.Last(); _minX = _bezierPoints.Min(pp => pp.X); _minY = _bezierPoints.Min(pp => pp.Y); var tmpList = _bezierPoints.Select((point, index) => (point, index)).ToList(); return; void GenerateGridMapping(ref Dictionary> dict, float gSize) { dict = new Dictionary>(); foreach (var (point, index) in tmpList) { var hash = CalculateHash(point, gSize); if (dict.TryGetValue(hash, out var ll)) ll.Add((point, index)); else dict[hash] = new List<(Vector2 Point, int Id)>() { (point, index) }; } } GenerateGridMapping(ref _pointsMappingSmall, 100); GenerateGridMapping(ref _pointsMappingBig, 1000); } 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), }; private float DeCasteljauX(int i, int j, float t) { if (i == 1) return (1 - t) * _controlPoints[j].X + t * _controlPoints[j + 1].X; return (1 - t) * DeCasteljauX(i - 1, j, t) + t * DeCasteljauX(i - 1, j + 1, t); } private float DeCasteljauY(int i, int j, float t) { if (i == 1) return (1 - t) * _controlPoints[j].Y + t * _controlPoints[j + 1].Y; return (1 - t) * DeCasteljauY(i - 1, j, t) + t * DeCasteljauY(i - 1, j + 1, t); } private int _order; private int _resolution; private List _controlPoints; private List _bezierPoints; private List>> _tangentInfo; private List _tangents; private List _remainDistances; private List _sumDistances; private List _curvatures; private Dictionary> _pointsMappingSmall; private Dictionary> _pointsMappingBig; private float _minX, _minY; private float _length; } }