@jscad/modeling 2.5.2 → 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.
Files changed (64) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/dist/jscad-modeling.min.js +217 -202
  3. package/package.json +2 -2
  4. package/src/colors/colorize.js +4 -5
  5. package/src/colors/colorize.test.js +19 -12
  6. package/src/geometries/geom2/applyTransforms.js +1 -1
  7. package/src/geometries/geom2/clone.js +2 -12
  8. package/src/geometries/geom2/transform.js +2 -6
  9. package/src/geometries/geom3/applyTransforms.js +1 -1
  10. package/src/geometries/geom3/clone.js +2 -14
  11. package/src/geometries/geom3/clone.test.js +0 -2
  12. package/src/geometries/geom3/create.js +0 -2
  13. package/src/geometries/geom3/create.test.js +0 -2
  14. package/src/geometries/geom3/fromCompactBinary.js +4 -6
  15. package/src/geometries/geom3/fromToCompactBinary.test.js +0 -6
  16. package/src/geometries/geom3/invert.test.js +0 -2
  17. package/src/geometries/geom3/toCompactBinary.js +8 -10
  18. package/src/geometries/geom3/transform.js +2 -7
  19. package/src/geometries/geom3/transform.test.js +0 -1
  20. package/src/geometries/geom3/type.d.ts +0 -1
  21. package/src/geometries/path2/applyTransforms.js +1 -1
  22. package/src/geometries/path2/clone.js +2 -13
  23. package/src/geometries/path2/transform.js +2 -7
  24. package/src/geometries/poly3/isConvex.js +1 -1
  25. package/src/geometries/poly3/measureArea.js +12 -13
  26. package/src/geometries/poly3/measureArea.test.js +15 -0
  27. package/src/geometries/poly3/plane.js +1 -2
  28. package/src/maths/mat4/index.js +1 -0
  29. package/src/maths/mat4/isOnlyTransformScale.js +21 -0
  30. package/src/maths/mat4/isOnlyTransformScale.test.js +27 -0
  31. package/src/maths/plane/fromPoints.js +32 -10
  32. package/src/maths/plane/fromPoints.test.js +4 -0
  33. package/src/maths/utils/index.d.ts +0 -1
  34. package/src/maths/utils/index.js +0 -1
  35. package/src/maths/vec2/rotate.js +1 -1
  36. package/src/maths/vec2/rotate.test.js +3 -3
  37. package/src/measurements/index.js +4 -1
  38. package/src/measurements/measureBoundingBox.js +128 -38
  39. package/src/measurements/measureBoundingBox.test.js +8 -0
  40. package/src/measurements/measureBoundingSphere.js +146 -0
  41. package/src/measurements/measureBoundingSphere.test.js +59 -0
  42. package/src/measurements/measureCenter.js +28 -0
  43. package/src/measurements/measureCenter.test.js +58 -0
  44. package/src/measurements/measureCenterOfMass.js +106 -0
  45. package/src/measurements/measureCenterOfMass.test.js +58 -0
  46. package/src/measurements/measureDimensions.js +28 -0
  47. package/src/measurements/measureDimensions.test.js +58 -0
  48. package/src/measurements/measureEpsilon.js +3 -9
  49. package/src/operations/booleans/reTesselateCoplanarPolygons.js +1 -1
  50. package/src/operations/booleans/trees/PolygonTreeNode.js +0 -1
  51. package/src/operations/expansions/expand.test.js +1 -1
  52. package/src/operations/extrusions/extrudeRotate.test.js +18 -10
  53. package/src/operations/modifiers/generalize.js +0 -1
  54. package/src/operations/modifiers/snapPolygons.js +2 -2
  55. package/src/operations/modifiers/snapPolygons.test.js +13 -5
  56. package/src/primitives/index.d.ts +1 -0
  57. package/src/primitives/index.js +2 -1
  58. package/src/primitives/triangle.d.ts +10 -0
  59. package/src/primitives/triangle.js +164 -0
  60. package/src/primitives/triangle.test.js +95 -0
  61. package/src/maths/utils/clamp.d.ts +0 -3
  62. package/src/maths/utils/clamp.js +0 -4
  63. package/src/maths/utils/quantizeForSpace.d.ts +0 -3
  64. package/src/maths/utils/quantizeForSpace.js +0 -6
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jscad/modeling",
3
- "version": "2.5.2",
3
+ "version": "2.7.0",
4
4
  "description": "Constructive Solid Geometry (CSG) Library for JSCAD",
