|
- package path
-
- import (
- "fmt"
- "git.wtrh.nl/patterns/gopatterns/pkg/pattern/point"
- "github.com/tdewolff/canvas"
- splines "gitlab.com/Achilleshiel/gosplines"
- "log/slog"
- )
-
- const resolution = 40
-
- // Spline defines a smooth curved path through points.
- type Spline struct {
- *Path
- start, end point.Point
- }
-
- type SplineOpts struct {
- Start, End point.Point
- Points []point.Point
- Style Style
- }
-
- // NewSpline returns a new spline through points. Start and end points can be provided as
- // the start and stop direction of the spline. When start or end point arguments are left nil there
- // are no constraints on the direction.
- func NewSpline(opts SplineOpts) Spline {
- s := Spline{
- Path: NewPath(opts.Points, opts.Style),
- start: opts.Start,
- end: opts.End,
- }
-
- if opts.Start == nil && len(opts.Points) > 1 {
- s.start = opts.Points[0]
- }
-
- if opts.End == nil && len(opts.Points) > 1 {
- s.end = opts.Points[len(opts.Points)-1]
- }
-
- return s
- }
-
- // Draw the spline to the provided [canvas.Canvas].
- func (s Spline) Draw(c *canvas.Canvas) error {
- points, err := s.build()
- if err != nil {
- return fmt.Errorf("generating spline: %w", err)
- }
-
- path := NewPath(points, s.style)
-
- if err = path.Draw(c); err != nil {
- return fmt.Errorf("draw spline points to canvas: %w", err)
- }
-
- length, _ := s.Length()
- slog.Debug("Draw spline", "length", length, "from", points[0].Name(), "to", points[len(points)-1].Name())
-
- return nil
- }
-
- func (s Spline) Length() (float64, error) {
- points, err := s.build()
- if err != nil {
- return 0.0, fmt.Errorf("generating spline: %w", err)
- }
-
- length := 0.0
-
- for i := range points[1:] {
- length += points[i].Position().Distance(points[i+1].Position())
- }
-
- return length, nil
- }
-
- func (s Spline) build() (points []point.Point, err error) {
- if len(s.points) < 2 {
- return s.points, nil
- }
-
- x := make([]float64, len(s.points))
- y := make([]float64, len(s.points))
-
- for i, p := range s.points {
- x[i], y[i] = p.Vector().Values()
- }
-
- diffStart := s.start.Vector().Subtract(s.points[0].Vector())
- diffEnd := s.points[len(s.points)-1].Vector().Subtract(s.end.Vector())
-
- xCoefficient, err := splines.SolveSplineWithConstraint(x, diffStart.X, diffEnd.X)
- if err != nil {
- return nil, fmt.Errorf("unable to calculate coefficients for x: %w", err)
- }
-
- yCoefficient, err := splines.SolveSplineWithConstraint(y, diffStart.Y, diffEnd.Y)
- if err != nil {
- return nil, fmt.Errorf("unable to calculate coefficients for y: %w", err)
- }
-
- points = make([]point.Point, 0, len(x)*resolution)
- stepSize := 1.0 / float64(resolution-1)
-
- for i := range len(s.points) - 1 {
- points = append(points, s.points[i])
-
- for t := stepSize; t < 1.0; t += stepSize {
- xCalculated := xCoefficient[i].Calculate(t)
- yCalculated := yCoefficient[i].Calculate(t)
- points = append(points, point.NewAbsolutePoint(xCalculated, yCalculated, 0, "0"))
- }
- }
-
- points = append(points, s.points[len(s.points)-1])
-
- return points, nil
- }
|