UNPKG

@jscad/modeling

Version:

Constructive Solid Geometry (CSG) Library for JSCAD

164 lines (135 loc) 5.19 kB
const { NEPS } = require('../maths/constants') const vec2 = require('../maths/vec2') const geom2 = require('../geometries/geom2') const { isNumberArray } = require('./commonChecks') // returns angle C const solveAngleFromSSS = (a, b, c) => Math.acos(((a * a) + (b * b) - (c * c)) / (2 * a * b)) // returns side c const solveSideFromSAS = (a, C, b) => { if (C > NEPS) { return Math.sqrt(a * a + b * b - 2 * a * b * Math.cos(C)) } // Explained in https://www.nayuki.io/page/numerically-stable-law-of-cosines return Math.sqrt((a - b) * (a - b) + a * b * C * C * (1 - C * C / 12)) } // AAA is when three angles of a triangle, but no sides const solveAAA = (angles) => { const eps = Math.abs(angles[0] + angles[1] + angles[2] - Math.PI) if (eps > NEPS) throw new Error('AAA triangles require angles that sum to PI') const A = angles[0] const B = angles[1] const C = Math.PI - A - B // Note: This is not 100% proper but... // default the side c length to 1 // solve the other lengths const c = 1 const a = (c / Math.sin(C)) * Math.sin(A) const b = (c / Math.sin(C)) * Math.sin(B) return createTriangle(A, B, C, a, b, c) } // AAS is when two angles and one side are known, and the side is not between the angles const solveAAS = (values) => { const A = values[0] const B = values[1] const C = Math.PI + NEPS - A - B if (C < NEPS) throw new Error('AAS triangles require angles that sum to PI') const a = values[2] const b = (a / Math.sin(A)) * Math.sin(B) const c = (a / Math.sin(A)) * Math.sin(C) return createTriangle(A, B, C, a, b, c) } // ASA is when two angles and the side between the angles are known const solveASA = (values) => { const A = values[0] const B = values[2] const C = Math.PI + NEPS - A - B if (C < NEPS) throw new Error('ASA triangles require angles that sum to PI') const c = values[1] const a = (c / Math.sin(C)) * Math.sin(A) const b = (c / Math.sin(C)) * Math.sin(B) return createTriangle(A, B, C, a, b, c) } // SAS is when two sides and the angle between them are known const solveSAS = (values) => { const c = values[0] const B = values[1] const a = values[2] const b = solveSideFromSAS(c, B, a) const A = solveAngleFromSSS(b, c, a) // solve for A const C = Math.PI - A - B return createTriangle(A, B, C, a, b, c) } // SSA is when two sides and an angle that is not the angle between the sides are known const solveSSA = (values) => { const c = values[0] const a = values[1] const C = values[2] const A = Math.asin(a * Math.sin(C) / c) const B = Math.PI - A - C const b = (c / Math.sin(C)) * Math.sin(B) return createTriangle(A, B, C, a, b, c) } // SSS is when we know three sides of the triangle const solveSSS = (lengths) => { const a = lengths[1] const b = lengths[2] const c = lengths[0] if (((a + b) <= c) || ((b + c) <= a) || ((c + a) <= b)) { throw new Error('SSS triangle is incorrect, as the longest side is longer than the sum of the other sides') } const A = solveAngleFromSSS(b, c, a) // solve for A const B = solveAngleFromSSS(c, a, b) // solve for B const C = Math.PI - A - B return createTriangle(A, B, C, a, b, c) } const createTriangle = (A, B, C, a, b, c) => { const p0 = vec2.fromValues(0, 0) // everything starts from 0, 0 const p1 = vec2.fromValues(c, 0) const p2 = vec2.fromValues(a, 0) vec2.add(p2, vec2.rotate(p2, p2, [0, 0], Math.PI - B), p1) return geom2.fromPoints([p0, p1, p2]) } /** * Construct a triangle in two dimensional space from the given options. * The triangle is always constructed CCW from the origin, [0, 0, 0]. * @see https://www.mathsisfun.com/algebra/trig-solving-triangles.html * @param {Object} [options] - options for construction * @param {String} [options.type='SSS'] - type of triangle to construct; A ~ angle, S ~ side * @param {Array} [options.values=[1,1,1]] - angle (radians) of corners or length of sides * @returns {geom2} new 2D geometry * @alias module:modeling/primitives.triangle * * @example * let myshape = triangle({type: 'AAS', values: [degToRad(62), degToRad(35), 7]}) */ const triangle = (options) => { const defaults = { type: 'SSS', values: [1, 1, 1] } let { type, values } = Object.assign({}, defaults, options) if (typeof (type) !== 'string') throw new Error('triangle type must be a string') type = type.toUpperCase() if (!((type[0] === 'A' || type[0] === 'S') && (type[1] === 'A' || type[1] === 'S') && (type[2] === 'A' || type[2] === 'S'))) throw new Error('triangle type must contain three letters; A or S') if (!isNumberArray(values, 3)) throw new Error('triangle values must contain three values') if (!values.every((n) => n > 0)) throw new Error('triangle values must be greater than zero') switch (type) { case 'AAA': return solveAAA(values) case 'AAS': return solveAAS(values) case 'ASA': return solveASA(values) case 'SAS': return solveSAS(values) case 'SSA': return solveSSA(values) case 'SSS': return solveSSS(values) default: throw new Error('invalid triangle type, try again') } } module.exports = triangle