Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

424 lines
10KB

  1. package template
  2. import (
  3. "errors"
  4. "fmt"
  5. "maps"
  6. "math"
  7. "strconv"
  8. "git.wtrh.nl/patterns/gopatterns/pkg/pattern"
  9. "git.wtrh.nl/patterns/gopatterns/pkg/pattern/point"
  10. "gopkg.in/Knetic/govaluate.v3"
  11. )
  12. const maxRecursionDepth = 100
  13. var (
  14. // ErrPointNotFound is returned when a required point is not defined.
  15. ErrPointNotFound = errors.New("required point not found")
  16. // ErrRelativePointRecursion is returned when a points are relative to itself.
  17. ErrRelativePointRecursion = errors.New("point cannot be relative to itself")
  18. ErrInvalidArguments = errors.New("invalid arguments to call function")
  19. )
  20. // Points contains a map with points.
  21. type Points map[point.ID]Point
  22. // Point contains the template information for a point.
  23. type Point struct {
  24. Position Position `yaml:"position"`
  25. RelativeTo *point.ID `yaml:"relativeTo,omitempty"`
  26. Description string `yaml:"description"`
  27. Between *BetweenPoint `yaml:"between"`
  28. Extend *ExtendPoint `yaml:"extend"`
  29. Hide bool `yaml:"hide"`
  30. Polar *PolarPoint `yaml:"polar"`
  31. }
  32. var ErrInvalidPointID = errors.New("type cannot be converted to a PointID")
  33. func (p Points) Functions(pat *pattern.Pattern) map[string]govaluate.ExpressionFunction {
  34. functions := p.evaluationFunctions()
  35. maps.Copy(functions, map[string]govaluate.ExpressionFunction{
  36. "DistanceBetween": func(args ...interface{}) (interface{}, error) {
  37. if len(args) != 2 {
  38. return nil, fmt.Errorf("function DistanceBetween() requires 2 arguments: %w",
  39. ErrInvalidArguments)
  40. }
  41. points, err := p.getOrCreateFromArgs(pat, args...)
  42. if err != nil {
  43. return nil, err
  44. }
  45. return points[0].Position().Distance(points[1].Position()), nil
  46. },
  47. "AngleBetween": func(args ...interface{}) (interface{}, error) {
  48. if len(args) != 2 {
  49. return nil, fmt.Errorf("function AngleBetween() requires 2 arguments: %w",
  50. ErrInvalidArguments)
  51. }
  52. points, err := p.getOrCreateFromArgs(pat, args...)
  53. if err != nil {
  54. return nil, err
  55. }
  56. return points[0].Vector().AngleBetween(points[1].Vector()), nil
  57. },
  58. "YDistanceBetween": func(args ...interface{}) (interface{}, error) {
  59. if len(args) != 2 {
  60. return nil, fmt.Errorf("function DistanceBetween() requires 2 arguments: %w",
  61. ErrInvalidArguments)
  62. }
  63. points, err := p.getOrCreateFromArgs(pat, args...)
  64. if err != nil {
  65. return nil, err
  66. }
  67. return points[0].Vector().Y - points[1].Vector().Y, nil
  68. },
  69. "XDistanceBetween": func(args ...interface{}) (interface{}, error) {
  70. if len(args) != 2 {
  71. return nil, fmt.Errorf("function DistanceBetween() requires 2 arguments: %w",
  72. ErrInvalidArguments)
  73. }
  74. points, err := p.getOrCreateFromArgs(pat, args...)
  75. if err != nil {
  76. return nil, err
  77. }
  78. return points[0].Vector().X - points[1].Vector().X, nil
  79. },
  80. })
  81. return functions
  82. }
  83. func (p Points) getOrCreateFromArgs(pat *pattern.Pattern, args ...interface{}) ([]point.Point, error) {
  84. points := make([]point.Point, 0, len(args))
  85. for i, arg := range args {
  86. id, err := toPointID(arg)
  87. if err != nil {
  88. return nil, fmt.Errorf("parsing args[%d] to pointID: %w", i, err)
  89. }
  90. newPoint, err := p.getOrCreate(id, pat, 0)
  91. if err != nil {
  92. return nil, fmt.Errorf("get or create point %q: %w", id, err)
  93. }
  94. points = append(points, newPoint)
  95. }
  96. return points, nil
  97. }
  98. func toPointID(arg interface{}) (point.ID, error) {
  99. v1, ok := arg.(string)
  100. if !ok {
  101. f, ok := arg.(float64)
  102. if !ok {
  103. return "", fmt.Errorf("parsing %v as PointID: %w", arg, ErrInvalidPointID)
  104. }
  105. v1 = strconv.FormatFloat(f, 'f', -1, 64)
  106. }
  107. id1 := point.ID(v1)
  108. return id1, nil
  109. }
  110. // BetweenPoint contains the template information for a point in between two other points.
  111. type BetweenPoint struct {
  112. From point.ID `yaml:"from"`
  113. To point.ID `yaml:"to"`
  114. Offset *Value `yaml:"offset"`
  115. }
  116. func (p Points) evaluationFunctions() map[string]govaluate.ExpressionFunction {
  117. return map[string]govaluate.ExpressionFunction{
  118. "acos": func(args ...interface{}) (interface{}, error) {
  119. if len(args) != 1 {
  120. return nil, fmt.Errorf("function acos() requires 1 argument: %w",
  121. ErrInvalidArguments)
  122. }
  123. x, ok := args[0].(float64)
  124. if !ok {
  125. return nil, fmt.Errorf("evaluate acos(): parsing %q as float64: %w", args[0],
  126. ErrInvalidArguments)
  127. }
  128. return math.Acos(x), nil
  129. },
  130. "asin": func(args ...interface{}) (interface{}, error) {
  131. if len(args) != 1 {
  132. return nil, fmt.Errorf("function asin() requires 1 argument: %w",
  133. ErrInvalidArguments)
  134. }
  135. x, ok := args[0].(float64)
  136. if !ok {
  137. return nil, fmt.Errorf("evaluate asin(): parsing %q as float64: %w", args[0],
  138. ErrInvalidArguments)
  139. }
  140. return math.Asin(x), nil
  141. },
  142. "atan2": func(args ...interface{}) (interface{}, error) {
  143. if len(args) != 2 {
  144. return nil, fmt.Errorf("function atan2() requires 2 arguments: %w",
  145. ErrInvalidArguments)
  146. }
  147. x, ok := args[0].(float64)
  148. if !ok {
  149. return nil, fmt.Errorf("evaluate atan2(): parsing %q as float64: %w", args[0],
  150. ErrInvalidArguments)
  151. }
  152. y, ok := args[1].(float64)
  153. if !ok {
  154. return nil, fmt.Errorf("evaluate atan2(): parsing %q as float64: %w", args[0],
  155. ErrInvalidArguments)
  156. }
  157. return math.Atan2(x, y), nil
  158. },
  159. }
  160. }
  161. // AddToPattern will add all points to the provided [pattern.Pattern].
  162. func (p Points) AddToPattern(pat *pattern.Pattern) error {
  163. for id := range p {
  164. if pat.GetPoint(id) == nil {
  165. err := p.addSingleToPattern(id, pat, 0)
  166. if err != nil {
  167. return err
  168. }
  169. }
  170. }
  171. return nil
  172. }
  173. func (p Points) addSingleToPattern(id point.ID, pat *pattern.Pattern, depth int) (err error) {
  174. templatePoint, ok := p[id]
  175. if !ok {
  176. return ErrPointNotFound
  177. }
  178. var newPoint point.Point
  179. switch {
  180. case templatePoint.RelativeTo != nil && templatePoint.Polar != nil:
  181. newPoint, err = p.createPolar(id, pat, depth)
  182. if err != nil {
  183. return err
  184. }
  185. case templatePoint.RelativeTo != nil:
  186. newPoint, err = p.createRelative(id, pat, depth)
  187. if err != nil {
  188. return err
  189. }
  190. case templatePoint.Between != nil:
  191. newPoint, err = p.createBetween(id, pat, depth)
  192. if err != nil {
  193. return err
  194. }
  195. case templatePoint.Extend != nil:
  196. newPoint, err = p.createExtend(id, pat, depth)
  197. if err != nil {
  198. return err
  199. }
  200. default:
  201. x, y, r, err := templatePoint.Position.evaluate(pat.Parameters(), p.Functions(pat))
  202. if err != nil {
  203. return err
  204. }
  205. newPoint = point.NewAbsolutePoint(x, y, r, id)
  206. }
  207. if templatePoint.Hide {
  208. newPoint.SetHide()
  209. }
  210. pat.AddPoint(newPoint)
  211. return nil
  212. }
  213. func (p Points) createRelative(
  214. id point.ID,
  215. pat *pattern.Pattern,
  216. depth int,
  217. ) (*point.RelativePoint, error) {
  218. templatePoint, ok := p[id]
  219. if !ok {
  220. return nil, ErrPointNotFound
  221. }
  222. relativePointID := *templatePoint.RelativeTo
  223. if relativePointID == id || depth > maxRecursionDepth {
  224. return nil, ErrRelativePointRecursion
  225. }
  226. relativePoint, err := p.getOrCreate(relativePointID, pat, depth)
  227. if err != nil {
  228. return nil, err
  229. }
  230. x, y, r, err := templatePoint.Position.evaluate(pat.Parameters(), p.Functions(pat))
  231. if err != nil {
  232. return nil, err
  233. }
  234. return point.NewRelativePoint(relativePoint).
  235. WithXOffset(x).WithYOffset(y).WithRotationOffset(r).
  236. MarkWith(id), nil
  237. }
  238. //nolint:ireturn,dupl
  239. func (p Points) createBetween(id point.ID, pat *pattern.Pattern, depth int) (point.Point, error) {
  240. newPoint, ok := p[id]
  241. if !ok {
  242. return nil, ErrPointNotFound
  243. }
  244. if newPoint.Between.To == id || newPoint.Between.From == id || depth > maxRecursionDepth {
  245. return nil, ErrRelativePointRecursion
  246. }
  247. fromPoint, err := p.getOrCreate(newPoint.Between.From, pat, depth)
  248. if err != nil {
  249. return nil, err
  250. }
  251. toPoint, err := p.getOrCreate(newPoint.Between.To, pat, depth)
  252. if err != nil {
  253. return nil, err
  254. }
  255. params := pat.Parameters()
  256. offset, err := newPoint.Between.Offset.Evaluate(params, p.Functions(pat))
  257. if err != nil {
  258. return nil, err
  259. }
  260. return point.NewBetweenPoint(fromPoint, toPoint, offset, id), nil
  261. }
  262. //nolint:ireturn
  263. func (p Points) getOrCreate(id point.ID, pat *pattern.Pattern, depth int) (point.Point, error) {
  264. if pat.GetPoint(id) == nil {
  265. err := p.addSingleToPattern(id, pat, depth+1)
  266. if err != nil {
  267. return nil, err
  268. }
  269. }
  270. createdPoint := pat.GetPoint(id)
  271. if createdPoint == nil {
  272. panic("getPoint cannot be nil")
  273. }
  274. return createdPoint, nil
  275. }
  276. //nolint:ireturn,dupl
  277. func (p Points) createExtend(id point.ID, pat *pattern.Pattern, depth int) (point.Point, error) {
  278. newPoint, ok := p[id]
  279. if !ok {
  280. return nil, ErrPointNotFound
  281. }
  282. if newPoint.Extend.To == id || newPoint.Extend.From == id || depth > maxRecursionDepth {
  283. return nil, ErrRelativePointRecursion
  284. }
  285. fromPoint, err := p.getOrCreate(newPoint.Extend.From, pat, depth)
  286. if err != nil {
  287. return nil, err
  288. }
  289. toPoint, err := p.getOrCreate(newPoint.Extend.To, pat, depth)
  290. if err != nil {
  291. return nil, err
  292. }
  293. params := pat.Parameters()
  294. offset, err := newPoint.Extend.Offset.Evaluate(params, p.Functions(pat))
  295. if err != nil {
  296. return nil, err
  297. }
  298. return point.NewExtendPoint(fromPoint, toPoint, offset, id), nil
  299. }
  300. //nolint:ireturn
  301. func (p Points) createPolar(id point.ID, pat *pattern.Pattern, depth int) (point.Point, error) {
  302. templatePoint, ok := p[id]
  303. if !ok {
  304. return nil, ErrPointNotFound
  305. }
  306. relativePointID := *templatePoint.RelativeTo
  307. if relativePointID == id || depth > maxRecursionDepth {
  308. return nil, ErrRelativePointRecursion
  309. }
  310. relativePoint, err := p.getOrCreate(relativePointID, pat, depth)
  311. if err != nil {
  312. return nil, err
  313. }
  314. x, y, err := templatePoint.Polar.evaluate(pat.Parameters(), p.Functions(pat))
  315. if err != nil {
  316. return nil, err
  317. }
  318. return point.NewRelativePoint(relativePoint).
  319. WithXOffset(x).WithYOffset(y).MarkWith(id), nil
  320. }
  321. // ExtendPoint describes how to draw a new point that extends in line with two points.
  322. type ExtendPoint struct {
  323. From point.ID `yaml:"from"`
  324. To point.ID `yaml:"to"`
  325. Offset *Value `yaml:"offset"`
  326. }
  327. // PolarPoint describes how to draw a new point with a direction and a distance from the current
  328. // position.
  329. type PolarPoint struct {
  330. Length *Value `yaml:"length"`
  331. Rotation *Value `yaml:"rotation"`
  332. }
  333. func (p PolarPoint) evaluate(
  334. params govaluate.MapParameters,
  335. funcs map[string]govaluate.ExpressionFunction,
  336. ) (x, y float64, err error) {
  337. rotation, err := p.Rotation.Evaluate(params, funcs)
  338. if err != nil {
  339. return 0, 0, err
  340. }
  341. length, err := p.Length.Evaluate(params, funcs)
  342. if err != nil {
  343. return 0, 0, err
  344. }
  345. return math.Sin(rotation) * -length, math.Cos(rotation) * -length, nil
  346. }