modeling/src/primitives/ellipsoid.js

  1. const { TAU } = require('../maths/constants')
  2. const vec3 = require('../maths/vec3')
  3. const geom3 = require('../geometries/geom3')
  4. const poly3 = require('../geometries/poly3')
  5. const { sin, cos } = require('../maths/utils/trigonometry')
  6. const { isGTE, isNumberArray } = require('./commonChecks')
  7. /**
  8. * Construct an axis-aligned ellipsoid in three dimensional space.
  9. * @param {Object} [options] - options for construction
  10. * @param {Array} [options.center=[0,0,0]] - center of ellipsoid
  11. * @param {Array} [options.radius=[1,1,1]] - radius of ellipsoid, along X, Y and Z
  12. * @param {Number} [options.segments=32] - number of segments to create per full rotation
  13. * @param {Array} [options.axes] - an array with three vectors for the x, y and z base vectors
  14. * @returns {geom3} new 3D geometry
  15. * @alias module:modeling/primitives.ellipsoid
  16. *
  17. * @example
  18. * let myshape = ellipsoid({radius: [5, 10, 20]})
  19. */
  20. const ellipsoid = (options) => {
  21. const defaults = {
  22. center: [0, 0, 0],
  23. radius: [1, 1, 1],
  24. segments: 32,
  25. axes: [[1, 0, 0], [0, -1, 0], [0, 0, 1]]
  26. }
  27. const { center, radius, segments, axes } = Object.assign({}, defaults, options)
  28. if (!isNumberArray(center, 3)) throw new Error('center must be an array of X, Y and Z values')
  29. if (!isNumberArray(radius, 3)) throw new Error('radius must be an array of X, Y and Z values')
  30. if (!radius.every((n) => n >= 0)) throw new Error('radius values must be positive')
  31. if (!isGTE(segments, 4)) throw new Error('segments must be four or more')
  32. // if any radius is zero return empty geometry
  33. if (radius[0] === 0 || radius[1] === 0 || radius[2] === 0) return geom3.create()
  34. const xvector = vec3.scale(vec3.create(), vec3.normalize(vec3.create(), axes[0]), radius[0])
  35. const yvector = vec3.scale(vec3.create(), vec3.normalize(vec3.create(), axes[1]), radius[1])
  36. const zvector = vec3.scale(vec3.create(), vec3.normalize(vec3.create(), axes[2]), radius[2])
  37. const qsegments = Math.round(segments / 4)
  38. let prevcylinderpoint
  39. const polygons = []
  40. const p1 = vec3.create()
  41. const p2 = vec3.create()
  42. for (let slice1 = 0; slice1 <= segments; slice1++) {
  43. const angle = TAU * slice1 / segments
  44. const cylinderpoint = vec3.add(vec3.create(), vec3.scale(p1, xvector, cos(angle)), vec3.scale(p2, yvector, sin(angle)))
  45. if (slice1 > 0) {
  46. let prevcospitch, prevsinpitch
  47. for (let slice2 = 0; slice2 <= qsegments; slice2++) {
  48. const pitch = TAU / 4 * slice2 / qsegments
  49. const cospitch = cos(pitch)
  50. const sinpitch = sin(pitch)
  51. if (slice2 > 0) {
  52. let points = []
  53. let point
  54. point = vec3.subtract(vec3.create(), vec3.scale(p1, prevcylinderpoint, prevcospitch), vec3.scale(p2, zvector, prevsinpitch))
  55. points.push(vec3.add(point, point, center))
  56. point = vec3.subtract(vec3.create(), vec3.scale(p1, cylinderpoint, prevcospitch), vec3.scale(p2, zvector, prevsinpitch))
  57. points.push(vec3.add(point, point, center))
  58. if (slice2 < qsegments) {
  59. point = vec3.subtract(vec3.create(), vec3.scale(p1, cylinderpoint, cospitch), vec3.scale(p2, zvector, sinpitch))
  60. points.push(vec3.add(point, point, center))
  61. }
  62. point = vec3.subtract(vec3.create(), vec3.scale(p1, prevcylinderpoint, cospitch), vec3.scale(p2, zvector, sinpitch))
  63. points.push(vec3.add(point, point, center))
  64. polygons.push(poly3.create(points))
  65. points = []
  66. point = vec3.add(vec3.create(), vec3.scale(p1, prevcylinderpoint, prevcospitch), vec3.scale(p2, zvector, prevsinpitch))
  67. points.push(vec3.add(vec3.create(), center, point))
  68. point = vec3.add(point, vec3.scale(p1, cylinderpoint, prevcospitch), vec3.scale(p2, zvector, prevsinpitch))
  69. points.push(vec3.add(vec3.create(), center, point))
  70. if (slice2 < qsegments) {
  71. point = vec3.add(point, vec3.scale(p1, cylinderpoint, cospitch), vec3.scale(p2, zvector, sinpitch))
  72. points.push(vec3.add(vec3.create(), center, point))
  73. }
  74. point = vec3.add(point, vec3.scale(p1, prevcylinderpoint, cospitch), vec3.scale(p2, zvector, sinpitch))
  75. points.push(vec3.add(vec3.create(), center, point))
  76. points.reverse()
  77. polygons.push(poly3.create(points))
  78. }
  79. prevcospitch = cospitch
  80. prevsinpitch = sinpitch
  81. }
  82. }
  83. prevcylinderpoint = cylinderpoint
  84. }
  85. return geom3.create(polygons)
  86. }
  87. module.exports = ellipsoid