@jscad/modeling 2.6.1 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,164 @@
1
+ const vec2 = require('../maths/vec2')
2
+
3
+ const geom2 = require('../geometries/geom2')
4
+
5
+ const { isNumberArray } = require('./commonChecks')
6
+
7
+ const NEPS = 1e-13
8
+
9
+ // returns angle C
10
+ const solveAngleFromSSS = (a, b, c) => Math.acos(((a * a) + (b * b) - (c * c)) / (2 * a * b))
11
+
12
+ // returns side c
13
+ const solveSideFromSAS = (a, C, b) => {
14
+ if (C > NEPS) {
15
+ return Math.sqrt(a * a + b * b - 2 * a * b * Math.cos(C))
16
+ }
17
+
18
+ // Explained in https://www.nayuki.io/page/numerically-stable-law-of-cosines
19
+ return Math.sqrt((a - b) * (a - b) + a * b * C * C * (1 - C * C / 12))
20
+ }
21
+
22
+ // AAA is when three angles of a triangle, but no sides
23
+ const solveAAA = (angles) => {
24
+ const eps = Math.abs(angles[0] + angles[1] + angles[2] - Math.PI)
25
+ if (eps > NEPS) throw new Error('AAA triangles require angles that sum to PI')
26
+
27
+ const A = angles[0]
28
+ const B = angles[1]
29
+ const C = Math.PI - A - B
30
+
31
+ // Note: This is not 100% proper but...
32
+ // default the side c length to 1
33
+ // solve the other lengths
34
+ const c = 1
35
+ const a = (c / Math.sin(C)) * Math.sin(A)
36
+ const b = (c / Math.sin(C)) * Math.sin(B)
37
+ return createTriangle(A, B, C, a, b, c)
38
+ }
39
+
40
+ // AAS is when two angles and one side are known, and the side is not between the angles
41
+ const solveAAS = (values) => {
42
+ const A = values[0]
43
+ const B = values[1]
44
+ const C = Math.PI + NEPS - A - B
45
+
46
+ if (C < NEPS) throw new Error('AAS triangles require angles that sum to PI')
47
+
48
+ const a = values[2]
49
+ const b = (a / Math.sin(A)) * Math.sin(B)
50
+ const c = (a / Math.sin(A)) * Math.sin(C)
51
+ return createTriangle(A, B, C, a, b, c)
52
+ }
53
+
54
+ // ASA is when two angles and the side between the angles are known
55
+ const solveASA = (values) => {
56
+ const A = values[0]
57
+ const B = values[2]
58
+ const C = Math.PI + NEPS - A - B
59
+
60
+ if (C < NEPS) throw new Error('ASA triangles require angles that sum to PI')
61
+
62
+ const c = values[1]
63
+ const a = (c / Math.sin(C)) * Math.sin(A)
64
+ const b = (c / Math.sin(C)) * Math.sin(B)
65
+ return createTriangle(A, B, C, a, b, c)
66
+ }
67
+
68
+ // SAS is when two sides and the angle between them are known
69
+ const solveSAS = (values) => {
70
+ const c = values[0]
71
+ const B = values[1]
72
+ const a = values[2]
73
+
74
+ const b = solveSideFromSAS(c, B, a)
75
+
76
+ const A = solveAngleFromSSS(b, c, a) // solve for A
77
+ const C = Math.PI - A - B
78
+ return createTriangle(A, B, C, a, b, c)
79
+ }
80
+
81
+ // SSA is when two sides and an angle that is not the angle between the sides are known
82
+ const solveSSA = (values) => {
83
+ const c = values[0]
84
+ const a = values[1]
85
+ const C = values[2]
86
+
87
+ const A = Math.asin(a * Math.sin(C) / c)
88
+ const B = Math.PI - A - C
89
+
90
+ const b = (c / Math.sin(C)) * Math.sin(B)
91
+ return createTriangle(A, B, C, a, b, c)
92
+ }
93
+
94
+ // SSS is when we know three sides of the triangle
95
+ const solveSSS = (lengths) => {
96
+ const a = lengths[1]
97
+ const b = lengths[2]
98
+ const c = lengths[0]
99
+ if (((a + b) <= c) || ((b + c) <= a) || ((c + a) <= b)) {
100
+ throw new Error('SSS triangle is incorrect, as the longest side is longer than the sum of the other sides')
101
+ }
102
+
103
+ const A = solveAngleFromSSS(b, c, a) // solve for A
104
+ const B = solveAngleFromSSS(c, a, b) // solve for B
105
+ const C = Math.PI - A - B
106
+ return createTriangle(A, B, C, a, b, c)
107
+ }
108
+
109
+ const createTriangle = (A, B, C, a, b, c) => {
110
+ const p0 = vec2.fromValues(0, 0) // everything starts from 0, 0
111
+ const p1 = vec2.fromValues(c, 0)
112
+ const p2 = vec2.fromValues(a, 0)
113
+ vec2.add(p2, vec2.rotate(p2, p2, [0, 0], Math.PI - B), p1)
114
+ return geom2.fromPoints([p0, p1, p2])
115
+ }
116
+
117
+ /**
118
+ * Construct a triangle in two dimensional space from the given options.
119
+ * The triangle is always constructed CCW from the origin, [0, 0, 0].
120
+ * @see https://www.mathsisfun.com/algebra/trig-solving-triangles.html
121
+ * @param {Object} [options] - options for construction
122
+ * @param {String} [options.type='SSS' - type of triangle to construct; A ~ angle, S ~ side
123
+ * @param {Array} [options.values=[1,1,1]] - angle (radians) of corners or length of sides
124
+ * @returns {geom2} new 2D geometry
125
+ * @alias module:modeling/primitives.triangle
126
+ *
127
+ * @example
128
+ * let myshape = triangle({type: 'AAS', values: [values: [degToRad(62), degToRad(35), 7]})
129
+ */
130
+ const triangle = (options) => {
131
+ const defaults = {
132
+ type: 'SSS',
133
+ values: [1, 1, 1]
134
+ }
135
+ let { type, values } = Object.assign({}, defaults, options)
136
+
137
+ if (typeof (type) !== 'string') throw new Error('triangle type must be a string')
138
+ type = type.toUpperCase()
139
+ if (!((type[0] === 'A' || type[0] === 'S') &&
140
+ (type[1] === 'A' || type[1] === 'S') &&
141
+ (type[2] === 'A' || type[2] === 'S'))) throw new Error('triangle type must contain three letters; A or S')
142
+
143
+ if (!isNumberArray(values, 3)) throw new Error('triangle values must contain three values')
144
+ if (!values.every((n) => n > 0)) throw new Error('triangle values must be greater than zero')
145
+
146
+ switch (type) {
147
+ case 'AAA':
148
+ return solveAAA(values)
149
+ case 'AAS':
150
+ return solveAAS(values)
151
+ case 'ASA':
152
+ return solveASA(values)
153
+ case 'SAS':
154
+ return solveSAS(values)
155
+ case 'SSA':
156
+ return solveSSA(values)
157
+ case 'SSS':
158
+ return solveSSS(values)
159
+ default:
160
+ throw new Error('invalid triangle type, try again')
161
+ }
162
+ }
163
+
164
+ module.exports = triangle
@@ -0,0 +1,95 @@
1
+ const test = require('ava')
2
+
3
+ const { triangle } = require('./index')
4
+
5
+ const degToRad = require('../utils/degToRad')
6
+ const geom2 = require('../geometries/geom2')
7
+
8
+ const comparePoints = require('../../test/helpers/comparePoints')
9
+
10
+ test('triangle (defaults)', (t) => {
11
+ const geometry = triangle()
12
+ const obs = geom2.toPoints(geometry)
13
+ const exp = [
14
+ [0, 0],
15
+ [1, 0],
16
+ [0.5000000000000002, 0.8660254037844387]
17
+ ]
18
+
19
+ t.deepEqual(obs.length, 3)
20
+ t.true(comparePoints(obs, exp))
21
+ })
22
+
23
+ test('triangle (options)', (t) => {
24
+ // test SSS
25
+ let geometry = triangle({ type: 'SSS', values: [7, 8, 6] })
26
+ let obs = geom2.toPoints(geometry)
27
+ let exp = [
28
+ [0, 0],
29
+ [7, 0],
30
+ [1.5, 5.809475019311125]
31
+ ]
32
+
33
+ t.deepEqual(obs.length, 3)
34
+ t.true(comparePoints(obs, exp))
35
+
36
+ // test AAA
37
+ geometry = triangle({ type: 'AAA', values: [Math.PI / 2, Math.PI / 4, Math.PI / 4] })
38
+ obs = geom2.toPoints(geometry)
39
+ exp = [
40
+ [0, 0],
41
+ [1, 0],
42
+ [0, 1.0000000000000002]
43
+ ]
44
+
45
+ t.deepEqual(obs.length, 3)
46
+ t.true(comparePoints(obs, exp))
47
+
48
+ // test AAS
49
+ geometry = triangle({ type: 'AAS', values: [degToRad(62), degToRad(35), 7] })
50
+ obs = geom2.toPoints(geometry)
51
+ exp = [
52
+ [0, 0],
53
+ [7.86889631692936, 0],
54
+ [2.1348320069064197, 4.015035054457325]
55
+ ]
56
+
57
+ t.deepEqual(obs.length, 3)
58
+ t.true(comparePoints(obs, exp))
59
+
60
+ // test ASA
61
+ geometry = triangle({ type: 'ASA', values: [degToRad(76), 9, degToRad(34)] })
62
+ obs = geom2.toPoints(geometry)
63
+ exp = [
64
+ [0, 0],
65
+ [9, 0],
66
+ [1.295667368233083, 5.196637976713814]
67
+ ]
68
+
69
+ t.deepEqual(obs.length, 3)
70
+ t.true(comparePoints(obs, exp))
71
+
72
+ // test SAS
73
+ geometry = triangle({ type: 'SAS', values: [5, degToRad(49), 7] })
74
+ obs = geom2.toPoints(geometry)
75
+ exp = [
76
+ [0, 0],
77
+ [5, 0],
78
+ [0.4075867970664495, 5.282967061559405]
79
+ ]
80
+
81
+ t.deepEqual(obs.length, 3)
82
+ t.true(comparePoints(obs, exp))
83
+
84
+ // test SSA
85
+ geometry = triangle({ type: 'SSA', values: [8, 13, degToRad(31)] })
86
+ obs = geom2.toPoints(geometry)
87
+ exp = [
88
+ [0, 0],
89
+ [8, 0],
90
+ [8.494946725906148, 12.990574573070846]
91
+ ]
92
+
93
+ t.deepEqual(obs.length, 3)
94
+ t.true(comparePoints(obs, exp))
95
+ })