5
5
  "repository": "https://github.com/jscad/OpenJSCAD.org",
6
6
  "main": "src/index.js",
@@ -60,5 +60,5 @@
60
60
  "nyc": "15.1.0",
61
61
  "uglifyify": "5.0.2"
62
62
  },
63
- "gitHead": "a761f641bb2ac6016dc4a02ef86ce279dd553d7c"
63
+ "gitHead": "fbc9b90f0ae02bea4e6e7b974c65e751c3858269"
64
64
  }
@@ -6,13 +6,13 @@ const path2 = require('../geometries/path2')
6
6
  const poly3 = require('../geometries/poly3')
7
7
 
8
8
  const colorGeom2 = (color, object) => {
9
- const newgeom2 = geom2.create(geom2.toSides(object))
9
+ const newgeom2 = geom2.clone(object)
10
10
  newgeom2.color = color
11
11
  return newgeom2
12
12
  }
13
13
 
14
14
  const colorGeom3 = (color, object) => {
15
- const newgeom3 = geom3.create(geom3.toPolygons(object))
15
+ const newgeom3 = geom3.clone(object)
16
16
  newgeom3.color = color
17
17
  return newgeom3
18
18
  }
@@ -31,10 +31,9 @@ const colorPoly3 = (color, object) => {
31
31
 
32
32
  /**
33
33
  * Assign the given color to the given objects.
34
- * Note: The color should only be assigned after performing all operations.
35
34
  * @param {Array} color - RGBA color values, where each value is between 0 and 1.0
36
- * @param {Object|Array} objects - the objects of which to color
37
- * @return {Object|Array} new geometry, or list of new geometries with an additional attribute 'color'
35
+ * @param {Object|Array} objects - the objects of which to apply the given color
36
+ * @return {Object|Array} new object, or list of new objects with an additional attribute 'color'
38
37
  * @alias module:modeling/colors.colorize
39
38
  *
40
39
  * @example
@@ -43,23 +43,30 @@ test('color (rgba on geometry)', (t) => {
43
43
  const obs = colorize([1, 1, 0.5, 0.8], obj0, obj1, obj2, obj3)
44
44
  t.is(obs.length, 4)
45
45
 
46
- let exp = geom2.clone(obj0)
47
- exp.color = [1, 1, 0.5, 0.8]
48
46
  t.not(obj0, obs[0])
49
- t.deepEqual(obs[0], exp)
47
+ t.deepEqual(obs[0].color, [1, 1, 0.5, 0.8])
50
48
 
51
- exp = geom3.clone(obj1)
52
- exp.color = [1, 1, 0.5, 0.8]
53
49
  t.not(obj1, obs[1])
54
- t.deepEqual(obs[1], exp)
50
+ t.deepEqual(obs[1].color, [1, 1, 0.5, 0.8])
55
51
 
56
- exp = path2.clone(obj2)
57
- exp.color = [1, 1, 0.5, 0.8]
58
52
  t.not(obj2, obs[2])
59
- t.deepEqual(obs[2], exp)
53
+ t.deepEqual(obs[2].color, [1, 1, 0.5, 0.8])
60
54
 
61
- exp = poly3.clone(obj3)
62
- exp.color = [1, 1, 0.5, 0.8]
63
55
  t.not(obj3, obs[3])
64
- t.deepEqual(obs[3], exp)
56
+ t.deepEqual(obs[3].color, [1, 1, 0.5, 0.8])
57
+ })
58
+
59
+ test('color (returns new object)', (t) => {
60
+ const obj0 = geom2.fromPoints([[0, 0], [1, 0], [0, 1]])
61
+ // const obj1 = geom2.fromPoints([[0, 0], [1, 0], [0, 1]])
62
+ // const obj2 = geom2.fromPoints([[0, 0], [1, 0], [0, 1]])
63
+ const obj3 = geom2.fromPoints([[0, 0], [1, 0], [0, 1]])
64
+
65
+ const obs = colorize([1, 1, 1, 0.8], obj0, obj3)
66
+ t.not(obj0, obs[0])
67
+ t.deepEqual(obs[0].color, [1, 1, 1, 0.8])
68
+ t.is(obj0.color, undefined)
69
+ t.not(obj3, obs[1])
70
+ t.deepEqual(obs[1].color, [1, 1, 1, 0.8])
71
+ t.is(obj3.color, undefined)
65
72
  })
@@ -19,7 +19,7 @@ const applyTransforms = (geometry) => {
19
19
  const p1 = vec2.transform(vec2.create(), side[1], geometry.transforms)
20
20
  return [p0, p1]
21
21
  })
22
- mat4.identity(geometry.transforms)
22
+ geometry.transforms = mat4.create()
23
23
  return geometry
24
24
  }
25
25
 
@@ -1,19 +1,9 @@
1
- const mat4 = require('../../maths/mat4')
2
- const vec2 = require('../../maths/vec2')
3
-
4
- const create = require('./create')
5
-
6
1
  /**
7
- * Performs a deep clone of the given geometry.
2
+ * Performs a shallow clone of the given geometry.
8
3
  * @param {geom2} geometry - the geometry to clone
9
4
  * @returns {geom2} new geometry
10
5
  * @alias module:modeling/geometries/geom2.clone
11
6
  */
12
- const clone = (geometry) => {
13
- const out = create()
14
- out.sides = geometry.sides.map((side) => [vec2.clone(side[0]), vec2.clone(side[1])])
15
- out.transforms = mat4.clone(geometry.transforms)
16
- return out
17
- }
7
+ const clone = (geometry) => Object.assign({}, geometry)
18
8
 
19
9
  module.exports = clone
@@ -1,7 +1,5 @@
1
1
  const mat4 = require('../../maths/mat4')
2
2
 
3
- const create = require('./create')
4
-
5
3
  /**
6
4
  * Transform the given geometry using the given matrix.
7
5
  * This is a lazy transform of the sides, as this function only adjusts the transforms.
@@ -15,10 +13,8 @@ const create = require('./create')
15
13
  * let newgeometry = transform(fromZRotation(degToRad(90)), geometry)
16
14
  */
17
15
  const transform = (matrix, geometry) => {
18
- const newgeometry = create(geometry.sides) // reuse the sides
19
-
20
- mat4.multiply(newgeometry.transforms, matrix, geometry.transforms)
21
- return newgeometry
16
+ const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)
17
+ return Object.assign({}, geometry, { transforms })
22
18
  }
