@jscad/modeling 3.0.4-alpha.0 → 3.0.6-alpha.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 (116) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/jscad-modeling.es.js +2 -2
  3. package/dist/jscad-modeling.min.js +2 -2
  4. package/package.json +4 -4
  5. package/rollup.config.js +1 -1
  6. package/src/colors/colorize.js +17 -1
  7. package/src/colors/hsvToRgb.js +1 -0
  8. package/src/colors/rgbToHsl.js +3 -1
  9. package/src/colors/rgbToHsv.js +1 -0
  10. package/src/curves/bezier/create.js +1 -1
  11. package/src/geometries/geom2/fromPoints.d.ts +4 -0
  12. package/src/geometries/geom2/fromPoints.js +28 -0
  13. package/src/geometries/geom2/fromPoints.test.js +22 -0
  14. package/src/geometries/geom2/index.d.ts +1 -0
  15. package/src/geometries/geom2/index.js +1 -0
  16. package/src/geometries/geom2/transform.js +1 -0
  17. package/src/geometries/geom3/transform.js +1 -0
  18. package/src/geometries/geom3/validate.js +11 -1
  19. package/src/geometries/path2/appendArc.js +19 -25
  20. package/src/geometries/path2/appendBezier.js +8 -11
  21. package/src/geometries/path2/appendPoints.js +21 -3
  22. package/src/geometries/path2/appendPoints.test.js +16 -4
  23. package/src/geometries/path2/close.js +7 -10
  24. package/src/geometries/path2/concat.js +2 -2
  25. package/src/geometries/path2/fromPoints.js +7 -7
  26. package/src/geometries/path2/reverse.js +3 -3
  27. package/src/geometries/path2/transform.js +1 -0
  28. package/src/geometries/path2/validate.js +2 -2
  29. package/src/geometries/path3/clone.d.ts +3 -0
  30. package/src/geometries/path3/clone.js +11 -0
  31. package/src/geometries/path3/close.js +7 -8
  32. package/src/geometries/path3/index.d.ts +1 -0
  33. package/src/geometries/path3/index.js +1 -0
  34. package/src/geometries/path3/transform.js +1 -0
  35. package/src/geometries/poly2/type.d.ts +1 -5
  36. package/src/geometries/slice/earcut/index.js +4 -2
  37. package/src/geometries/slice/earcut/linkedPolygon.js +4 -2
  38. package/src/geometries/slice/fromOutlines.d.ts +5 -0
  39. package/src/geometries/slice/fromOutlines.js +16 -0
  40. package/src/geometries/slice/fromOutlines.test.js +17 -0
  41. package/src/geometries/slice/index.d.ts +1 -1
  42. package/src/geometries/slice/index.js +1 -1
  43. package/src/geometries/slice/transform.js +2 -1
  44. package/src/maths/line2/intersectPointOfLines.js +1 -4
  45. package/src/maths/plane/fromNormalAndPoint.js +4 -6
  46. package/src/maths/plane/fromPoints.js +8 -7
  47. package/src/maths/plane/fromPointsRandom.js +13 -13
  48. package/src/measurements/measureAggregateEpsilon.js +3 -1
  49. package/src/measurements/measureAggregateEpsilon.test.js +1 -1
  50. package/src/measurements/measureArea.js +6 -4
  51. package/src/measurements/measureArea.test.js +4 -1
  52. package/src/measurements/measureBoundingBox.js +16 -2
  53. package/src/measurements/measureBoundingBox.test.js +4 -1
  54. package/src/measurements/measureBoundingSphere.js +38 -29
  55. package/src/measurements/measureBoundingSphere.test.js +4 -1
  56. package/src/measurements/measureCenterOfMass.js +3 -2
  57. package/src/measurements/measureEpsilon.js +4 -2
  58. package/src/operations/booleans/martinez/computeFields.js +6 -0
  59. package/src/operations/booleans/martinez/divideSegment.js +2 -1
  60. package/src/operations/booleans/martinez/fillQueue.js +3 -1
  61. package/src/operations/booleans/martinez/subdivideSegments.js +5 -4
  62. package/src/operations/booleans/subtractGeom3Sub.js +1 -1
  63. package/src/operations/booleans/trees/PolygonTreeNode.js +3 -0
  64. package/src/operations/booleans/trees/splitPolygonByPlane.d.ts +1 -3
  65. package/src/operations/booleans/union.js +1 -1
  66. package/src/operations/extrusions/extrudeFromSlices.js +1 -1
  67. package/src/operations/extrusions/extrudeFromSlices.test.js +1 -1
  68. package/src/operations/extrusions/extrudeHelical.js +2 -1
  69. package/src/operations/extrusions/extrudeLinear.js +1 -1
  70. package/src/operations/extrusions/extrudeLinearGeom2.js +12 -8
  71. package/src/operations/extrusions/extrudeRotate.js +1 -1
  72. package/src/operations/hulls/hull.js +3 -2
  73. package/src/operations/hulls/quickhull/Face.js +8 -3
  74. package/src/operations/hulls/quickhull/QuickHull.js +10 -5
  75. package/src/operations/hulls/quickhull/VertexList.js +2 -1
  76. package/src/operations/hulls/toUniquePoints.js +3 -0
  77. package/src/operations/modifiers/generalize.js +9 -2
  78. package/src/operations/modifiers/reTesselateCoplanarPolygons.js +2 -2
  79. package/src/operations/modifiers/reTesselateCoplanarPolygons.test.js +29 -3
  80. package/src/operations/modifiers/retessellate.js +1 -0
  81. package/src/operations/modifiers/snap.js +22 -3
  82. package/src/operations/modifiers/snap.test.js +12 -2
  83. package/src/operations/offsets/offsetGeom3.test.js +2 -2
  84. package/src/operations/transforms/align.js +2 -1
  85. package/src/operations/transforms/align.test.js +1 -1
  86. package/src/operations/transforms/mirror.js +6 -2
  87. package/src/operations/transforms/rotate.js +6 -2
  88. package/src/operations/transforms/scale.js +6 -2
  89. package/src/operations/transforms/transform.js +6 -2
  90. package/src/operations/transforms/transform.test.js +16 -5
  91. package/src/operations/transforms/translate.js +12 -4
  92. package/src/primitives/arc.js +14 -14
  93. package/src/primitives/circle.js +10 -9
  94. package/src/primitives/cube.js +5 -6
  95. package/src/primitives/cuboid.js +6 -6
  96. package/src/primitives/cylinder.js +8 -8
  97. package/src/primitives/cylinderElliptic.js +11 -11
  98. package/src/primitives/ellipse.js +12 -12
  99. package/src/primitives/ellipsoid.js +8 -8
  100. package/src/primitives/geodesicSphere.js +6 -6
  101. package/src/primitives/line.js +2 -0
  102. package/src/primitives/polygon.js +6 -7
  103. package/src/primitives/polyhedron.js +7 -8
  104. package/src/primitives/rectangle.js +6 -6
  105. package/src/primitives/roundedCuboid.js +8 -8
  106. package/src/primitives/roundedCylinder.js +9 -9
  107. package/src/primitives/roundedRectangle.js +8 -8
  108. package/src/primitives/sphere.js +7 -8
  109. package/src/primitives/square.js +6 -6
  110. package/src/primitives/star.js +12 -14
  111. package/src/primitives/torus.js +11 -11
  112. package/src/primitives/triangle.js +7 -6
  113. package/src/utils/areAllShapesTheSameType.js +4 -0
  114. package/test/helpers/nearlyEqual.js +1 -0
  115. package/src/geometries/slice/fromGeom2.d.ts +0 -5
  116. package/src/geometries/slice/fromGeom2.js +0 -17
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jscad/modeling",
3
- "version": "3.0.4-alpha.0",
3
+ "version": "3.0.6-alpha.0",
4
4
  "description": "Constructive Solid Geometry (CSG) Library for JSCAD",
