modeling/src/curves/bezier/create.js

  1. /**
  2. * Represents a bezier easing function.
  3. * @typedef {Object} bezier
  4. * @property {Array} points - The control points for the bezier curve. The first and last point will also be the start and end of the curve
  5. * @property {string} pointType - A reference to the type and dimensionality of the points that the curve was created from
  6. * @property {number} dimensions - The dimensionality of the bezier
  7. * @property {Array} permutations - A pre-calculation of the bezier algorithm's co-efficients
  8. * @property {Array} tangentPermutations - A pre-calculation of the bezier algorithm's tangent co-efficients
  9. *
  10. */
  11. /**
  12. * Creates an object representing a bezier easing curve.
  13. * Curves can have both an arbitrary number of control points, and an arbitrary number of dimensions.
  14. *
  15. * @example
  16. * const b = bezier.create([0,10]) // a linear progression from 0 to 10
  17. * const b = bezier.create([0, 0, 10, 10]) // a symmetrical cubic easing curve that starts slowly and ends slowly from 0 to 10
  18. * const b = bezier.create([0,0,0], [0,5,10], [10,0,-5], [10,10,10]]) // a cubic 3 dimensional easing curve that can generate position arrays for modelling
  19. * // Usage
  20. * let position = bezier.valueAt(t,b) // where 0 < t < 1
  21. * let tangent = bezier.tangentAt(t,b) // where 0 < t < 1
  22. *
  23. * @param {Array} points An array with at least 2 elements of either all numbers, or all arrays of numbers that are the same size.
  24. * @returns {bezier} a new bezier data object
  25. * @alias module:modeling/curves/bezier.create
  26. */
  27. const create = (points) => {
  28. if (!Array.isArray(points)) throw new Error('Bezier points must be a valid array/')
  29. if (points.length < 2) throw new Error('Bezier points must contain at least 2 values.')
  30. const pointType = getPointType(points)
  31. return {
  32. points: points,
  33. pointType: pointType,
  34. dimensions: pointType === 'float_single' ? 0 : points[0].length,
  35. permutations: getPermutations(points.length - 1),
  36. tangentPermutations: getPermutations(points.length - 2)
  37. }
  38. }
  39. const getPointType = function (points) {
  40. let firstPointType = null
  41. points.forEach((point) => {
  42. let pType = ''
  43. if (Number.isFinite(point)) {
  44. pType = 'float_single'
  45. } else if (Array.isArray(point)) {
  46. point.forEach((val) => {
  47. if (!Number.isFinite(val)) throw new Error('Bezier point values must all be numbers.')
  48. })
  49. pType = 'float_' + point.length
  50. } else throw new Error('Bezier points must all be numbers or arrays of number.')
  51. if (firstPointType == null) {
  52. firstPointType = pType
  53. } else {
  54. if (firstPointType !== pType) {
  55. throw new Error('Bezier points must be either all numbers or all arrays of numbers of the same size.')
  56. }
  57. }
  58. })
  59. return firstPointType
  60. }
  61. const getPermutations = function (c) {
  62. const permutations = []
  63. for (let i = 0; i <= c; i++) {
  64. permutations.push(factorial(c) / (factorial(i) * factorial(c - i)))
  65. }
  66. return permutations
  67. }
  68. const factorial = function (b) {
  69. let out = 1
  70. for (let i = 2; i <= b; i++) {
  71. out *= i
  72. }
  73. return out
  74. }
  75. module.exports = create