23
19
 
24
20
  module.exports = transform
@@ -17,7 +17,7 @@ const applyTransforms = (geometry) => {
17
17
  // const isMirror = mat4.isMirroring(geometry.transforms)
18
18
  // TBD if (isMirror) newvertices.reverse()
19
19
  geometry.polygons = geometry.polygons.map((polygon) => poly3.transform(geometry.transforms, polygon))
20
- mat4.identity(geometry.transforms)
20
+ geometry.transforms = mat4.create()
21
21
  return geometry
22
22
  }
23
23
 
@@ -1,21 +1,9 @@
1
- const mat4 = require('../../maths/mat4')
2
-
3
- const poly3 = require('../poly3')
4
-
5
- const create = require('./create')
6
-
7
1
  /**
8
- * Performs a deep clone of the given geometry.
2
+ * Performs a shallow clone of the given geometry.
9
3
  * @param {geom3} geometry - the geometry to clone
10
4
  * @returns {geom3} a new geometry
11
5
  * @alias module:modeling/geometries/geom3.clone
12
6
  */
13
- const clone = (geometry) => {
14
- const out = create()
15
- out.polygons = geometry.polygons.map((polygon) => poly3.clone(polygon))
16
- out.isRetesselated = geometry.isRetesselated
17
- out.transforms = mat4.clone(geometry.transforms)
18
- return out
19
- }
7
+ const clone = (geometry) => Object.assign({}, geometry)
20
8
 
21
9
  module.exports = clone