5
5
  "homepage": "https://openjscad.xyz/",
6
6
  "repository": "https://github.com/jscad/OpenJSCAD.org",
@@ -11,10 +11,10 @@
11
11
  "module": "dist/jscad-modeling.es.js",
12
12
  "scripts": {
13
13
  "build": "rollup --config",
14
- "coverage": "c8 --all --reporter=html --reporter=text pnpm test",
14
+ "coverage": "c8 --all --reporter=html --reporter=text npm test",
15
15
  "test": "ava 'src/**/*.test.js' --verbose --timeout 2m",
16
16
  "test:tsd": "tsd",
17
- "version": "pnpm run build && git add dist"
17
+ "version": "npm run build && git add dist"
18
18
  },
19
19
  "contributors": [
20
20
  {
@@ -63,5 +63,5 @@
63
63
  "c8": "^10.1.0",
64
64
  "rollup": "^4.52.0"
65
65
  },
66
- "gitHead": "1de52a2b7b6bd31134fd0b72a2842f31ecf8f237"
66
+ "gitHead": "201d3987b309fd1f383b10b5555dd0a2765cad43"
67
67
  }
package/rollup.config.js CHANGED
@@ -2,7 +2,7 @@ import * as fs from 'fs'
2
2
 
3
3
  import terser from '@rollup/plugin-terser'
