@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.
- package/CHANGELOG.md +21 -0
- package/dist/jscad-modeling.min.js +81 -78
- package/package.json +2 -2
- package/src/colors/colorize.test.js +2 -2
- package/src/geometries/geom2/transform.js +0 -2
- package/src/geometries/geom3/transform.js +0 -2
- package/src/geometries/path2/transform.js +0 -2
- package/src/geometries/poly3/isConvex.js +1 -1
- package/src/geometries/poly3/measureArea.js +12 -13
- package/src/geometries/poly3/measureArea.test.js +15 -0
- package/src/geometries/poly3/plane.js +1 -2
- package/src/maths/plane/fromPoints.js +32 -10
- package/src/maths/plane/fromPoints.test.js +4 -0
- package/src/measurements/measureBoundingBox.js +14 -27
- package/src/measurements/measureCenterOfMass.js +0 -1
- package/src/measurements/measureEpsilon.js +3 -9
- package/src/operations/booleans/reTesselateCoplanarPolygons.js +1 -1
- package/src/operations/booleans/trees/PolygonTreeNode.js +0 -1
- package/src/operations/expansions/expand.test.js +1 -1
- package/src/operations/extrusions/extrudeRotate.test.js +18 -10
- package/src/operations/modifiers/generalize.js +0 -1
- package/src/operations/modifiers/snapPolygons.js +1 -1
- package/src/operations/modifiers/snapPolygons.test.js +10 -10
- package/src/primitives/index.d.ts +1 -0
- package/src/primitives/index.js +2 -1
- package/src/primitives/triangle.d.ts +10 -0
- package/src/primitives/triangle.js +164 -0
- package/src/primitives/triangle.test.js +95 -0
|
@@ -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
|
+
})
|