Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

393 рядки
9.5KB

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