modeling/src/primitives/triangle.js

  1. const { NEPS } = require('../maths/constants')
  2. const vec2 = require('../maths/vec2')
  3. const geom2 = require('../geometries/geom2')
  4. const { isNumberArray } = require('./commonChecks')
  5. // returns angle C
  6. const solveAngleFromSSS = (a, b, c) => Math.acos(((a * a) + (b * b) - (c * c)) / (2 * a * b))
  7. // returns side c
  8. const solveSideFromSAS = (a, C, b) => {
  9. if (C > NEPS) {
  10. return Math.sqrt(a * a + b * b - 2 * a * b * Math.cos(C))
  11. }
  12. // Explained in https://www.nayuki.io/page/numerically-stable-law-of-cosines
  13. return Math.sqrt((a - b) * (a - b) + a * b * C * C * (1 - C * C / 12))
  14. }
  15. // AAA is when three angles of a triangle, but no sides
  16. const solveAAA = (angles) => {
  17. const eps = Math.abs(angles[0] + angles[1] + angles[2] - Math.PI)
  18. if (eps > NEPS) throw new Error('AAA triangles require angles that sum to PI')
  19. const A = angles[0]
  20. const B = angles[1]
  21. const C = Math.PI - A - B
  22. // Note: This is not 100% proper but...
  23. // default the side c length to 1
  24. // solve the other lengths
  25. const c = 1
  26. const a = (c / Math.sin(C)) * Math.sin(A)
  27. const b = (c / Math.sin(C)) * Math.sin(B)
  28. return createTriangle(A, B, C, a, b, c)
  29. }
  30. // AAS is when two angles and one side are known, and the side is not between the angles
  31. const solveAAS = (values) => {
  32. const A = values[0]
  33. const B = values[1]
  34. const C = Math.PI + NEPS - A - B
  35. if (C < NEPS) throw new Error('AAS triangles require angles that sum to PI')
  36. const a = values[2]
  37. const b = (a / Math.sin(A)) * Math.sin(B)
  38. const c = (a / Math.sin(A)) * Math.sin(C)
  39. return createTriangle(A, B, C, a, b, c)
  40. }
  41. // ASA is when two angles and the side between the angles are known
  42. const solveASA = (values) => {
  43. const A = values[0]
  44. const B = values[2]
  45. const C = Math.PI + NEPS - A - B
  46. if (C < NEPS) throw new Error('ASA triangles require angles that sum to PI')
  47. const c = values[1]
  48. const a = (c / Math.sin(C)) * Math.sin(A)
  49. const b = (c / Math.sin(C)) * Math.sin(B)
  50. return createTriangle(A, B, C, a, b, c)
  51. }
  52. // SAS is when two sides and the angle between them are known
  53. const solveSAS = (values) => {
  54. const c = values[0]
  55. const B = values[1]
  56. const a = values[2]
  57. const b = solveSideFromSAS(c, B, a)
  58. const A = solveAngleFromSSS(b, c, a) // solve for A
  59. const C = Math.PI - A - B
  60. return createTriangle(A, B, C, a, b, c)
  61. }
  62. // SSA is when two sides and an angle that is not the angle between the sides are known
  63. const solveSSA = (values) => {
  64. const c = values[0]
  65. const a = values[1]
  66. const C = values[2]
  67. const A = Math.asin(a * Math.sin(C) / c)
  68. const B = Math.PI - A - C
  69. const b = (c / Math.sin(C)) * Math.sin(B)
  70. return createTriangle(A, B, C, a, b, c)
  71. }
  72. // SSS is when we know three sides of the triangle
  73. const solveSSS = (lengths) => {
  74. const a = lengths[1]
  75. const b = lengths[2]
  76. const c = lengths[0]
  77. if (((a + b) <= c) || ((b + c) <= a) || ((c + a) <= b)) {
  78. throw new Error('SSS triangle is incorrect, as the longest side is longer than the sum of the other sides')
  79. }
  80. const A = solveAngleFromSSS(b, c, a) // solve for A
  81. const B = solveAngleFromSSS(c, a, b) // solve for B
  82. const C = Math.PI - A - B
  83. return createTriangle(A, B, C, a, b, c)
  84. }
  85. const createTriangle = (A, B, C, a, b, c) => {
  86. const p0 = vec2.fromValues(0, 0) // everything starts from 0, 0
  87. const p1 = vec2.fromValues(c, 0)
  88. const p2 = vec2.fromValues(a, 0)
  89. vec2.add(p2, vec2.rotate(p2, p2, [0, 0], Math.PI - B), p1)
  90. return geom2.fromPoints([p0, p1, p2])
  91. }
  92. /**
  93. * Construct a triangle in two dimensional space from the given options.
  94. * The triangle is always constructed CCW from the origin, [0, 0, 0].
  95. * @see https://www.mathsisfun.com/algebra/trig-solving-triangles.html
  96. * @param {Object} [options] - options for construction
  97. * @param {String} [options.type='SSS'] - type of triangle to construct; A ~ angle, S ~ side
  98. * @param {Array} [options.values=[1,1,1]] - angle (radians) of corners or length of sides
  99. * @returns {geom2} new 2D geometry
  100. * @alias module:modeling/primitives.triangle
  101. *
  102. * @example
  103. * let myshape = triangle({type: 'AAS', values: [degToRad(62), degToRad(35), 7]})
  104. */
  105. const triangle = (options) => {
  106. const defaults = {
  107. type: 'SSS',
  108. values: [1, 1, 1]
  109. }
  110. let { type, values } = Object.assign({}, defaults, options)
  111. if (typeof (type) !== 'string') throw new Error('triangle type must be a string')
  112. type = type.toUpperCase()
  113. if (!((type[0] === 'A' || type[0] === 'S') &&
  114. (type[1] === 'A' || type[1] === 'S') &&
  115. (type[2] === 'A' || type[2] === 'S'))) throw new Error('triangle type must contain three letters; A or S')
  116. if (!isNumberArray(values, 3)) throw new Error('triangle values must contain three values')
  117. if (!values.every((n) => n > 0)) throw new Error('triangle values must be greater than zero')
  118. switch (type) {
  119. case 'AAA':
  120. return solveAAA(values)
  121. case 'AAS':
  122. return solveAAS(values)
  123. case 'ASA':
  124. return solveASA(values)
  125. case 'SAS':
  126. return solveSAS(values)
  127. case 'SSA':
  128. return solveSSA(values)
  129. case 'SSS':
  130. return solveSSS(values)
  131. default:
  132. throw new Error('invalid triangle type, try again')
  133. }
  134. }
  135. module.exports = triangle