/
docNemo
/
clothes-graph
Обзор
Документация
Войти
/
docNemo
/
clothes-graph
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
src/ClothesGraph.Geometry/Curves.cs
153 строки
5 KB
docNemo
Фундамент продукта: параметрическое построение выкроек, печать, установщик
08 авг 2026, 22:17
08 авг 2026, 22:17
cd47cce
Код
Авторство
О чём код?
using System; namespace ClothesGraph.Geometry; /// <summary> /// Линия чертежа, параметризованная от 0 до 1. /// </summary> public interface ICurve { Point2 Start { get; } Point2 End { get; } Point2 PointAt(double parameter); /// <summary>Длина линии.</summary> Length Length { get; } /// <summary>Точка на заданном расстоянии от начала вдоль линии.</summary> Point2 PointAtDistance(Length distance); /// <summary>Направление касательной в градусах против часовой стрелки.</summary> double DirectionAt(double parameter); } /// <summary>Прямолинейный отрезок.</summary> public sealed class LineCurve(Point2 start, Point2 end) : ICurve { public Point2 Start { get; } = start; public Point2 End { get; } = end; public Point2 PointAt(double parameter) { var offset = (End - Start) * Math.Clamp(parameter, 0, 1); return Start + offset; } public Length Length => Start.DistanceTo(End); public Point2 PointAtDistance(Length distance) { var total = Length; if (total.Units == 0) return Start; return PointAt(distance / total); } public double DirectionAt(double parameter) => (End - Start).Degrees; } /// <summary> /// Кубическая кривая Безье. /// </summary> /// <remarks> /// Длина считается разбиением на хорды: замкнутой формулы для длины кубической /// кривой не существует, а точности разбиения хватает с большим запасом — /// шаг разбиения даёт погрешность заметно ниже цены деления хранения. /// </remarks> public sealed class CubicCurve(Point2 start, Point2 control1, Point2 control2, Point2 end) : ICurve { private const int LengthSamples = 128; public Point2 Start { get; } = start; public Point2 Control1 { get; } = control1; public Point2 Control2 { get; } = control2; public Point2 End { get; } = end; public Point2 PointAt(double parameter) { var t = Math.Clamp(parameter, 0, 1); var u = 1 - t; var x = u * u * u * Start.X.Units + 3 * u * u * t * Control1.X.Units + 3 * u * t * t * Control2.X.Units + t * t * t * End.X.Units; var y = u * u * u * Start.Y.Units + 3 * u * u * t * Control1.Y.Units + 3 * u * t * t * Control2.Y.Units + t * t * t * End.Y.Units; return new Point2( Length.FromUnits((long)Math.Round(x, MidpointRounding.AwayFromZero)), Length.FromUnits((long)Math.Round(y, MidpointRounding.AwayFromZero))); } public Length Length { get { var total = 0.0; var previous = Start; for (var index = 1; index <= LengthSamples; index++) { var current = PointAt(index / (double)LengthSamples); total += (current - previous).Magnitude.Units; previous = current; } return Length.FromUnits((long)Math.Round(total, MidpointRounding.AwayFromZero)); } } public Point2 PointAtDistance(Length distance) { if (distance <= Length.Zero) return Start; var target = (double)distance.Units; var travelled = 0.0; var previous = Start; for (var index = 1; index <= LengthSamples; index++) { var parameter = index / (double)LengthSamples; var current = PointAt(parameter); var step = (current - previous).Magnitude.Units; if (travelled + step >= target) { var withinStep = step > 0 ? (target - travelled) / step : 0; var previousParameter = (index - 1) / (double)LengthSamples; return PointAt(previousParameter + (parameter - previousParameter) * withinStep); } travelled += step; previous = current; } return End; } public double DirectionAt(double parameter) { var t = Math.Clamp(parameter, 0, 1); var u = 1 - t; var dx = 3 * u * u * (Control1.X.Units - Start.X.Units) + 6 * u * t * (Control2.X.Units - Control1.X.Units) + 3 * t * t * (End.X.Units - Control2.X.Units); var dy = 3 * u * u * (Control1.Y.Units - Start.Y.Units) + 6 * u * t * (Control2.Y.Units - Control1.Y.Units) + 3 * t * t * (End.Y.Units - Control2.Y.Units); return Math.Atan2(dy, dx) * 180.0 / Math.PI; } }