@@ -7,7 +7,6 @@ const { comparePolygons, compareVectors } = require('../../../test/helpers/')
7
7
  test('clone: Creates a clone on an empty geom3', (t) => {
8
8
  const expected = {
9
9
  polygons: [],
10
- isRetesselated: false,
11
10
  transforms: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
12
11
  }
13
12
  const geometry = create()
@@ -22,7 +21,6 @@ test('clone: Creates a clone of a populated geom3', (t) => {
22
21
  polygons: [
23
22
  { vertices: [[0, 0, 0], [1, 0, 0], [1, 0, 1]] }
24
23
  ],
25
- isRetesselated: false,
26
24
  transforms: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
27
25
  }
28
26
  const geometry = fromPoints(points)
@@ -4,7 +4,6 @@ const mat4 = require('../../maths/mat4')
4
4
  * Represents a 3D geometry consisting of a list of polygons.
5
5
  * @typedef {Object} geom3
6
6
  * @property {Array} polygons - list of polygons, each polygon containing three or more points
7
- * @property {Boolean} isRetesselated - true if retesselation has been performed
8
7
  * @property {mat4} transforms - transforms to apply to the sides, see transform()
9
8
  */
10
9
 
@@ -20,7 +19,6 @@ const create = (polygons) => {
20
19
  }
21
20
  return {
22
21
  polygons: polygons,
23
- isRetesselated: false,
24
22
  transforms: mat4.create()
25
23
  }
26
24
  }
@@ -7,7 +7,6 @@ const { create } = require('./index')
7
7
  test('create: Creates an empty geom3', (t) => {
8
8
  const expected = {
9
9
  polygons: [],
10
- isRetesselated: false,
11
10
  transforms: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
12
11
  }
13
12
  t.deepEqual(create(), expected)
@@ -20,7 +19,6 @@ test('create: Creates a populated geom3', (t) => {
20
19
  const polygons = [polygon]
21
20
  const expected = {
22
21
  polygons: polygons,
23
- isRetesselated: false,
24
22
  transforms: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
25
23
  }
26
24
  t.deepEqual(create(polygons), expected)
@@ -18,10 +18,8 @@ const fromCompactBinary = (data) => {
18
18
 
19
19
  created.transforms = mat4.clone(data.slice(1, 17))
20
20
 
21
- created.isRetesselated = !!data[17]
22
-
23
- const numberOfVertices = data[22]
24
- let ci = 23
21
+ const numberOfVertices = data[21]
22
+ let ci = 22
25
23
  let vi = data.length - (numberOfVertices * 3)
26
24
  while (vi < data.length) {
27
25
  const verticesPerPolygon = data[ci]
@@ -36,8 +34,8 @@ const fromCompactBinary = (data) => {
36
34
  }
37
35
 
38
36
  // transfer known properities, i.e. color
39
- if (data[18] >= 0) {
40
- created.color = [data[18], data[19], data[20], data[21]]
37
+ if (data[17] >= 0) {
38
+ created.color = [data[17], data[18], data[19], data[20]]
41
39
  }
42
40
  // TODO: how about custom properties or fields ?
43
41
  return created
@@ -12,7 +12,6 @@ test('toCompactBinary: converts geom3 (default)', (t) => {
12
12
  0, 1, 0, 0,
13
13
  0, 0, 1, 0,
14
14
  0, 0, 0, 1,
15
- 0, // isRetesselated flag
16
15
  -1, -1, -1, -1, // color
17
16
  0 // number of vertices
18
17
  ]
@@ -32,7 +31,6 @@ test('toCompactBinary: converts geom3 into a compact form', (t) => {
32
31
  0, 1, 0, 0,
33
32
  0, 0, 1, 0,
34
33
  0, 0, 0, 1,
35
- 0, // isRetesselated flag
36
34
  -1, -1, -1, -1, // color
37
35
  7, // number of vertices
38
36
  3, // number of vertices per polygon (2)
@@ -59,7 +57,6 @@ test('toCompactBinary: converts geom3 into a compact form', (t) => {
59
57
  0, 1, 0, 0,
60
58
  0, 0, 1, 0,
61
59
  0, 0, 0, 1,
62
- 0, // isRetesselated flag
63
60
  1, 2, 3, 4, // color
64
61
  7, // number of vertices
65
62
  3, // number of vertices per polygon (2)
@@ -84,7 +81,6 @@ test('fromCompactBinary: convert a compact form into a geom3', (t) => {
84
81
  0, 1, 0, 0,
85
82
  0, 0, 1, 0,
86
83
  0, 0, 0, 1,
87
- 0, // isRetesselated flag
88
84
  -1, -1, -1, -1, // color
89
85
  0 // number of vertices
90
86
  ]
@@ -99,7 +95,6 @@ test('fromCompactBinary: convert a compact form into a geom3', (t) => {
99
95
  0, 1, 0, 0,
100
96
  0, 0, 1, 0,
101
97
  0, 0, 0, 1,
102
- 0, // isRetesselated flag
103
98
  -1, -1, -1, -1, // color
104
99
  7, // number of vertices
105
100
  3, // number of vertices per polygon (2)
@@ -125,7 +120,6 @@ test('fromCompactBinary: convert a compact form into a geom3', (t) => {
125
120
  0, 1, 0, 0,
126
121
  0, 0, 1, 0,
127
122
  0, 0, 0, 1,
128
- 0, // isRetesselated flag
129
123
  4, 5, 6, 7, // color
130
124
  7, // number of vertices
131
125
  3, // number of vertices per polygon (2)
@@ -7,7 +7,6 @@ const { comparePolygons, compareVectors } = require('../../../test/helpers/')
7
7
  test('invert: Creates a invert on an empty geom3', (t) => {
8
8
  const expected = {
9
9
  polygons: [],
10
- isRetesselated: false,
11
10
  transforms: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
12
11
  }
13
12
  const geometry = create()
@@ -22,7 +21,6 @@ test('invert: Creates a invert of a populated geom3', (t) => {
22
21
  polygons: [
23
22
  { vertices: [[1, 0, 1], [1, 0, 0], [0, 0, 0]] }
24
23
  ],
25
- isRetesselated: false,
26
24
  transforms: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
27
25
  }
28
26
  const geometry = fromPoints(points)
@@ -16,8 +16,8 @@ const toCompactBinary = (geom) => {
16
16
  if (geom.color) color = geom.color
17
17
 
18
18
  // FIXME why Float32Array?
19
- const compacted = new Float32Array(1 + 16 + 1 + 4 + 1 + numberOfPolygons + (numberOfVertices * 3))
20
- // type + transforms + isRetesselated + color + numberOfPolygons + numberOfVerticesPerPolygon[] + vertices data[]
19
+ const compacted = new Float32Array(1 + 16 + 4 + 1 + numberOfPolygons + (numberOfVertices * 3))
20
+ // type + transforms + color + numberOfPolygons + numberOfVerticesPerPolygon[] + vertices data[]
21
21
 
22
22
  compacted[0] = 1 // type code: 0 => geom2, 1 => geom3 , 2 => path2
23
23
 
@@ -38,16 +38,14 @@ const toCompactBinary = (geom) => {
38
38
  compacted[15] = transforms[14]
39
39
  compacted[16] = transforms[15]
40
40
 
41
- compacted[17] = geom.isRetesselated ? 1 : 0
41
+ compacted[17] = color[0]
42
+ compacted[18] = color[1]
43
+ compacted[19] = color[2]
44
+ compacted[20] = color[3]
42
45
 
43
- compacted[18] = color[0]
44
- compacted[19] = color[1]
45
- compacted[20] = color[2]
46
- compacted[21] = color[3]
46
+ compacted[21] = numberOfVertices
47
47
 
48
- compacted[22] = numberOfVertices
49
-
50
- let ci = 23
48
+ let ci = 22
51
49
  let vi = ci + numberOfPolygons
52
50
  polygons.forEach((polygon) => {
53
51
  const points = poly3.toPoints(polygon)
@@ -1,7 +1,5 @@
1
1
  const mat4 = require('../../maths/mat4')
2
2
 
3
- const create = require('./create')
4
-
5
3
  /**
6
4
  * Transform the given geometry using the given matrix.
7
5
  * This is a lazy transform of the polygons, as this function only adjusts the transforms.
@@ -15,11 +13,8 @@ const create = require('./create')
15
13
  * let newgeometry = transform(fromXRotation(degToRad(90)), geometry)
16
14
  */
17
15
  const transform = (matrix, geometry) => {
18
- const newgeometry = create(geometry.polygons) // reuse the polygons
19
- newgeometry.isRetesselated = geometry.isRetesselated
20
-
21
- mat4.multiply(newgeometry.transforms, matrix, geometry.transforms)
22
- return newgeometry
16
+ const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)
17
+ return Object.assign({}, geometry, { transforms })
23
18
  }
24
19
 
25
20
  module.exports = transform
@@ -18,7 +18,6 @@ test('transform: Adjusts the transforms of a populated geom3', (t) => {
18
18
  polygons: [
19
19
  { vertices: [[0, 0, 0], [1, 0, 0], [1, 0, 1]] }
20
20
  ],
21
- isRetesselated: false,
22
21
  transforms: [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
23
22
  }
24
23
  const geometry = fromPoints(points)
@@ -6,6 +6,5 @@ export default Geom3
6
6
 
7
7
  declare interface Geom3 extends Colored {
8
8
  polygons: Array<Poly3>
9
- isRetesselated: boolean
10
9
  transforms: Mat4
11
10
  }
@@ -13,7 +13,7 @@ const applyTransforms = (geometry) => {
13
13
  if (mat4.isIdentity(geometry.transforms)) return geometry
14
14
 
15
15
  geometry.points = geometry.points.map((point) => vec2.transform(vec2.create(), point, geometry.transforms))
16
- mat4.identity(geometry.transforms)
16
+ geometry.transforms = mat4.create()
17
17
  return geometry
18
18
  }
19
19
 
@@ -1,20 +1,9 @@
1
- const mat4 = require('../../maths/mat4')
2
- const vec2 = require('../../maths/vec2')
3
-
4
- const create = require('./create')
5
-
6
1
  /**
7
- * Performs a deep clone of the give geometry.
2
+ * Performs a shallow clone of the give geometry.
8
3
  * @param {path2} geometry - the geometry to clone
9
4
  * @returns {path2} a new path
10
5
  * @alias module:modeling/geometries/path2.clone
11
6
  */
12
- const clone = (geometry) => {
13
- const out = create()
14
- out.points = geometry.points.map((point) => vec2.clone(point))
15
- out.isClosed = geometry.isClosed
16
- out.transforms = mat4.clone(geometry.transforms)
17
- return out
18
- }
7
+ const clone = (geometry) => Object.assign({}, geometry)
19
8
 
20
9
  module.exports = clone
@@ -1,7 +1,5 @@
1
1
  const mat4 = require('../../maths/mat4')
2
2
 
3
- const create = require('./create')
4
-
5
3
  /**
6
4
  * Transform the given geometry using the given matrix.
7
5
  * This is a lazy transform of the points, as this function only adjusts the transforms.
@@ -15,11 +13,8 @@ const create = require('./create')
15
13
  * let newpath = transform(fromZRotation(Math.PI / 4), path)
16
14
  */
17
15
  const transform = (matrix, geometry) => {
18
- const newgeometry = create(geometry.points) // reuse the points
19
- newgeometry.isClosed = geometry.isClosed
20
-
21
- mat4.multiply(newgeometry.transforms, matrix, geometry.transforms)
22
- return newgeometry
16
+ const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)
17
+ return Object.assign({}, geometry, { transforms })
23
18
  }
24
19
 
25
20
  module.exports = transform
@@ -13,7 +13,7 @@ const areVerticesConvex = (vertices) => {
13
13
  const numvertices = vertices.length
14
14
  if (numvertices > 2) {
15
15
  // note: plane ~= normal point
16
- const normal = plane.fromPoints(plane.create(), vertices[0], vertices[1], vertices[2])
16
+ const normal = plane.fromPoints(plane.create(), ...vertices)
17
17
  let prevprevpos = vertices[numvertices - 2]
18
18
  let prevpos = vertices[numvertices - 1]
19
19
  for (let i = 0; i < numvertices; i++) {
@@ -1,4 +1,4 @@
1
- const vec3 = require('../../maths/vec3')
1
+ const plane = require('./plane')
2
2
 
3
3
  /**
4
4
  * Measure the area of the given polygon.
@@ -14,19 +14,18 @@ const measureArea = (poly3) => {
14
14
  }
15
15
  const vertices = poly3.vertices
16
16
 
17
- // calculate a real normal
18
- const a = vertices[0]
19
- const b = vertices[1]
20
- const c = vertices[2]
21
- const ba = vec3.subtract(vec3.create(), b, a)
22
- const ca = vec3.subtract(vec3.create(), c, a)
23
- const normal = vec3.cross(ba, ba, ca)
17
+ // calculate a normal vector
18
+ const normal = plane(poly3)
24
19
 
25
- // determin direction of projection
20
+ // determine direction of projection
26
21
  const ax = Math.abs(normal[0])
27
22
  const ay = Math.abs(normal[1])
28
23
  const az = Math.abs(normal[2])
29
- const an = Math.sqrt((ax * ax) + (ay * ay) + (az * az)) // length of normal
24
+
25
+ if (ax + ay + az === 0) {
26
+ // normal does not exist
27
+ return 0
28
+ }
30
29
 
31
30
  let coord = 3 // ignore Z coordinates
32
31
  if ((ax > ay) && (ax > az)) {
@@ -50,7 +49,7 @@ const measureArea = (poly3) => {
50
49
  }
51
50
  area += (vertices[0][1] * (vertices[1][2] - vertices[n - 1][2]))
52
51
  // scale to get area
53
- area *= (an / (2 * normal[0]))
52
+ area /= (2 * normal[0])
54
53
  break
55
54
 
56
55
  case 2: // ignore Y coordinates
@@ -62,7 +61,7 @@ const measureArea = (poly3) => {
62
61
  }
63
62
  area += (vertices[0][2] * (vertices[1][0] - vertices[n - 1][0]))
64
63
  // scale to get area
65
- area *= (an / (2 * normal[1]))
64
+ area /= (2 * normal[1])
66
65
  break
67
66
 
68
67
  case 3: // ignore Z coordinates
@@ -75,7 +74,7 @@ const measureArea = (poly3) => {
75
74
  }
76
75
  area += (vertices[0][0] * (vertices[1][1] - vertices[n - 1][1]))
77
76
  // scale to get area
78
- area *= (an / (2 * normal[2]))
77
+ area /= (2 * normal[2])
79
78
  break
80
79
  }
81
80
  return area
@@ -37,6 +37,21 @@ test('poly3: measureArea() should return correct values', (t) => {
37
37
  let ret4 = measureArea(ply4)
38
38
  t.is(ret4, 19.5)
39
39
 
40
+ // colinear vertices non-zero area
41
+ const ply5 = fromPoints([[0, 0, 0], [1, 0, 0], [2, 0, 0], [0, 1, 0]])
42
+ const ret5 = measureArea(ply5)
43
+ t.is(ret5, 1)
44
+
45
+ // colinear vertices empty area
46
+ const ply6 = fromPoints([[0, 0, 0], [1, 0, 0], [2, 0, 0]])
47
+ const ret6 = measureArea(ply6)
48
+ t.is(ret6, 0)
49
+
50
+ // duplicate vertices empty area
51
+ const ply7 = fromPoints([[0, 0, 0], [0, 0, 0], [0, 0, 0]])
52
+ const ret7 = measureArea(ply7)
53
+ t.is(ret7, 0)
54
+
40
55
  // rotated to various angles
41
56
  let rotation = mat4.fromZRotation(mat4.create(), (45 * 0.017453292519943295))
42
57
  ply1 = transform(rotation, ply1)
@@ -2,8 +2,7 @@ const mplane = require('../../maths/plane/')
2
2
 
3
3
  const plane = (polygon) => {
4
4
  if (!polygon.plane) {
5
- const vertices = polygon.vertices
6
- polygon.plane = mplane.fromPoints(mplane.create(), vertices[0], vertices[1], vertices[2])
5
+ polygon.plane = mplane.fromPoints(mplane.create(), ...polygon.vertices)
7
6
  }
8
7
  return polygon.plane
9
8
  }
@@ -21,6 +21,7 @@ module.exports = {
21
21
  fromZRotation: require('./fromZRotation'),
22
22
  identity: require('./identity'),
23
23
  isIdentity: require('./isIdentity'),
24
+ isOnlyTransformScale: require('./isOnlyTransformScale'),
24
25
  isMirroring: require('./isMirroring'),
25
26
  mirrorByPlane: require('./mirrorByPlane'),
26
27
  multiply: require('./multiply'),
@@ -0,0 +1,21 @@
1
+
2
+ /**
3
+ * Determine whether the given matrix is translate+scale.
4
+ * this code returns true for 180 rotation as it can be interpreted as scale (-1,-1)
5
+ *
6
+ * this method is primarily useful for measureBoundingBox
7
+ *
8
+ */
9
+ const isOnlyTransformScale = (matrix) => (
10
+
11
+ // TODO check if it is worth the effort to add recognition of 90 deg rotations
12
+
13
+ isZero(matrix[1]) && isZero(matrix[2]) && isZero(matrix[3]) &&
14
+ isZero(matrix[4]) && isZero(matrix[6]) && isZero(matrix[7]) &&
15
+ isZero(matrix[8]) && isZero(matrix[9]) && isZero(matrix[11]) &&
16
+ matrix[15] === 1
17
+ )
18
+
19
+ const isZero = (num) => Math.abs(num) < Number.EPSILON
20
+
21
+ module.exports = isOnlyTransformScale
@@ -0,0 +1,27 @@
1
+ const test = require('ava')
2
+
3
+ const { isOnlyTransformScale, create, fromTranslation, fromTaitBryanRotation, fromScaling, invert, multiply } = require('./index')
4
+
5
+ test('mat4: isOnlyTransformScale() should return true for right angles', (t) => {
6
+ let someRotation = fromTaitBryanRotation(create(), Math.PI, 0, 0)
7
+ t.true(isOnlyTransformScale(someRotation))
8
+ t.true(isOnlyTransformScale(invert(create(), someRotation)))
9
+
10
+ someRotation = fromTaitBryanRotation(create(), 0, 0, 0)
11
+ t.true(isOnlyTransformScale(someRotation))
12
+ })
13
+
14
+ test('mat4: isOnlyTransformScale() should return correct values', (t) => {
15
+ const identity = create() // identity matrix
16
+ t.true(isOnlyTransformScale(identity))
17
+
18
+ const someTranslation = fromTranslation(create(), [5, 5, 5])
19
+ t.true(isOnlyTransformScale(someTranslation))
20
+
21
+ const someScaling = fromScaling(create(), [5, 5, 5])
22
+ t.true(isOnlyTransformScale(someScaling))
23
+ t.true(isOnlyTransformScale(invert(create(), someScaling)))
24
+
25
+ const combined = multiply(create(), someTranslation, someScaling)
26
+ t.true(isOnlyTransformScale(combined))
27
+ })
@@ -10,17 +10,39 @@ const vec3 = require('../vec3')
10
10
  * @returns {plane} out
11
11
  * @alias module:modeling/maths/plane.fromPoints
12
12
  */
13
- const fromPoints = (out, a, b, c) => {
14
- const ba = vec3.subtract(vec3.create(), b, a)
15
- const ca = vec3.subtract(vec3.create(), c, a)
16
- vec3.cross(ba, ba, ca)
17
- vec3.normalize(ba, ba) // normal part
18
- const w = vec3.dot(ba, a)
13
+ const fromPoints = (out, ...vertices) => {
14
+ const len = vertices.length
19
15
 
20
- out[0] = ba[0]
21
- out[1] = ba[1]
22
- out[2] = ba[2]
23
- out[3] = w
16
+ // Calculate normal vector for a single vertex
17
+ // Inline to avoid allocations
18
+ const ba = vec3.create()
19
+ const ca = vec3.create()
20
+ const vertexNormal = (index) => {
21
+ const a = vertices[index]
22
+ const b = vertices[(index + 1) % len]
23
+ const c = vertices[(index + 2) % len]
24
+ vec3.subtract(ba, b, a) // ba = b - a
25
+ vec3.subtract(ca, c, a) // ca = c - a
26
+ vec3.cross(ba, ba, ca) // ba = ba x ca
27
+ vec3.normalize(ba, ba)
28
+ return ba
29
+ }
30
+
31
+ out[0] = 0
32
+ out[1] = 0
33
+ out[2] = 0
34
+ if (len === 3) {
35
+ // optimization for triangles, which are always coplanar
36
+ vec3.copy(out, vertexNormal(0))
37
+ } else {
38
+ // sum of vertex normals
39
+ vertices.forEach((v, i) => {
40
+ vec3.add(out, out, vertexNormal(i))
41
+ })
42
+ // renormalize normal vector
43
+ vec3.normalize(out, out)
44
+ }
45
+ out[3] = vec3.dot(out, vertices[0])
24
46
  return out
25
47
  }
26
48