modeling/src/primitives/star.js

  1. const { TAU } = require('../maths/constants')
  2. const vec2 = require('../maths/vec2')
  3. const geom2 = require('../geometries/geom2')
  4. const { isGT, isGTE, isNumberArray } = require('./commonChecks')
  5. // @see http://www.jdawiseman.com/papers/easymath/surds_star_inner_radius.html
  6. const getRadiusRatio = (vertices, density) => {
  7. if (vertices > 0 && density > 1 && density < vertices / 2) {
  8. return Math.cos(Math.PI * density / vertices) / Math.cos(Math.PI * (density - 1) / vertices)
  9. }
  10. return 0
  11. }
  12. const getPoints = (vertices, radius, startAngle, center) => {
  13. const a = TAU / vertices
  14. const points = []
  15. for (let i = 0; i < vertices; i++) {
  16. const point = vec2.fromAngleRadians(vec2.create(), a * i + startAngle)
  17. vec2.scale(point, point, radius)
  18. vec2.add(point, center, point)
  19. points.push(point)
  20. }
  21. return points
  22. }
  23. /**
  24. * Construct a star in two dimensional space.
  25. * @see https://en.wikipedia.org/wiki/Star_polygon
  26. * @param {Object} [options] - options for construction
  27. * @param {Array} [options.center=[0,0]] - center of star
  28. * @param {Number} [options.vertices=5] - number of vertices (P) on the star
  29. * @param {Number} [options.density=2] - density (Q) of star
  30. * @param {Number} [options.outerRadius=1] - outer radius of vertices
  31. * @param {Number} [options.innerRadius=0] - inner radius of vertices, or zero to calculate
  32. * @param {Number} [options.startAngle=0] - starting angle for first vertice, in radians
  33. * @returns {geom2} new 2D geometry
  34. * @alias module:modeling/primitives.star
  35. *
  36. * @example
  37. * let star1 = star({vertices: 8, outerRadius: 10}) // star with 8/2 density
  38. * let star2 = star({vertices: 12, outerRadius: 40, innerRadius: 20}) // star with given radius
  39. */
  40. const star = (options) => {
  41. const defaults = {
  42. center: [0, 0],
  43. vertices: 5,
  44. outerRadius: 1,
  45. innerRadius: 0,
  46. density: 2,
  47. startAngle: 0
  48. }
  49. let { center, vertices, outerRadius, innerRadius, density, startAngle } = Object.assign({}, defaults, options)
  50. if (!isNumberArray(center, 2)) throw new Error('center must be an array of X and Y values')
  51. if (!isGTE(vertices, 2)) throw new Error('vertices must be two or more')
  52. if (!isGT(outerRadius, 0)) throw new Error('outerRadius must be greater than zero')
  53. if (!isGTE(innerRadius, 0)) throw new Error('innerRadius must be greater than zero')
  54. if (!isGTE(startAngle, 0)) throw new Error('startAngle must be greater than zero')
  55. // force integers
  56. vertices = Math.floor(vertices)
  57. density = Math.floor(density)
  58. startAngle = startAngle % TAU
  59. if (innerRadius === 0) {
  60. if (!isGTE(density, 2)) throw new Error('density must be two or more')
  61. innerRadius = outerRadius * getRadiusRatio(vertices, density)
  62. }
  63. const centerv = vec2.clone(center)
  64. const outerPoints = getPoints(vertices, outerRadius, startAngle, centerv)
  65. const innerPoints = getPoints(vertices, innerRadius, startAngle + Math.PI / vertices, centerv)
  66. const allPoints = []
  67. for (let i = 0; i < vertices; i++) {
  68. allPoints.push(outerPoints[i])
  69. allPoints.push(innerPoints[i])
  70. }
  71. return geom2.fromPoints(allPoints)
  72. }
  73. module.exports = star