|
- package template
-
- import (
- "errors"
- "fmt"
- "gopkg.in/Knetic/govaluate.v3"
- "math"
- "strconv"
-
- "git.wtrh.nl/wouter/gopatterns/pkg/pattern"
- "git.wtrh.nl/wouter/gopatterns/pkg/pattern/point"
- )
-
- const maxRecursionDepth = 100
-
- var (
- // ErrPointNotFound is returned when a required point is not defined.
- ErrPointNotFound = errors.New("required point not found")
-
- // ErrRelativePointRecursion is returned when a points are relative to itself.
- ErrRelativePointRecursion = errors.New("point cannot be relative to itself")
-
- ErrInvalidArguments = errors.New("invalid arguments to call function")
- )
-
- // Points contains a map with points.
- type Points map[point.ID]Point
-
- // Point contains the template information for a point.
- type Point struct {
- Position Position `yaml:"position"`
- RelativeTo *point.ID `yaml:"relativeTo,omitempty"`
- Description string `yaml:"description"`
- Between *BetweenPoint `yaml:"between"`
- Extend *ExtendPoint `yaml:"extend"`
- Hide bool `yaml:"hide"`
- Polar *PolarPoint `yaml:"polar"`
- }
-
- var ErrInvalidPointID = errors.New("type cannot be converted to a PointID")
-
- func (p Points) Functions(pat *pattern.Pattern) map[string]govaluate.ExpressionFunction {
- return map[string]govaluate.ExpressionFunction{
- "DistanceBetween": func(args ...interface{}) (interface{}, error) {
- if len(args) != 2 {
- return nil, fmt.Errorf("function DistanceBetween() requires 2 arguments: %w",
- ErrInvalidArguments)
- }
-
- points, err := p.getOrCreateFromArgs(pat, args...)
- if err != nil {
- return nil, err
- }
- return points[0].Position().Distance(points[1].Position()), nil
- },
- "AngleBetween": func(args ...interface{}) (interface{}, error) {
- if len(args) != 2 {
- return nil, fmt.Errorf("function AngleBetween() requires 2 arguments: %w",
- ErrInvalidArguments)
- }
-
- points, err := p.getOrCreateFromArgs(pat, args...)
- if err != nil {
- return nil, err
- }
-
- return points[0].Vector().AngleBetween(points[1].Vector()), nil
- },
- }
- }
-
- func (p Points) getOrCreateFromArgs(pat *pattern.Pattern, args ...interface{}) ([]point.Point, error) {
- points := make([]point.Point, 0, len(args))
- for i, arg := range args {
- id, err := toPointID(arg)
- if err != nil {
- return nil, fmt.Errorf("parsing args[%d] to pointID: %w", i, err)
- }
-
- newPoint, err := p.getOrCreate(id, pat, 0)
- if err != nil {
- return nil, fmt.Errorf("get or create point %q: %w", id, err)
- }
-
- points = append(points, newPoint)
- }
-
- return points, nil
- }
-
- func toPointID(arg interface{}) (point.ID, error) {
- v1, ok := arg.(string)
- if !ok {
- f, ok := arg.(float64)
- if !ok {
- return "", fmt.Errorf("parsing %v as PointID: %w", arg, ErrInvalidPointID)
- }
- v1 = strconv.FormatFloat(f, 'f', -1, 64)
- }
- id1 := point.ID(v1)
- return id1, nil
- }
-
- // BetweenPoint contains the template information for a point in between two other points.
- type BetweenPoint struct {
- From point.ID `yaml:"from"`
- To point.ID `yaml:"to"`
- Offset *Value `yaml:"offset"`
- }
-
- func (p Points) evaluationFunctions() map[string]govaluate.ExpressionFunction {
- return map[string]govaluate.ExpressionFunction{
- "acos": func(args ...interface{}) (interface{}, error) {
- if len(args) != 1 {
- return nil, fmt.Errorf("function acos() requires 1 argument: %w",
- ErrInvalidArguments)
- }
-
- return math.Acos(args[0].(float64)), nil
- },
- "atan2": func(args ...interface{}) (interface{}, error) {
- if len(args) != 2 {
- return nil, fmt.Errorf("function atan2() requires 2 arguments: %w",
- ErrInvalidArguments)
- }
-
- return math.Atan2(args[0].(float64), args[1].(float64)), nil
- },
- }
- }
-
- // AddToPattern will add all points to the provided [pattern.Pattern].
- func (p Points) AddToPattern(pat *pattern.Pattern) error {
- for id := range p {
- if pat.GetPoint(id) == nil {
- err := p.addSingleToPattern(id, pat, 0)
- if err != nil {
- return err
- }
- }
- }
-
- return nil
- }
-
- func (p Points) addSingleToPattern(id point.ID, pat *pattern.Pattern, depth int) (err error) {
- templatePoint, ok := p[id]
- if !ok {
- return ErrPointNotFound
- }
-
- var newPoint point.Point
-
- switch {
- case templatePoint.RelativeTo != nil && templatePoint.Polar != nil:
- newPoint, err = p.createPolar(id, pat, depth)
- if err != nil {
- return err
- }
- case templatePoint.RelativeTo != nil:
- newPoint, err = p.createRelative(id, pat, depth)
- if err != nil {
- return err
- }
- case templatePoint.Between != nil:
- newPoint, err = p.createBetween(id, pat, depth)
- if err != nil {
- return err
- }
- case templatePoint.Extend != nil:
- newPoint, err = p.createExtend(id, pat, depth)
- if err != nil {
- return err
- }
- default:
- x, y, r, err := templatePoint.Position.evaluate(pat.Parameters(), p.Functions(pat))
- if err != nil {
- return err
- }
-
- newPoint = point.NewAbsolutePoint(x, y, r, id)
- }
-
- if templatePoint.Hide {
- newPoint.SetHide()
- }
-
- pat.AddPoint(newPoint)
-
- return nil
- }
-
- func (p Points) createRelative(
- id point.ID,
- pat *pattern.Pattern,
- depth int,
- ) (*point.RelativePoint, error) {
- templatePoint, ok := p[id]
- if !ok {
- return nil, ErrPointNotFound
- }
-
- relativePointID := *templatePoint.RelativeTo
- if relativePointID == id || depth > maxRecursionDepth {
- return nil, ErrRelativePointRecursion
- }
-
- relativePoint, err := p.getOrCreate(relativePointID, pat, depth)
- if err != nil {
- return nil, err
- }
-
- x, y, r, err := templatePoint.Position.evaluate(pat.Parameters(), p.Functions(pat))
- if err != nil {
- return nil, err
- }
-
- return point.NewRelativePoint(relativePoint).
- WithXOffset(x).WithYOffset(y).WithRotationOffset(r).
- MarkWith(id), nil
- }
-
- //nolint:ireturn
- func (p Points) createBetween(id point.ID, pat *pattern.Pattern, depth int) (point.Point, error) {
- newPoint, ok := p[id]
- if !ok {
- return nil, ErrPointNotFound
- }
-
- if newPoint.Between.To == id || newPoint.Between.From == id || depth > maxRecursionDepth {
- return nil, ErrRelativePointRecursion
- }
-
- fromPoint, err := p.getOrCreate(newPoint.Between.From, pat, depth)
- if err != nil {
- return nil, err
- }
-
- toPoint, err := p.getOrCreate(newPoint.Between.To, pat, depth)
- if err != nil {
- return nil, err
- }
-
- params := pat.Parameters()
-
- offset, err := newPoint.Between.Offset.Evaluate(params, p.Functions(pat))
- if err != nil {
- return nil, err
- }
-
- return point.NewBetweenPoint(fromPoint, toPoint, offset, id), nil
- }
-
- //nolint:ireturn
- func (p Points) getOrCreate(id point.ID, pat *pattern.Pattern, depth int) (point.Point, error) {
- if pat.GetPoint(id) == nil {
- err := p.addSingleToPattern(id, pat, depth+1)
- if err != nil {
- return nil, err
- }
- }
-
- createdPoint := pat.GetPoint(id)
- if createdPoint == nil {
- panic("getPoint cannot be nil")
- }
-
- return createdPoint, nil
- }
-
- func (p Points) createExtend(id point.ID, pat *pattern.Pattern, depth int) (point.Point, error) {
- newPoint, ok := p[id]
- if !ok {
- return nil, ErrPointNotFound
- }
-
- if newPoint.Extend.To == id || newPoint.Extend.From == id || depth > maxRecursionDepth {
- return nil, ErrRelativePointRecursion
- }
-
- fromPoint, err := p.getOrCreate(newPoint.Extend.From, pat, depth)
- if err != nil {
- return nil, err
- }
-
- toPoint, err := p.getOrCreate(newPoint.Extend.To, pat, depth)
- if err != nil {
- return nil, err
- }
-
- params := pat.Parameters()
-
- offset, err := newPoint.Extend.Offset.Evaluate(params, p.Functions(pat))
- if err != nil {
- return nil, err
- }
-
- return point.NewExtendPoint(fromPoint, toPoint, offset, id), nil
- }
-
- func (p Points) createPolar(id point.ID, pat *pattern.Pattern, depth int) (point.Point, error) {
- templatePoint, ok := p[id]
- if !ok {
- return nil, ErrPointNotFound
- }
-
- relativePointID := *templatePoint.RelativeTo
- if relativePointID == id || depth > maxRecursionDepth {
- return nil, ErrRelativePointRecursion
- }
-
- relativePoint, err := p.getOrCreate(relativePointID, pat, depth)
- if err != nil {
- return nil, err
- }
-
- x, y, err := templatePoint.Polar.evaluate(pat.Parameters(), p.Functions(pat))
- if err != nil {
- return nil, err
- }
-
- return point.NewRelativePoint(relativePoint).
- WithXOffset(x).WithYOffset(y).MarkWith(id), nil
- }
-
- type ExtendPoint struct {
- From point.ID `yaml:"from"`
- To point.ID `yaml:"to"`
- Offset *Value `yaml:"offset"`
- }
-
- type PolarPoint struct {
- Length *Value `yaml:"length"`
- Rotation *Value `yaml:"rotation"`
- }
-
- func (p PolarPoint) evaluate(params govaluate.MapParameters, funcs map[string]govaluate.ExpressionFunction) (x, y float64, err error) {
- rotation, err := p.Rotation.Evaluate(params, funcs)
- if err != nil {
- return 0, 0, err
- }
-
- length, err := p.Length.Evaluate(params, funcs)
- if err != nil {
- return 0, 0, err
- }
-
- return math.Sin(rotation) * -length, math.Cos(rotation) * -length, nil
- }
|