您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

125 行
3.0KB

  1. package path
  2. import (
  3. "fmt"
  4. "log/slog"
  5. "git.wtrh.nl/patterns/gopatterns/pkg/point"
  6. "git.wtrh.nl/patterns/gopatterns/pkg/util"
  7. "github.com/tdewolff/canvas"
  8. splines "gitlab.com/Achilleshiel/gosplines"
  9. )
  10. const resolution = 40
  11. // Spline defines a smooth curved path through points.
  12. type Spline struct {
  13. *Polygon
  14. start, end point.Point
  15. }
  16. type SplineOpts struct {
  17. Start, End point.Point
  18. Points []point.Point
  19. Style Style
  20. ID util.ID
  21. }
  22. // NewSpline returns a new spline through points. Start and end points can be provided as
  23. // the start and stop direction of the spline. When start or end point arguments are left nil there
  24. // are no constraints on the direction.
  25. func NewSpline(opts SplineOpts) Spline {
  26. s := Spline{
  27. Polygon: NewPolygon(opts.Points, opts.Style, opts.ID),
  28. start: opts.Start,
  29. end: opts.End,
  30. }
  31. if opts.Start == nil && len(opts.Points) > 1 {
  32. s.start = opts.Points[0]
  33. }
  34. if opts.End == nil && len(opts.Points) > 1 {
  35. s.end = opts.Points[len(opts.Points)-1]
  36. }
  37. return s
  38. }
  39. // Draw the spline to the provided [canvas.Canvas].
  40. func (s Spline) Draw(c *canvas.Canvas) error {
  41. points, err := s.build()
  42. if err != nil {
  43. return fmt.Errorf("generating spline: %w", err)
  44. }
  45. path := NewPolygon(points, s.style, s.id)
  46. if err = path.Draw(c); err != nil {
  47. return fmt.Errorf("draw spline points to canvas: %w", err)
  48. }
  49. length, _ := s.Length()
  50. slog.Debug("Draw spline", "length", length, "from", points[0].Name(), "to", points[len(points)-1].Name())
  51. return nil
  52. }
  53. func (s Spline) Length() (float64, error) {
  54. points, err := s.build()
  55. if err != nil {
  56. return 0.0, fmt.Errorf("generating spline: %w", err)
  57. }
  58. length := 0.0
  59. for i := range points[1:] {
  60. length += points[i].Position().Distance(points[i+1].Position())
  61. }
  62. return length, nil
  63. }
  64. func (s Spline) build() (points []point.Point, err error) {
  65. if len(s.points) < 2 {
  66. return s.points, nil
  67. }
  68. x := make([]float64, len(s.points))
  69. y := make([]float64, len(s.points))
  70. for i, p := range s.points {
  71. x[i], y[i] = p.Vector().Values()
  72. }
  73. diffStart := s.start.Vector().Subtract(s.points[0].Vector())
  74. diffEnd := s.points[len(s.points)-1].Vector().Subtract(s.end.Vector())
  75. xCoefficient, err := splines.SolveSplineWithConstraint(x, diffStart.X, diffEnd.X)
  76. if err != nil {
  77. return nil, fmt.Errorf("unable to calculate coefficients for x: %w", err)
  78. }
  79. yCoefficient, err := splines.SolveSplineWithConstraint(y, diffStart.Y, diffEnd.Y)
  80. if err != nil {
  81. return nil, fmt.Errorf("unable to calculate coefficients for y: %w", err)
  82. }
  83. points = make([]point.Point, 0, len(x)*resolution)
  84. stepSize := 1.0 / float64(resolution-1)
  85. for i := range len(s.points) - 1 {
  86. points = append(points, s.points[i])
  87. for t := stepSize; t < 1.0; t += stepSize {
  88. xCalculated := xCoefficient[i].Calculate(t)
  89. yCalculated := yCoefficient[i].Calculate(t)
  90. points = append(points, point.NewAbsolutePoint(xCalculated, yCalculated, 0, "0"))
  91. }
  92. }
  93. points = append(points, s.points[len(s.points)-1])
  94. return points, nil
  95. }