25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

122 satır
2.9KB

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