4
4
 
5
- const {name, version, license} = JSON.parse(fs.readFileSync('package.json'))
5
+ const { name, version, license } = JSON.parse(fs.readFileSync('package.json'))
6
6
 
7
7
  export default {
8
8
  input: 'src/index.js',
@@ -1,6 +1,8 @@
1
1
  import * as geom2 from '../geometries/geom2/index.js'
2
2
  import * as geom3 from '../geometries/geom3/index.js'
3
3
  import * as path2 from '../geometries/path2/index.js'
4
+ import * as path3 from '../geometries/path3/index.js'
5
+ import * as slice from '../geometries/slice/index.js'
4
6
  import * as poly3 from '../geometries/poly3/index.js'
5
7
 
6
8
  const colorGeom2 = (color, object) => {
@@ -21,6 +23,18 @@ const colorPath2 = (color, object) => {
21
23
  return newPath2
22
24
  }
23
25
 
26
+ const colorPath3 = (color, object) => {
27
+ const newPath3 = path3.clone(object)
28
+ newPath3.color = color
29
+ return newPath3
30
+ }
31
+
32
+ const colorSlice = (color, object) => {
33
+ const newSlice = slice.clone(object)
34
+ newSlice.color = color
35
+ return newSlice
36
+ }
37
+
24
38
  const colorPoly3 = (color, object) => {
25
39
  const newPoly = poly3.clone(object)
26
40
  newPoly.color = color
@@ -46,9 +60,11 @@ export const colorize = (color, ...objects) => {
46
60
  if (color.length === 3) color = [color[0], color[1], color[2], 1.0] // add alpha
47
61
 
48
62
  const results = objects.map((object) => {
49
- if (geom2.isA(object)) return colorGeom2(color, object)
50
63
  if (geom3.isA(object)) return colorGeom3(color, object)
64
+ if (geom2.isA(object)) return colorGeom2(color, object)
51
65
  if (path2.isA(object)) return colorPath2(color, object)
66
+ if (path3.isA(object)) return colorPath3(color, object)
67
+ if (slice.isA(object)) return colorSlice(color, object)
52
68
  if (poly3.isA(object)) return colorPoly3(color, object)
53
69
  if (Array.isArray(object)) return colorize(color, ...object)
54
70
 
@@ -56,6 +56,7 @@ export const hsvToRgb = (...values) => {
56
56
  b = v
57
57
  break
58
58
  case 5:
59
+ default:
59
60
  r = v
60
61
  g = p
61
62
  b = q
@@ -24,7 +24,8 @@ export const rgbToHsl = (...values) => {
24
24
  const l = (max + min) / 2
25
25
 
26
26
  if (max === min) {
27
- h = s = 0 // achromatic
27
+ h = 0
28
+ s = 0 // achromatic
28
29
  } else {
29
30
  const d = max - min
30
31
  s = l > 0.5 ? d / (2 - max - min) : d / (max + min)
@@ -36,6 +37,7 @@ export const rgbToHsl = (...values) => {
36
37
  h = (b - r) / d + 2
37
38
  break
38
39
  case b:
40
+ default:
39
41
  h = (r - g) / d + 4
40
42
  break
41
43
  }
@@ -35,6 +35,7 @@ export const rgbToHsv = (...values) => {
35
35
  h = (b - r) / d + 2
36
36
  break
37
37
  case b:
38
+ default:
38
39
  h = (r - g) / d + 4
39
40
  break
40
41
  }
@@ -51,7 +51,7 @@ const getPointType = function (points) {
51
51
  })
52
52
  pType = 'float_' + point.length
53
53
  } else throw new Error('Bezier points must all be numbers or arrays of number.')
54
- if (firstPointType == null) {
54
+ if (firstPointType === null) {
55
55
  firstPointType = pType
56
56
  } else {
57
57
  if (firstPointType !== pType) {
@@ -0,0 +1,4 @@
1
+ import type { Geom2 } from './type.d.ts'
2
+ import type { Vec2 } from '../../maths/vec2/type.d.ts'
3
+
4
+ declare function fromPoints(points: Array<Vec2>): Geom2
@@ -0,0 +1,28 @@
1
+ import * as vec2 from '../../maths/vec2/index.js'
2
+
3
+ import { create } from './create.js'
4
+
5
+ /**
6
+ * Create a new 2D geometry from the given points.
7
+ *
8
+ * The direction (rotation) of the points is not relevant,
9
+ * as the points can define a convex or a concave polygon.
10
+ *
11
+ * The geometry must not self intersect, i.e. the sides cannot cross.
12
+ * @param {Array} points - list of points in 2D space
13
+ * @returns {geom2} a new geometry
14
+ * @alias module:modeling/geom2.fromPoints
15
+ */
16
+ export const fromPoints = (points) => {
17
+ if (!Array.isArray(points)) {
18
+ throw new Error('the given points must be an array')
19
+ }
20
+ const length = points.length
21
+ if (length < 3) {
22
+ throw new Error('the given points must define a closed geometry with three or more points')
23
+ }
24
+ // adjust length if the given points are closed by the same point
25
+ if (vec2.equals(points[0], points[length - 1])) points = points.slice(0, -1)
26
+
27
+ return create([points])
28
+ }
@@ -0,0 +1,22 @@
1
+ import test from 'ava'
2
+
3
+ import { fromPoints } from './index.js'
4
+
5
+ test('fromPoints: creates populated geom2', (t) => {
6
+ const points = [[0, 0], [1, 0], [0, 1]]
7
+ const expected = {
8
+ outlines: [[[0, 0], [1, 0], [0, 1]]],
9
+ transforms: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]
10
+ }
11
+ t.deepEqual(fromPoints(points), expected)
12
+
13
+ const points2 = [[0, 0], [1, 0], [0, 1], [0, 0]]
14
+ t.deepEqual(fromPoints(points2), expected)
15
+ })
16
+
17
+ test('fromPoints: throws for improper points', (t) => {
18
+ t.throws(() => fromPoints(), { instanceOf: Error })
19
+ t.throws(() => fromPoints(0, 0), { instanceOf: Error })
20
+ t.throws(() => fromPoints([]), { instanceOf: Error })
21
+ t.throws(() => fromPoints([[0, 0]]), { instanceOf: Error })
22
+ })
@@ -1,5 +1,6 @@
1
1
  export { clone } from './clone.js'
2
2
  export { create } from './create.js'
3
+ export { fromPoints } from './fromPoints.js'
3
4
  export { fromSides } from './fromSides.js'
4
5
  export { isA } from './isA.js'
5
6
  export { reverse } from './reverse.js'
@@ -26,6 +26,7 @@
26
26
  */
27
27
  export { clone } from './clone.js'
28
28
  export { create } from './create.js'
29
+ export { fromPoints } from './fromPoints.js'
29
30
  export { fromSides } from './fromSides.js'
30
31
  export { isA } from './isA.js'
31
32
  export { reverse } from './reverse.js'
@@ -18,6 +18,7 @@ import { reverse } from './reverse.js'
18
18
  */
19
19
  export const transform = (matrix, geometry) => {
20
20
  const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)
21
+ // use Object.assign in order to assign EXTRA attributes like color, name, etc
21
22
  const transformed = Object.assign({}, geometry, { transforms })
22
23
  // determine if the transform is mirroring in 2D
23
24
  if (matrix[0] * matrix[5] - matrix[4] * matrix[1] < 0) {
@@ -17,5 +17,6 @@ import * as mat4 from '../../maths/mat4/index.js'
17
17
  */
18
18
  export const transform = (matrix, geometry) => {
19
19
  const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)
20
+ // use Object.assign in order to assign EXTRA attributes like color, name, etc
20
21
  return Object.assign({}, geometry, { transforms })
21
22
  }
@@ -22,7 +22,17 @@ export const validate = (object) => {
22
22
  }
23
23
 
24
24
  // check polygons
25
- object.polygons.forEach(poly3.validate)
25
+ object.polygons.forEach((p, i) => {
26
+ if (p.vertices.length < 3) {
27
+ throw new Error(`invalid polygon ${i}: only ${p.vertices.length} vertices`)
28
+ }
29
+
30
+ try {
31
+ poly3.validate(p)
32
+ } catch (e) {
33
+ throw new Error(`invalid polygon ${i}: ${e}`)
34
+ }
35
+ })
26
36
  validateManifold(object)
27
37
 
28
38
  // check transforms
@@ -1,7 +1,7 @@
1
1
  import { TAU } from '../../maths/constants.js'
2
2
  import * as vec2 from '../../maths/vec2/index.js'
3
3
 
4
- import { fromPoints } from './fromPoints.js'
4
+ import { appendPoints } from './appendPoints.js'
5
5
  import { toPoints } from './toPoints.js'
6
6
 
7
7
  /**
@@ -27,19 +27,19 @@ import { toPoints } from './toPoints.js'
27
27
  * myShape = path2.appendArc({endpoint: [12.5, -22.96875], radius: [15, -19.6875]}, myShape);
28
28
  */
29
29
  export const appendArc = (options, geometry) => {
30
- const defaults = {
31
- radius: [0, 0], // X and Y radius
32
- xaxisRotation: 0,
33
- clockwise: false,
34
- large: false,
35
- segments: 16
36
- }
37
- let { endpoint, radius, xaxisRotation, clockwise, large, segments } = Object.assign({}, defaults, options)
30
+ const {
31
+ endpoint = null,
32
+ radius = [0, 0], // X and Y radius
33
+ xaxisRotation = 0,
34
+ clockwise = false,
35
+ large = false,
36
+ segments = 16
37
+ } = options
38
38
 
39
39
  // validate the given options
40
40
  if (!Array.isArray(endpoint)) throw new Error('endpoint must be an array of X and Y values')
41
- if (endpoint.length < 2) throw new Error('endpoint must contain X and Y values')
42
- endpoint = vec2.clone(endpoint)
41
+ if (endpoint.length !== 2) throw new Error('endpoint must contain X and Y values')
42
+ let xendPoint = vec2.clone(endpoint)
43
43
 
44
44
  if (!Array.isArray(radius)) throw new Error('radius must be an array of X and Y values')
45
45
  if (radius.length < 2) throw new Error('radius must contain X and Y values')
@@ -48,11 +48,6 @@ export const appendArc = (options, geometry) => {
48
48
 
49
49
  const decimals = 100000
50
50
 
51
- // validate the given geometry
52
- if (geometry.isClosed) {
53
- throw new Error('the given path cannot be closed')
54
- }
55
-
56
51
  const points = toPoints(geometry)
57
52
  if (points.length < 1) {
58
53
  throw new Error('the given path must contain one or more points (as the starting point for the arc)')
@@ -65,14 +60,14 @@ export const appendArc = (options, geometry) => {
65
60
  // round to precision in order to have determinate calculations
66
61
  xRadius = Math.round(xRadius * decimals) / decimals
67
62
  yRadius = Math.round(yRadius * decimals) / decimals
68
- endpoint = vec2.fromValues(Math.round(endpoint[0] * decimals) / decimals, Math.round(endpoint[1] * decimals) / decimals)
63
+ xendPoint = vec2.fromValues(Math.round(xendPoint[0] * decimals) / decimals, Math.round(xendPoint[1] * decimals) / decimals)
69
64
 
70
65
  const sweepFlag = !clockwise
71
- let newPoints = []
66
+ const newPoints = []
72
67
  if ((xRadius === 0) || (yRadius === 0)) {
73
68
  // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes:
74
69
  // If rx = 0 or ry = 0, then treat this as a straight line from (x1, y1) to (x2, y2) and stop
75
- newPoints.push(endpoint)
70
+ newPoints.push(xendPoint)
76
71
  } else {
77
72
  xRadius = Math.abs(xRadius)
78
73
  yRadius = Math.abs(yRadius)
@@ -81,7 +76,7 @@ export const appendArc = (options, geometry) => {
81
76
  const phi = xaxisRotation
82
77
  const cosPhi = Math.cos(phi)
83
78
  const sinPhi = Math.sin(phi)
84
- const minusHalfDistance = vec2.subtract(vec2.create(), startpoint, endpoint)
79
+ const minusHalfDistance = vec2.subtract(vec2.create(), startpoint, xendPoint)
85
80
  vec2.scale(minusHalfDistance, minusHalfDistance, 0.5)
86
81
  // F.6.5.1:
87
82
  // round to precision in order to have determinate calculations
@@ -106,7 +101,7 @@ export const appendArc = (options, geometry) => {
106
101
  vec2.scale(centerTranslated, centerTranslated, multiplier1)
107
102
  // F.6.5.3:
108
103
  let center = vec2.fromValues(cosPhi * centerTranslated[0] - sinPhi * centerTranslated[1], sinPhi * centerTranslated[0] + cosPhi * centerTranslated[1])
109
- center = vec2.add(center, center, vec2.scale(vec2.create(), vec2.add(vec2.create(), startpoint, endpoint), 0.5))
104
+ center = vec2.add(center, center, vec2.scale(vec2.create(), vec2.add(vec2.create(), startpoint, xendPoint), 0.5))
110
105
 
111
106
  // F.6.5.5:
112
107
  const vector1 = vec2.fromValues((startTranslated[0] - centerTranslated[0]) / xRadius, (startTranslated[1] - centerTranslated[1]) / yRadius)
@@ -134,9 +129,8 @@ export const appendArc = (options, geometry) => {
134
129
  newPoints.push(point)
135
130
  }
136
131
  // ensure end point is precisely what user gave as parameter
137
- if (numSteps) newPoints.push(options.endpoint)
132
+ if (numSteps) newPoints.push(endpoint)
138
133
  }
139
- newPoints = points.concat(newPoints)
140
- const result = fromPoints({}, newPoints)
141
- return result
134
+
135
+ return appendPoints(newPoints, geometry)
142
136
  }
@@ -1,6 +1,6 @@
1
1
  import { TAU } from '../../maths/constants.js'
2
2
  import * as vec2 from '../../maths/vec2/index.js'
3
- import * as vec3 from '../../maths/vec3/index.js' // FIXME why vec3?
3
+ import * as vec3 from '../../maths/vec3/index.js' // required due to vec2.cross()
4
4
 
5
5
  import { appendPoints } from './appendPoints.js'
6
6
  import { toPoints } from './toPoints.js'
@@ -27,10 +27,10 @@ import { toPoints } from './toPoints.js'
27
27
  * myShape = path2.appendBezier({controlPoints: [null, [25,-30],[40,-30],[40,-20]]}, myShape)
28
28
  */
29
29
  export const appendBezier = (options, geometry) => {
30
- const defaults = {
31
- segments: 16
32
- }
33
- let { controlPoints, segments } = Object.assign({}, defaults, options)
30
+ let {
31
+ controlPoints = null,
32
+ segments = 16
33
+ } = options
34
34
 
35
35
  // validate the given options
36
36
  if (!Array.isArray(controlPoints)) throw new Error('controlPoints must be an array of one or more points')
@@ -39,10 +39,6 @@ export const appendBezier = (options, geometry) => {
39
39
  if (segments < 4) throw new Error('segments must be four or more')
40
40
 
41
41
  // validate the given geometry
42
- if (geometry.isClosed) {
43
- throw new Error('the given geometry cannot be closed')
44
- }
45
-
46
42
  const points = toPoints(geometry)
47
43
  if (points.length < 1) {
48
44
  throw new Error('the given path must contain one or more points (as the starting point for the bezier curve)')
@@ -148,9 +144,10 @@ export const appendBezier = (options, geometry) => {
148
144
  }
149
145
 
150
146
  // append to the new points to the given path
151
- // but skip the first new point because it is identical to the last point in the given path
152
- newPoints.shift()
153
147
  const result = appendPoints(newPoints, geometry)
148
+
149
+ // add a hint for the next bezier
154
150
  result.lastBezierControlPoint = controlPoints[controlPoints.length - 2]
151
+
155
152
  return result
156
153
  }
@@ -1,5 +1,7 @@
1
- import { concat } from './concat.js'
2
- import { create } from './create.js'
1
+ import { equals } from '../../maths/vec2/equals.js'
2
+
3
+ import { fromPoints } from './fromPoints.js'
4
+ import { toPoints } from './toPoints.js'
3
5
 
4
6
  /**
5
7
  * Append the given list of points to the end of the given geometry.
@@ -12,4 +14,20 @@ import { create } from './create.js'
12
14
  * @example
13
15
  * let newPath = path2.appendPoints([[3, 4], [4, 5]], oldPath)
14
16
  */
15
- export const appendPoints = (points, geometry) => concat(geometry, create(points))
17
+ export const appendPoints = (points, geometry) => {
18
+ let originalPoints = toPoints(geometry)
19
+
20
+ if (geometry.isClosed && originalPoints.length > 1) {
21
+ // add the closing point to the originalPoints
22
+ originalPoints = originalPoints.slice()
23
+ originalPoints.push(originalPoints[0])
24
+ }
25
+
26
+ if (originalPoints.length > 0) {
27
+ // remove redundant points
28
+ const lastPoint = originalPoints[originalPoints.length - 1]
29
+ while (points.length > 0 && equals(points[0], lastPoint)) points.shift()
30
+ }
31
+
32
+ return fromPoints({}, originalPoints.concat(points))
33
+ }
@@ -12,10 +12,16 @@ test('appendPoints: appending to an empty path produces a new path with expected
12
12
 
13
13
  test('appendPoints: appending to a path produces a new path with expected points', (t) => {
14
14
  const p1 = fromPoints({}, [[1, 1], [2, 2]])
15
- const obs = appendPoints([[3, 3], [4, 4]], p1)
16
- const pts = toPoints(obs)
15
+ let obs = appendPoints([[3, 3], [4, 4]], p1)
16
+ let pts = toPoints(obs)
17
17
  t.not(p1, obs)
18
18
  t.is(pts.length, 4)
19
+
20
+ const p2 = fromPoints({ closed: true }, [[0, 0], [1, 0], [0, 1]])
21
+ obs = appendPoints([[0, -1], [0, -2]], p2)
22
+ pts = toPoints(obs)
23
+ t.not(p2, obs)
24
+ t.is(pts.length, 6)
19
25
  })
20
26
 
21
27
  test('appendPoints: appending empty points to a path produces a new path with expected points', (t) => {
@@ -28,8 +34,14 @@ test('appendPoints: appending empty points to a path produces a new path with ex
28
34
 
29
35
  test('appendPoints: appending same points to a path produces a new path with expected points', (t) => {
30
36
  const p1 = fromPoints({}, [[1, 1], [2, 2]])
31
- const obs = appendPoints([[2, 2], [3, 3]], p1)
32
- const pts = toPoints(obs)
37
+ let obs = appendPoints([[2, 2], [3, 3]], p1)
38
+ let pts = toPoints(obs)
33
39
  t.not(p1, obs)
34
40
  t.is(pts.length, 3)
41
+
42
+ const p2 = fromPoints({ closed: true }, [[0, 0], [1, 0], [0, 1]])
43
+ obs = appendPoints([[0, 0], [0, 0], [0, -1]], p2)
44
+ pts = toPoints(obs)
45
+ t.not(p2, obs)
46
+ t.is(pts.length, 5)
35
47
  })
@@ -1,8 +1,6 @@
1
1
  import { EPS } from '../../maths/constants.js'
2
2
 
3
- import * as vec2 from '../../maths/vec2/index.js'
4
-
5
- import { clone } from './clone.js'
3
+ import { distance } from '../../maths/vec2/distance.js'
6
4
 
7
5
  /**
8
6
  * Close the given geometry.
@@ -17,19 +15,18 @@ import { clone } from './clone.js'
17
15
  export const close = (geometry) => {
18
16
  if (geometry.isClosed) return geometry
19
17
 
20
- const cloned = clone(geometry)
21
- cloned.isClosed = true
22
-
23
- if (cloned.points.length > 1) {
18
+ const isClosed = true
19
+ const points = geometry.points.slice()
20
+ if (points.length > 1) {
24
21
  // make sure the paths are formed properly
25
- const points = cloned.points
26
22
  const p0 = points[0]
27
23
  let pn = points[points.length - 1]
28
- while (vec2.distance(p0, pn) < (EPS * EPS)) {
24
+ while (distance(p0, pn) < EPS) {
29
25
  points.pop()
30
26
  if (points.length === 1) break
31
27
  pn = points[points.length - 1]
32
28
  }
33
29
  }
34
- return cloned
30
+ // use Object.assign in order to assign EXTRA attributes like color, name, etc
31
+ return Object.assign({}, geometry, { isClosed, points })
35
32
  }
@@ -1,4 +1,4 @@
1
- import { equals } from '../../maths/vec2/index.js'
1
+ import { equals } from '../../maths/vec2/equals.js'
2
2
 
3
3
  import { fromPoints } from './fromPoints.js'
4
4
  import { toPoints } from './toPoints.js'
@@ -6,7 +6,7 @@ import { toPoints } from './toPoints.js'
6
6
  /**
7
7
  * Concatenate the given paths.
8
8
  *
9
- * If both contain the same point at the junction, merge it into one.
9
+ * If paths contain the same junction point, merge it into one.
10
10
  * A concatenation of zero paths is an empty, open path.
11
11
  * A concatenation of one closed path to a series of open paths produces a closed path.
12
12
  * A concatenation of a path to a closed path is an error.
@@ -1,6 +1,6 @@
1
1
  import { EPS } from '../../maths/constants.js'
2
2
 
3
- import * as vec2 from '../../maths/vec2/index.js'
3
+ import { distance } from '../../maths/vec2/distance.js'
4
4
 
5
5
  import { close } from './close.js'
6
6
  import { create } from './create.js'
@@ -18,20 +18,20 @@ import { create } from './create.js'
18
18
  * @alias module:modeling/path2.fromPoints
19
19
  *
20
20
  * @example
21
- * my newPath = path2.fromPoints({closed: true}, [[10, 10], [-10, 10]])
21
+ * let newPath = path2.fromPoints({closed: true}, [[10, 10], [-10, 10]])
22
22
  */
23
23
  export const fromPoints = (options, points) => {
24
- const defaults = { closed: false }
25
- let { closed } = Object.assign({}, defaults, options)
24
+ let {
25
+ closed = false
26
+ } = options
26
27
 
27
- let created = create()
28
- created.points = points.map((point) => vec2.clone(point))
28
+ let created = create(points.slice())
29
29
 
30
30
  // check if first and last points are equal
31
31
  if (created.points.length > 1) {
32
32
  const p0 = created.points[0]
33
33
  const pn = created.points[created.points.length - 1]
34
- if (vec2.distance(p0, pn) < (EPS * EPS)) {
34
+ if (distance(p0, pn) < EPS) {
35
35
  // and close automatically
36
36
  closed = true
37
37
  }
@@ -14,7 +14,7 @@ import { clone } from './clone.js'
14
14
  */
15
15
  export const reverse = (geometry) => {
16
16
  // NOTE: this only updates the order of the points
17
- const cloned = clone(geometry)
18
- cloned.points = geometry.points.slice().reverse()
19
- return cloned
17
+ const points = geometry.points.slice().reverse()
18
+ // use Object.assign in order to assign EXTRA attributes like color, name, etc
19
+ return Object.assign({}, geometry, { points })
20
20
  }
@@ -16,5 +16,6 @@ import * as mat4 from '../../maths/mat4/index.js'
16
16
  */
17
17
  export const transform = (matrix, geometry) => {
18
18
  const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)
19
+ // use Object.assign in order to assign EXTRA attributes like color, name, etc
19
20
  return Object.assign({}, geometry, { transforms })
20
21
  }
@@ -1,4 +1,4 @@
1
- import * as vec2 from '../../maths/vec2/index.js'
1
+ import { equals } from '../../maths/vec2/equals.js'
2
2
 
3
3
  import { isA } from './isA.js'
4
4
 
@@ -24,7 +24,7 @@ export const validate = (object) => {
24
24
  // check for duplicate points
25
25
  if (object.points.length > 1) {
26
26
  for (let i = 0; i < object.points.length; i++) {
27
- if (vec2.equals(object.points[i], object.points[(i + 1) % object.points.length])) {
27
+ if (equals(object.points[i], object.points[(i + 1) % object.points.length])) {
28
28
  throw new Error(`path2 has duplicate point ${object.points[i]}`)
29
29
  }
30
30
  }
@@ -0,0 +1,3 @@
1
+ import type { Path3 } from './type.d.ts'
2
+
3
+ export function clone(geometry: Path3): Path3
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Performs a shallow clone of the give geometry.
3
+ *
4
+ * @param {Path3} geometry - the geometry to clone
5
+ * @returns {Path3} a new path
6
+ * @alias module:modeling/path3.clone
7
+ *
8
+ * @example
9
+ * let newPath = path3.clone(oldPath)
10
+ */
11
+ export const clone = (geometry) => Object.assign({}, geometry)
@@ -1,6 +1,6 @@
1
1
  import { EPS } from '../../maths/constants.js'
2
2
 
3
- import * as vec3 from '../../maths/vec3/index.js'
3
+ import { distance } from '../../maths/vec3/distance.js'
4
4
 
5
5
  /**
6
6
  * Close the given geometry.
@@ -15,19 +15,18 @@ import * as vec3 from '../../maths/vec3/index.js'
15
15
  export const close = (geometry) => {
16
16
  if (geometry.isClosed) return geometry
17
17
 
18
- const cloned = Object.assign({}, geometry)
19
- cloned.isClosed = true
20
-
21
- if (cloned.vertices.length > 1) {
18
+ const isClosed = true
19
+ const vertices = geometry.vertices.slice()
20
+ if (vertices.length > 1) {
22
21
  // make sure the paths are formed properly
23
- const vertices = cloned.vertices
24
22
  const p0 = vertices[0]
25
23
  let pn = vertices[vertices.length - 1]
26
- while (vec3.distance(p0, pn) < (EPS * EPS)) {
24
+ while (distance(p0, pn) < EPS) {
27
25
  vertices.pop()
28
26
  if (vertices.length === 1) break
29
27
  pn = vertices[vertices.length - 1]
30
28
  }
31
29
  }
32
- return cloned
30
+ // use Object.assign in order to assign EXTRA attributes like color, name, etc
31
+ return Object.assign({}, geometry, { isClosed, vertices })
33
32
  }
@@ -1,3 +1,4 @@
1
+ export { clone } from './clone.js'
1
2
  export { close } from './close.js'
2
3
  export { concat } from './concat.js'
3
4
  export { create } from './create.js'
@@ -24,6 +24,7 @@
24
24
  * }
25
25
  */
26
26
 
27
+ export { clone } from './clone.js'
27
28
  export { close } from './close.js'
28
29
  export { concat } from './concat.js'
29
30
  export { create } from './create.js'
@@ -16,5 +16,6 @@ import * as mat4 from '../../maths/mat4/index.js'
16
16
  */
17
17
  export const transform = (matrix, geometry) => {
18
18
  const transforms = mat4.multiply(mat4.create(), matrix, geometry.transforms)
19
+ // use Object.assign in order to assign EXTRA attributes like color, name, etc
19
20
  return Object.assign({}, geometry, { transforms })
20
21
  }