From 6ac63a1c216c44dc7a744277fb1d484b909440bf Mon Sep 17 00:00:00 2001 From: Kenny Daniel Date: Sat, 3 Dec 2022 21:34:19 -0800 Subject: [PATCH 01/15] Geom2 no sides --- .../src/geometries/geom2/applyTransforms.js | 8 +- .../geometries/geom2/applyTransforms.test.js | 2 +- .../src/geometries/geom2/clone.test.js | 4 +- .../modeling/src/geometries/geom2/create.d.ts | 3 +- .../modeling/src/geometries/geom2/create.js | 18 +-- .../src/geometries/geom2/create.test.js | 8 +- .../src/geometries/geom2/fromCompactBinary.js | 18 ++- .../src/geometries/geom2/fromPoints.js | 11 +- .../src/geometries/geom2/fromPoints.test.js | 2 +- .../geom2/fromToCompactBinary.test.js | 50 +++----- packages/modeling/src/geometries/geom2/isA.js | 4 +- .../modeling/src/geometries/geom2/isA.test.js | 2 + .../modeling/src/geometries/geom2/reverse.js | 11 +- .../src/geometries/geom2/reverse.test.js | 6 +- .../src/geometries/geom2/toCompactBinary.js | 27 +++-- .../src/geometries/geom2/toOutlines.js | 108 +---------------- .../src/geometries/geom2/toOutlines.test.js | 113 +++++++----------- .../modeling/src/geometries/geom2/toPoints.js | 14 +-- .../modeling/src/geometries/geom2/toSides.js | 12 +- .../src/geometries/geom2/transform.test.js | 27 ++--- .../modeling/src/geometries/geom2/type.d.ts | 4 +- .../modeling/src/geometries/geom2/validate.js | 14 +-- .../modeling/src/geometries/poly2/create.js | 2 +- packages/modeling/src/primitives/polygon.js | 6 +- .../modeling/src/primitives/polygon.test.js | 6 +- 25 files changed, 169 insertions(+), 311 deletions(-) diff --git a/packages/modeling/src/geometries/geom2/applyTransforms.js b/packages/modeling/src/geometries/geom2/applyTransforms.js index eb1b70650..7bf450afd 100644 --- a/packages/modeling/src/geometries/geom2/applyTransforms.js +++ b/packages/modeling/src/geometries/geom2/applyTransforms.js @@ -14,10 +14,10 @@ export const applyTransforms = (geometry) => { if (mat4.isIdentity(geometry.transforms)) return geometry // apply transforms to each side - geometry.sides = geometry.sides.map((side) => { - const p0 = vec2.transform(vec2.create(), side[0], geometry.transforms) - const p1 = vec2.transform(vec2.create(), side[1], geometry.transforms) - return [p0, p1] + geometry.outlines = geometry.outlines.map((outline) => { + return outline.map((point) => { + return vec2.transform(vec2.create(), point, geometry.transforms) + }) }) geometry.transforms = mat4.create() return geometry diff --git a/packages/modeling/src/geometries/geom2/applyTransforms.test.js b/packages/modeling/src/geometries/geom2/applyTransforms.test.js index e2c66a0c6..e52e700c0 100644 --- a/packages/modeling/src/geometries/geom2/applyTransforms.test.js +++ b/packages/modeling/src/geometries/geom2/applyTransforms.test.js @@ -7,7 +7,7 @@ import applyTransforms from './applyTransforms.js' test('applyTransforms: Updates a populated geom2 with transformed sides', (t) => { const points = [[0, 0], [1, 0], [0, 1]] const expected = { - sides: [[[0, 1], [0, 0]], [[0, 0], [1, 0]], [[1, 0], [0, 1]]], + outlines: [[[0, 0], [1, 0], [0, 1]]], transforms: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] } const geometry = fromPoints(points) diff --git a/packages/modeling/src/geometries/geom2/clone.test.js b/packages/modeling/src/geometries/geom2/clone.test.js index d1f28cae4..3ceafb102 100644 --- a/packages/modeling/src/geometries/geom2/clone.test.js +++ b/packages/modeling/src/geometries/geom2/clone.test.js @@ -4,7 +4,7 @@ import { clone, create, fromPoints } from './index.js' test('clone: Creates a clone on an empty geom2', (t) => { const expected = { - sides: [], + outlines: [], transforms: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] } const geometry = create() @@ -16,7 +16,7 @@ test('clone: Creates a clone on an empty geom2', (t) => { test('clone: Creates a clone of a complete geom2', (t) => { const points = [[0, 0], [1, 0], [0, 1]] const expected = { - sides: [[[0, 1], [0, 0]], [[0, 0], [1, 0]], [[1, 0], [0, 1]]], + outlines: [[[0, 0], [1, 0], [0, 1]]], transforms: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] } const geometry = fromPoints(points) diff --git a/packages/modeling/src/geometries/geom2/create.d.ts b/packages/modeling/src/geometries/geom2/create.d.ts index 647c35b4b..28ef2a358 100644 --- a/packages/modeling/src/geometries/geom2/create.d.ts +++ b/packages/modeling/src/geometries/geom2/create.d.ts @@ -1,6 +1,7 @@ import Geom2 from './type' +import Poly2 from './type' import Vec2 from '../../maths/vec2/type' export default create -declare function create(sides?: Array<[Vec2, Vec2]>): Geom2 +declare function create(polygons?: Array): Geom2 diff --git a/packages/modeling/src/geometries/geom2/create.js b/packages/modeling/src/geometries/geom2/create.js index b607ebcd4..248eddd55 100644 --- a/packages/modeling/src/geometries/geom2/create.js +++ b/packages/modeling/src/geometries/geom2/create.js @@ -1,24 +1,24 @@ import * as mat4 from '../../maths/mat4/index.js' /** - * Represents a 2D geometry consisting of a list of sides. + * Represents a 2D geometry consisting of a list of 2D polygons. * @typedef {Object} geom2 - * @property {Array} sides - list of sides, each side containing two points - * @property {mat4} transforms - transforms to apply to the sides, see transform() + * @property {Array} outlines - list of polygon outlines + * @property {mat4} transforms - transforms to apply to the geometry, see transform() */ /** - * Create a new 2D geometry composed of unordered sides (two connected points). - * @param {Array} [sides] - list of sides where each side is an array of two points + * Create a new 2D geometry composed of polygon outlines. + * @param {Array} [outlines] - list outlines where each outline is an array of points * @returns {geom2} a new geometry * @alias module:modeling/geometries/geom2.create */ -export const create = (sides) => { - if (sides === undefined) { - sides = [] // empty contents +const create = (outlines) => { + if (outlines === undefined) { + outlines = [] // empty contents } return { - sides: sides, + outlines, transforms: mat4.create() } } diff --git a/packages/modeling/src/geometries/geom2/create.test.js b/packages/modeling/src/geometries/geom2/create.test.js index efee1e1b3..f8e546bcb 100644 --- a/packages/modeling/src/geometries/geom2/create.test.js +++ b/packages/modeling/src/geometries/geom2/create.test.js @@ -4,17 +4,17 @@ import { create } from './index.js' test('create: Creates an empty geom2', (t) => { const expected = { - sides: [], + outlines: [], transforms: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] } t.deepEqual(create(), expected) }) test('create: Creates a populated geom2', (t) => { - const sides = [[[0, 0], [1, 1]]] + const points = [[0, 0], [1, 0], [0, 1]] const expected = { - sides: sides, + outlines: [[[0, 0], [1, 0], [0, 1]]], transforms: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] } - t.deepEqual(create(sides), expected) + t.deepEqual(create([points]), expected) }) diff --git a/packages/modeling/src/geometries/geom2/fromCompactBinary.js b/packages/modeling/src/geometries/geom2/fromCompactBinary.js index 2271761cc..8cf48badc 100644 --- a/packages/modeling/src/geometries/geom2/fromCompactBinary.js +++ b/packages/modeling/src/geometries/geom2/fromCompactBinary.js @@ -16,11 +16,21 @@ export const fromCompactBinary = (data) => { created.transforms = mat4.clone(data.slice(1, 17)) - for (let i = 21; i < data.length; i += 4) { - const point0 = vec2.fromValues(data[i + 0], data[i + 1]) - const point1 = vec2.fromValues(data[i + 2], data[i + 3]) - created.sides.push([point0, point1]) + for (let i = 21; i < data.length;) { + const length = data[i++] // number of points for this polygon + if (length < 0 || i + length * 2 > data.length) { + throw new Error('invalid compact binary data') + } + const outline = [] + for (let j = 0; j < length; j++) { + const x = data[i + j * 2] + const y = data[i + j * 2 + 1] + outline.push(vec2.fromValues(x, y)) + } + created.outlines.push(outline) + i += length * 2 } + // transfer known properties, i.e. color if (data[17] >= 0) { created.color = [data[17], data[18], data[19], data[20]] diff --git a/packages/modeling/src/geometries/geom2/fromPoints.js b/packages/modeling/src/geometries/geom2/fromPoints.js index b52a3227b..5ded4a9e6 100644 --- a/packages/modeling/src/geometries/geom2/fromPoints.js +++ b/packages/modeling/src/geometries/geom2/fromPoints.js @@ -20,16 +20,9 @@ export const fromPoints = (points) => { throw new Error('the given points must define a closed geometry with three or more points') } // adjust length if the given points are closed by the same point - if (vec2.equals(points[0], points[length - 1])) --length + if (vec2.equals(points[0], points[length - 1])) points.length-- - const sides = [] - let prevpoint = points[length - 1] - for (let i = 0; i < length; i++) { - const point = points[i] - sides.push([vec2.clone(prevpoint), vec2.clone(point)]) - prevpoint = point - } - return create(sides) + return create([points]) } export default fromPoints diff --git a/packages/modeling/src/geometries/geom2/fromPoints.test.js b/packages/modeling/src/geometries/geom2/fromPoints.test.js index b9c9abe71..b2f0cb7ee 100644 --- a/packages/modeling/src/geometries/geom2/fromPoints.test.js +++ b/packages/modeling/src/geometries/geom2/fromPoints.test.js @@ -5,7 +5,7 @@ import { fromPoints } from './index.js' test('fromPoints: creates populated geom2', (t) => { const points = [[0, 0], [1, 0], [0, 1]] const expected = { - sides: [[[0, 1], [0, 0]], [[0, 0], [1, 0]], [[1, 0], [0, 1]]], + outlines: [[[0, 0], [1, 0], [0, 1]]], transforms: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] } t.deepEqual(fromPoints(points), expected) diff --git a/packages/modeling/src/geometries/geom2/fromToCompactBinary.test.js b/packages/modeling/src/geometries/geom2/fromToCompactBinary.test.js index 7b3635cdb..ced3ba3e5 100644 --- a/packages/modeling/src/geometries/geom2/fromToCompactBinary.test.js +++ b/packages/modeling/src/geometries/geom2/fromToCompactBinary.test.js @@ -16,12 +16,10 @@ test('toCompactBinary: converts geom2 into a compact form', (t) => { t.deepEqual(compacted1, expected1) // geometry with a hole - const geometry2 = create([[[10, 10], [-10, -10]], - [[-10, -10], [10, -10]], - [[10, -10], [10, 10]], - [[5, -5], [6, -4]], - [[6, -5], [5, -5]], - [[6, -4], [6, -5]]]) + const geometry2 = create([ + [[10, 10], [-10, -10], [10, -10]], + [[5, -5], [6, -4], [6, -5]] + ]) const compacted2 = toCompactBinary(geometry2) const expected2 = new Float32Array([ 0, // type flag @@ -30,12 +28,8 @@ test('toCompactBinary: converts geom2 into a compact form', (t) => { 0, 0, 1, 0, 0, 0, 0, 1, -1, -1, -1, -1, // color - 10, 10, -10, -10, // sides - -10, -10, 10, -10, - 10, -10, 10, 10, - 5, -5, 6, -4, - 6, -5, 5, -5, - 6, -4, 6, -5 + 3, 10, 10, -10, -10, 10, -10, // poly1 + 3, 5, -5, 6, -4, 6, -5 // poly2 ]) t.deepEqual(compacted2, expected2) @@ -49,12 +43,8 @@ test('toCompactBinary: converts geom2 into a compact form', (t) => { 0, 0, 1, 0, 0, 0, 0, 1, 1, 2, 3, 4, // color - 10, 10, -10, -10, // sides - -10, -10, 10, -10, - 10, -10, 10, 10, - 5, -5, 6, -4, - 6, -5, 5, -5, - 6, -4, 6, -5 + 3, 10, 10, -10, -10, 10, -10, // poly1 + 3, 5, -5, 6, -4, 6, -5 // poly2 ]) t.deepEqual(compacted3, expected3) }) @@ -81,20 +71,12 @@ test('fromCompactBinary: convert a compact form into a geom2', (t) => { 0, 0, 1, 0, 0, 0, 0, 1, -1, -1, -1, -1, // color - 10, 10, -10, -10, // sides - -10, -10, 10, -10, - 10, -10, 10, 10, - 5, -5, 6, -4, - 6, -5, 5, -5, - 6, -4, 6, -5 + 3, 10, 10, -10, -10, 10, -10, // poly1 + 3, 5, -5, 6, -5, 6, -4 // poly2 ] const expected2 = create([ - [[10, 10], [-10, -10]], - [[-10, -10], [10, -10]], - [[10, -10], [10, 10]], - [[5, -5], [6, -4]], - [[6, -5], [5, -5]], - [[6, -4], [6, -5]] + [[10, 10], [-10, -10], [10, -10]], + [[5, -5], [6, -5], [6, -4]] ]) const geometry2 = fromCompactBinary(compacted2) @@ -108,12 +90,8 @@ test('fromCompactBinary: convert a compact form into a geom2', (t) => { 0, 0, 1, 0, 0, 0, 0, 1, 4, 5, 6, 7, // color - 10, 10, -10, -10, // sides - -10, -10, 10, -10, - 10, -10, 10, 10, - 5, -5, 6, -4, - 6, -5, 5, -5, - 6, -4, 6, -5 + 3, 10, 10, -10, -10, 10, -10, // poly1 + 3, 5, -5, 6, -5, 6, -4 // poly2 ] expected2.color = [4, 5, 6, 7] const geometry3 = fromCompactBinary(compacted3) diff --git a/packages/modeling/src/geometries/geom2/isA.js b/packages/modeling/src/geometries/geom2/isA.js index 30ced9065..02e50e79b 100644 --- a/packages/modeling/src/geometries/geom2/isA.js +++ b/packages/modeling/src/geometries/geom2/isA.js @@ -6,8 +6,8 @@ */ export const isA = (object) => { if (object && typeof object === 'object') { - if ('sides' in object && 'transforms' in object) { - if (Array.isArray(object.sides) && 'length' in object.transforms) { + if ('outlines' in object && 'transforms' in object) { + if (Array.isArray(object.outlines) && 'length' in object.transforms) { return true } } diff --git a/packages/modeling/src/geometries/geom2/isA.test.js b/packages/modeling/src/geometries/geom2/isA.test.js index f70509dd0..2f7b5f5d7 100644 --- a/packages/modeling/src/geometries/geom2/isA.test.js +++ b/packages/modeling/src/geometries/geom2/isA.test.js @@ -13,7 +13,9 @@ test('isA: identifies non geom2', (t) => { const p1 = null const p2 = {} const p3 = { sides: 1, transforms: 1 } + const p4 = { outlines: 1, transforms: 1 } t.false(isA(p1)) t.false(isA(p2)) t.false(isA(p3)) + t.false(isA(p4)) }) diff --git a/packages/modeling/src/geometries/geom2/reverse.js b/packages/modeling/src/geometries/geom2/reverse.js index f535a5d1d..58c92b4ce 100644 --- a/packages/modeling/src/geometries/geom2/reverse.js +++ b/packages/modeling/src/geometries/geom2/reverse.js @@ -1,5 +1,4 @@ -import create from './create.js' -import toSides from './toSides.js' +import clone from './clone.js' /** * Reverses the given geometry so that the sides are flipped in the opposite order. @@ -12,11 +11,9 @@ import toSides from './toSides.js' * let newgeometry = reverse(geometry) */ export const reverse = (geometry) => { - const oldsides = toSides(geometry) - - const newsides = oldsides.map((side) => [side[1], side[0]]) - newsides.reverse() // is this required? - return create(newsides) + const reversed = clone(geometry) + reversed.outlines.forEach((outline) => outline.reverse()) + return reversed } export default reverse diff --git a/packages/modeling/src/geometries/geom2/reverse.test.js b/packages/modeling/src/geometries/geom2/reverse.test.js index 4a39d98bd..dfa4c2ed5 100644 --- a/packages/modeling/src/geometries/geom2/reverse.test.js +++ b/packages/modeling/src/geometries/geom2/reverse.test.js @@ -7,14 +7,12 @@ import { comparePoints, compareVectors } from '../../../test/helpers/index.js' test('reverse: Reverses a populated geom2', (t) => { const points = [[0, 0], [1, 0], [0, 1]] const expected = { - sides: [[[0, 1], [1, 0]], [[1, 0], [0, 0]], [[0, 0], [0, 1]]], + outlines: [[[0, 1], [1, 0], [0, 0]]], transforms: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] } const geometry = fromPoints(points) const another = reverse(geometry) t.not(geometry, another) - t.true(comparePoints(another.sides[0], expected.sides[0])) - t.true(comparePoints(another.sides[1], expected.sides[1])) - t.true(comparePoints(another.sides[2], expected.sides[2])) + t.true(comparePoints(another.outlines[0], expected.outlines[0])) t.true(compareVectors(another.transforms, expected.transforms)) }) diff --git a/packages/modeling/src/geometries/geom2/toCompactBinary.js b/packages/modeling/src/geometries/geom2/toCompactBinary.js index a1dd35bc8..2bfb5261d 100644 --- a/packages/modeling/src/geometries/geom2/toCompactBinary.js +++ b/packages/modeling/src/geometries/geom2/toCompactBinary.js @@ -5,13 +5,18 @@ * @alias module:modeling/geometries/geom2.toCompactBinary */ export const toCompactBinary = (geometry) => { - const sides = geometry.sides const transforms = geometry.transforms let color = [-1, -1, -1, -1] if (geometry.color) color = geometry.color + // Compute array size + let size = 21 + geometry.outlines.forEach((outline) => { + size += 2 * outline.length + 1 + }) + // FIXME why Float32Array? - const compacted = new Float32Array(1 + 16 + 4 + (sides.length * 4)) // type + transforms + color + sides data + const compacted = new Float32Array(size) // type + transforms + color + vertex data compacted[0] = 0 // type code: 0 => geom2, 1 => geom3 , 2 => path2 @@ -37,15 +42,15 @@ export const toCompactBinary = (geometry) => { compacted[19] = color[2] compacted[20] = color[3] - for (let i = 0; i < sides.length; i++) { - const ci = i * 4 + 21 - const point0 = sides[i][0] - const point1 = sides[i][1] - compacted[ci + 0] = point0[0] - compacted[ci + 1] = point0[1] - compacted[ci + 2] = point1[0] - compacted[ci + 3] = point1[1] - } + let index = 21 + geometry.outlines.forEach((outline) => { + compacted[index++] = outline.length + outline.forEach((point) => { + compacted[index++] = point[0] + compacted[index++] = point[1] + }) + }) + // TODO: how about custom properties or fields ? return compacted } diff --git a/packages/modeling/src/geometries/geom2/toOutlines.js b/packages/modeling/src/geometries/geom2/toOutlines.js index afc932feb..dca9b9429 100644 --- a/packages/modeling/src/geometries/geom2/toOutlines.js +++ b/packages/modeling/src/geometries/geom2/toOutlines.js @@ -1,43 +1,4 @@ -import * as vec2 from '../../maths/vec2/index.js' - -import toSides from './toSides.js' - -/* - * Create a list of edges which SHARE vertices. - * This allows the edges to be traversed in order. - */ -const toSharedVertices = (sides) => { - const unique = new Map() // {key: vertex} - const getUniqueVertex = (vertex) => { - const key = vertex.toString() - if (unique.has(key)) { - return unique.get(key) - } else { - unique.set(key, vertex) - return vertex - } - } - - return sides.map((side) => side.map(getUniqueVertex)) -} - -/* - * Convert a list of sides into a map from vertex to edges. - */ -const toVertexMap = (sides) => { - const vertexMap = new Map() - // first map to edges with shared vertices - const edges = toSharedVertices(sides) - // construct adjacent edges map - edges.forEach((edge) => { - if (vertexMap.has(edge[0])) { - vertexMap.get(edge[0]).push(edge) - } else { - vertexMap.set(edge[0], [edge]) - } - }) - return vertexMap -} +import applyTransforms from './applyTransforms.js' /** * Create the outline(s) of the given geometry. @@ -49,71 +10,8 @@ const toVertexMap = (sides) => { * let geometry = subtract(rectangle({size: [5, 5]}), rectangle({size: [3, 3]})) * let outlines = toOutlines(geometry) // returns two outlines */ -export const toOutlines = (geometry) => { - const vertexMap = toVertexMap(toSides(geometry)) // {vertex: [edges]} - const outlines = [] - while (true) { - let startSide - for (const [vertex, edges] of vertexMap) { - startSide = edges.shift() - if (!startSide) { - vertexMap.delete(vertex) - continue - } - break - } - if (startSide === undefined) break // all starting sides have been visited - - const connectedVertexPoints = [] - const startVertex = startSide[0] - while (true) { - connectedVertexPoints.push(startSide[0]) - const nextVertex = startSide[1] - if (nextVertex === startVertex) break // the outline has been closed - const nextPossibleSides = vertexMap.get(nextVertex) - if (!nextPossibleSides) { - throw new Error(`geometry is not closed at vertex ${nextVertex}`) - } - const nextSide = popNextSide(startSide, nextPossibleSides) - if (nextPossibleSides.length === 0) { - vertexMap.delete(nextVertex) - } - startSide = nextSide - } // inner loop - - // due to the logic of fromPoints() - // move the first point to the last - if (connectedVertexPoints.length > 0) { - connectedVertexPoints.push(connectedVertexPoints.shift()) - } - outlines.push(connectedVertexPoints) - } // outer loop - vertexMap.clear() - return outlines -} - -// find the first counter-clockwise edge from startSide and pop from nextSides -const popNextSide = (startSide, nextSides) => { - if (nextSides.length === 1) { - return nextSides.pop() - } - const v0 = vec2.create() - const startAngle = vec2.angleDegrees(vec2.subtract(v0, startSide[1], startSide[0])) - let bestAngle - let bestIndex - nextSides.forEach((nextSide, index) => { - const nextAngle = vec2.angleDegrees(vec2.subtract(v0, nextSide[1], nextSide[0])) - let angle = nextAngle - startAngle - if (angle < -180) angle += 360 - if (angle >= 180) angle -= 360 - if (bestIndex === undefined || angle > bestAngle) { - bestIndex = index - bestAngle = angle - } - }) - const nextSide = nextSides[bestIndex] - nextSides.splice(bestIndex, 1) // remove side from list - return nextSide +const toOutlines = (geometry) => { + return applyTransforms(geometry).outlines } export default toOutlines diff --git a/packages/modeling/src/geometries/geom2/toOutlines.test.js b/packages/modeling/src/geometries/geom2/toOutlines.test.js index 4097609ae..b7da8bd46 100644 --- a/packages/modeling/src/geometries/geom2/toOutlines.test.js +++ b/packages/modeling/src/geometries/geom2/toOutlines.test.js @@ -12,109 +12,84 @@ test('geom2: toOutlines() should return no outlines for empty geom2', (t) => { }) test('geom2: toOutlines() should return one or more outlines', (t) => { - const shp1 = create([[[-1, -1], [1, -1]], - [[1, -1], [1, 1]], - [[1, 1], [-1, -1]]]) + const shp1 = create([{vertices: [[-1, -1], [1, -1], [1, 1]]}]) const ret1 = toOutlines(shp1) const exp1 = [ - [[1, -1], [1, 1], [-1, -1]] + [[-1, -1], [1, -1], [1, 1]] ] - t.true(comparePoints(ret1[0], exp1[0])) + t.true(comparePoints(ret1[0].vertices, exp1[0])) - const shp2 = create([[[-1, -1], [1, -1]], - [[1, -1], [1, 1]], - [[1, 1], [-1, -1]], - [[4, 4], [6, 4]], - [[6, 4], [6, 6]], - [[6, 6], [4, 4]]]) + const shp2 = create([ + {vertices: [[-1, -1], [1, -1], [1, 1]]}, + {vertices: [[4, 4], [6, 4], [6, 6]]} + ]) const ret2 = toOutlines(shp2) const exp2 = [ - [[1, -1], [1, 1], [-1, -1]], - [[6, 4], [6, 6], [4, 4]] + [[-1, -1], [1, -1], [1, 1]], + [[4, 4], [6, 4], [6, 6]] ] - t.true(comparePoints(ret2[0], exp2[0])) - t.true(comparePoints(ret2[1], exp2[1])) + t.true(comparePoints(ret2[0].vertices, exp2[0])) + t.true(comparePoints(ret2[1].vertices, exp2[1])) }) test('geom2: toOutlines() should return outlines for holes in geom2', (t) => { - const shp1 = create([[[10, 10], [-10, -10]], - [[-10, -10], [10, -10]], - [[10, -10], [10, 10]], - [[5, -5], [6, -4]], - [[6, -5], [5, -5]], - [[6, -4], [6, -5]]]) + const shp1 = create([ + {vertices: [[10, 10], [-10, -10], [10, -10]]}, + {vertices: [[5, -5], [6, -4], [6, -4]]} + ]) const ret1 = toOutlines(shp1) const exp1 = [ - [[-10, -10], [10, -10], [10, 10]], - [[6, -4], [6, -5], [5, -5]] + [[10, 10], [-10, -10], [10, -10]], + [[5, -5], [6, -4], [6, -4]] ] - t.true(comparePoints(ret1[0], exp1[0])) - t.true(comparePoints(ret1[1], exp1[1])) + t.true(comparePoints(ret1[0].vertices, exp1[0])) + t.true(comparePoints(ret1[1].vertices, exp1[1])) - const shp2 = create([[[6, -4], [5, -5]], - [[5, -5], [6, -5]], - [[6, -5], [6, -4]], - [[10, 10], [-10, -10]], - [[-10, -10], [10, -10]], - [[10, -10], [10, 10]], - [[-6, -8], [8, 6]], - [[8, -8], [-6, -8]], - [[8, 6], [8, -8]]]) + const shp2 = create([ + {vertices: [[6, -4], [5, -5], [6, -5]]}, + {vertices: [[10, 10], [-10, -10], [10, -10]]}, + {vertices: [[-6, -8], [8, 6], [8, -8]]} + ]) const ret2 = toOutlines(shp2) const exp2 = [ - [[5, -5], [6, -5], [6, -4]], - [[-10, -10], [10, -10], [10, 10]], - [[8, 6], [8, -8], [-6, -8]] + [[6, -4], [5, -5], [6, -5]], + [[10, 10], [-10, -10], [10, -10]], + [[-6, -8], [8, 6], [8, -8]] ] - t.true(comparePoints(ret2[0], exp2[0])) - t.true(comparePoints(ret2[1], exp2[1])) - t.true(comparePoints(ret2[2], exp2[2])) + t.true(comparePoints(ret2[0].vertices, exp2[0])) + t.true(comparePoints(ret2[1].vertices, exp2[1])) + t.true(comparePoints(ret2[2].vertices, exp2[2])) }) test('geom2: toOutlines() should return outlines for edges that touch in geom2', (t) => { const shp1 = create([ - [[5, 15], [5, 5]], - [[5, 5], [15, 5]], - [[15, 5], [15, 15]], - [[15, 15], [5, 15]], - [[-5, 5], [-5, -5]], - [[-5, -5], [5, -5]], - [[5, -5], [5, 5]], - [[5, 5], [-5, 5]] + {vertices: [[5, 15], [5, 5], [15, 5], [15, 15]]}, + {vertices: [[-5, 5], [-5, -5], [5, -5], [5, 5]]} ]) const ret1 = toOutlines(shp1) const exp1 = [ - [[5, 5], [15, 5], [15, 15], [5, 15]], + [[5, 15], [5, 5], [15, 5], [15, 15]], [[-5, 5], [-5, -5], [5, -5], [5, 5]] ] - t.true(comparePoints(ret1[0], exp1[0])) - t.true(comparePoints(ret1[1], exp1[1])) + t.true(comparePoints(ret1[0].vertices, exp1[0])) + t.true(comparePoints(ret1[1].vertices, exp1[1])) }) test('geom2: toOutlines() should return outlines for holes that touch in geom2', (t) => { const shp1 = create([ - [[-20, 20], [-20, -20]], - [[-20, -20], [20, -20]], - [[20, -20], [20, 20]], - [[20, 20], [-20, 20]], - [[5, 5], [5, 15]], - [[15, 5], [5, 5]], - [[15, 15], [15, 5]], - [[5, 15], [15, 15]], - [[-5, -5], [-5, 5]], - [[5, -5], [-5, -5]], - [[5, 5], [5, -5]], - [[-5, 5], [5, 5]] + {vertices: [[-20, 20], [-20, -20], [20, -20], [20, 20]]}, + {vertices: [[5, 5], [5, 15], [15, 15], [15, 5]]}, + {vertices: [[-5, -5], [-5, 5], [5, 5], [5, -5]]} ]) const ret1 = toOutlines(shp1) const exp1 = [ - [[-20, -20], [20, -20], [20, 20], [-20, 20]], - [[5, 15], [15, 15], [15, 5], [5, 5]], - [[5, -5], [-5, -5], [-5, 5], [5, 5]] + [[-20, 20], [-20, -20], [20, -20], [20, 20]], + [[5, 5], [5, 15], [15, 15], [15, 5]], + [[-5, -5], [-5, 5], [5, 5], [5, -5]] ] - t.true(comparePoints(ret1[0], exp1[0])) - t.true(comparePoints(ret1[1], exp1[1])) - t.true(comparePoints(ret1[2], exp1[2])) + t.true(comparePoints(ret1[0].vertices, exp1[0])) + t.true(comparePoints(ret1[1].vertices, exp1[1])) + t.true(comparePoints(ret1[2].vertices, exp1[2])) }) // touching holes diff --git a/packages/modeling/src/geometries/geom2/toPoints.js b/packages/modeling/src/geometries/geom2/toPoints.js index 073fb8107..09c10e9ce 100644 --- a/packages/modeling/src/geometries/geom2/toPoints.js +++ b/packages/modeling/src/geometries/geom2/toPoints.js @@ -1,4 +1,3 @@ -import toSides from './toSides.js' /** * Produces an array of points from the given geometry. @@ -12,13 +11,12 @@ import toSides from './toSides.js' * let sharedpoints = toPoints(geometry) */ export const toPoints = (geometry) => { - const sides = toSides(geometry) - const points = sides.map((side) => side[0]) - // due to the logic of fromPoints() - // move the first point to the last - if (points.length > 0) { - points.push(points.shift()) - } + const points = [] + geometry.outlines.forEach((outline) => { + outline.forEach((point) => { + points.push(point) + }) + }) return points } diff --git a/packages/modeling/src/geometries/geom2/toSides.js b/packages/modeling/src/geometries/geom2/toSides.js index 897765513..8f63e44c1 100644 --- a/packages/modeling/src/geometries/geom2/toSides.js +++ b/packages/modeling/src/geometries/geom2/toSides.js @@ -11,6 +11,16 @@ import applyTransforms from './applyTransforms.js' * @example * let sharedsides = toSides(geometry) */ -export const toSides = (geometry) => applyTransforms(geometry).sides +export const toSides = (geometry) => { + applyTransforms(geometry) + const sides = [] + geometry.outlines.forEach((outline) => { + outline.forEach((point, i) => { + const j = (i + 1) % outline.length + sides.push([point, outline[j]]) + }) + }) + return sides +} export default toSides diff --git a/packages/modeling/src/geometries/geom2/transform.test.js b/packages/modeling/src/geometries/geom2/transform.test.js index 98756e206..170321535 100644 --- a/packages/modeling/src/geometries/geom2/transform.test.js +++ b/packages/modeling/src/geometries/geom2/transform.test.js @@ -15,39 +15,32 @@ test('transform: adjusts the transforms of geom2', (t) => { // expect lazy transform, i.e. only the transforms change const expected = { - sides: [[[0, 1], [0, 0]], [[0, 0], [1, 0]], [[1, 0], [0, 1]]], + outlines: [[[0, 0], [1, 0], [0, 1]]], transforms: [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] } const geometry = fromPoints(points) let another = transform(rotate90, geometry) t.not(geometry, another) - t.true(comparePoints(another.sides[0], expected.sides[0])) - t.true(comparePoints(another.sides[1], expected.sides[1])) - t.true(comparePoints(another.sides[2], expected.sides[2])) + t.true(comparePoints(another.outlines[0], expected.outlines[0])) t.true(compareVectors(another.transforms, expected.transforms)) // expect lazy transform, i.e. only the transforms change expected.transforms = [0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 5, 10, 15, 1] another = transform(mat4.fromTranslation(mat4.create(), [5, 10, 15]), another) - t.true(comparePoints(another.sides[0], expected.sides[0])) - t.true(comparePoints(another.sides[1], expected.sides[1])) - t.true(comparePoints(another.sides[2], expected.sides[2])) + t.true(comparePoints(another.outlines[0], expected.outlines[0])) t.true(compareVectors(another.transforms, expected.transforms)) // expect application of the transforms to the sides - expected.sides = [[[4, 10], [5, 10]], [[5, 10], [5, 11]], [[5, 11], [4, 10]]] - expected.transforms = mat4.create() - toSides(another) - t.true(comparePoints(another.sides[0], expected.sides[0])) - t.true(comparePoints(another.sides[1], expected.sides[1])) - t.true(comparePoints(another.sides[2], expected.sides[2])) - t.true(compareVectors(another.transforms, expected.transforms)) + expectedSides = [[[5, 10], [5, 11]], [[5, 11], [4, 10]], [[4, 10], [5, 10]]] + const sides = toSides(another) + t.true(comparePoints(sides[0], expectedSides[0])) + t.true(comparePoints(sides[1], expectedSides[1])) + t.true(comparePoints(sides[2], expectedSides[2])) // expect lazy transform, i.e. only the transforms change expected.transforms = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5, 10, 15, 1] + another.outlines = [[[0, 0], [1, 0], [0, 1]]] another = transform(mat4.fromTranslation(mat4.create(), [5, 10, 15]), another) - t.true(comparePoints(another.sides[0], expected.sides[0])) - t.true(comparePoints(another.sides[1], expected.sides[1])) - t.true(comparePoints(another.sides[2], expected.sides[2])) + t.true(comparePoints(another.outlines[0], expected.outlines[0])) t.true(compareVectors(another.transforms, expected.transforms)) }) diff --git a/packages/modeling/src/geometries/geom2/type.d.ts b/packages/modeling/src/geometries/geom2/type.d.ts index 5719b5765..be9b2e311 100644 --- a/packages/modeling/src/geometries/geom2/type.d.ts +++ b/packages/modeling/src/geometries/geom2/type.d.ts @@ -1,11 +1,11 @@ -import Vec2 from '../../maths/vec2/type' +import Poly2 from '../../maths/poly2/type' import Mat4 from '../../maths/mat4/type' import { Color } from '../types' export default Geom2 declare interface Geom2 { - sides: Array<[Vec2, Vec2]> + outlines: Array transforms: Mat4 color?: Color } diff --git a/packages/modeling/src/geometries/geom2/validate.js b/packages/modeling/src/geometries/geom2/validate.js index e5cc4f4ee..b01a3090a 100644 --- a/packages/modeling/src/geometries/geom2/validate.js +++ b/packages/modeling/src/geometries/geom2/validate.js @@ -18,13 +18,13 @@ export const validate = (object) => { throw new Error('invalid geom2 structure') } - // check for closedness - toOutlines(object) - - // check for self-edges - object.sides.forEach((side) => { - if (vec2.equals(side[0], side[1])) { - throw new Error(`geom2 self-edge ${side[0]}`) + // check for duplicate points + object.outlines.forEach((outline) => { + for (let i = 0; i < outline.length; i++) { + const j = (i + 1) % outline.length + if (vec2.equals(outline[i], outline[j])) { + throw new Error(`geom2 self-edge ${side[0]}`) + } } }) diff --git a/packages/modeling/src/geometries/poly2/create.js b/packages/modeling/src/geometries/poly2/create.js index 613ec4a07..77e05b278 100644 --- a/packages/modeling/src/geometries/poly2/create.js +++ b/packages/modeling/src/geometries/poly2/create.js @@ -18,7 +18,7 @@ export const create = (vertices) => { if (vertices === undefined || vertices.length < 3) { vertices = [] // empty contents } - return { vertices: vertices } + return { vertices } } export default create diff --git a/packages/modeling/src/primitives/polygon.js b/packages/modeling/src/primitives/polygon.js index 3bbf310cd..f0a167add 100644 --- a/packages/modeling/src/primitives/polygon.js +++ b/packages/modeling/src/primitives/polygon.js @@ -58,13 +58,13 @@ export const polygon = (options) => { const allpoints = [] listofpolys.forEach((list) => list.forEach((point) => allpoints.push(point))) - let sides = [] + let outlines = [] listofpaths.forEach((path) => { const setofpoints = path.map((index) => allpoints[index]) const geometry = geom2.fromPoints(setofpoints) - sides = sides.concat(geom2.toSides(geometry)) + outlines = outlines.concat(geometry.outlines) }) - return geom2.create(sides) + return geom2.create(outlines) } export default polygon diff --git a/packages/modeling/src/primitives/polygon.test.js b/packages/modeling/src/primitives/polygon.test.js index 00ab45a33..59f157069 100644 --- a/packages/modeling/src/primitives/polygon.test.js +++ b/packages/modeling/src/primitives/polygon.test.js @@ -18,7 +18,7 @@ test('polygon: providing only object.points creates expected geometry', (t) => { geometry = polygon({ points: [[[0, 0], [100, 0], [0, 100]], [[10, 10], [80, 10], [10, 80]]] }) obs = geom2.toPoints(geometry) - exp = [[0, 0], [100, 0], [10, 80], [10, 10], [80, 10], [0, 100]] + exp = [[0, 0], [100, 0], [0, 100], [10, 10], [80, 10], [10, 80]] t.notThrows(() => geom2.validate(geometry)) t.true(comparePoints(obs, exp)) @@ -37,7 +37,7 @@ test('polygon: providing object.points (array) and object.path (array) creates e geometry = polygon({ points: [[0, 0], [100, 0], [0, 100], [10, 10], [80, 10], [10, 80]], paths: [[0, 1, 2], [3, 4, 5]] }) obs = geom2.toPoints(geometry) - exp = [[0, 0], [100, 0], [10, 80], [10, 10], [80, 10], [0, 100]] + exp = [[0, 0], [100, 0], [0, 100], [10, 10], [80, 10], [10, 80]] t.notThrows(() => geom2.validate(geometry)) t.true(comparePoints(obs, exp)) @@ -46,7 +46,7 @@ test('polygon: providing object.points (array) and object.path (array) creates e geometry = polygon({ points: [[[0, 0], [100, 0], [0, 100]], [[10, 10], [80, 10], [10, 80]]], paths: [[0, 1, 2], [3, 4, 5]] }) obs = geom2.toPoints(geometry) - exp = [[0, 0], [100, 0], [10, 80], [10, 10], [80, 10], [0, 100]] + exp = [[0, 0], [100, 0], [0, 100], [10, 10], [80, 10], [10, 80]] t.notThrows(() => geom2.validate(geometry)) t.true(comparePoints(obs, exp)) From e808e9ad372aa474ff68a57a1b9f77445153f895 Mon Sep 17 00:00:00 2001 From: Kenny Daniel Date: Fri, 9 Dec 2022 15:47:30 -0800 Subject: [PATCH 02/15] Fix tests --- .../modeling/src/geometries/geom2/create.js | 2 +- .../src/geometries/geom2/fromOutlines.d.ts | 6 - .../src/geometries/geom2/fromOutlines.js | 36 ------ .../src/geometries/geom2/fromSides.js | 118 ++++++++++++++++++ .../modeling/src/geometries/geom2/index.d.ts | 1 - .../modeling/src/geometries/geom2/index.js | 2 +- .../src/geometries/geom2/toOutlines.js | 2 +- .../modeling/src/geometries/geom2/toPoints.js | 3 +- .../modeling/src/geometries/geom2/validate.js | 2 +- .../src/operations/booleans/martinez/index.js | 16 ++- .../operations/booleans/unionGeom2.test.js | 17 ++- .../src/operations/expansions/expand.test.js | 18 +-- .../src/operations/expansions/expandGeom2.js | 2 +- .../src/operations/expansions/expandPath2.js | 2 +- .../src/operations/expansions/offset.test.js | 73 +++++------ .../src/operations/expansions/offsetGeom2.js | 2 +- .../extrusions/earcut/polygonHierarchy.js | 2 +- .../extrusions/extrudeFromSlices.test.js | 38 +++--- .../extrusions/extrudeLinear.test.js | 26 ++-- .../extrusions/extrudeRectangular.test.js | 10 +- .../extrusions/extrudeRectangularGeom2.js | 2 +- .../operations/extrusions/extrudeRotate.js | 4 +- .../extrusions/extrudeRotate.test.js | 40 +++--- .../modeling/src/operations/modifiers/snap.js | 2 +- .../src/operations/modifiers/snap.test.js | 14 ++- 25 files changed, 255 insertions(+), 185 deletions(-) delete mode 100644 packages/modeling/src/geometries/geom2/fromOutlines.d.ts delete mode 100644 packages/modeling/src/geometries/geom2/fromOutlines.js create mode 100644 packages/modeling/src/geometries/geom2/fromSides.js diff --git a/packages/modeling/src/geometries/geom2/create.js b/packages/modeling/src/geometries/geom2/create.js index 248eddd55..599aa7d2f 100644 --- a/packages/modeling/src/geometries/geom2/create.js +++ b/packages/modeling/src/geometries/geom2/create.js @@ -13,7 +13,7 @@ import * as mat4 from '../../maths/mat4/index.js' * @returns {geom2} a new geometry * @alias module:modeling/geometries/geom2.create */ -const create = (outlines) => { +export const create = (outlines) => { if (outlines === undefined) { outlines = [] // empty contents } diff --git a/packages/modeling/src/geometries/geom2/fromOutlines.d.ts b/packages/modeling/src/geometries/geom2/fromOutlines.d.ts deleted file mode 100644 index 30119599a..000000000 --- a/packages/modeling/src/geometries/geom2/fromOutlines.d.ts +++ /dev/null @@ -1,6 +0,0 @@ -import Geom2 from './type' -import Vec2 from '../../maths/vec2/type' - -export default fromOutlines - -declare function fromOutlines(outlines: Array>): Geom2 diff --git a/packages/modeling/src/geometries/geom2/fromOutlines.js b/packages/modeling/src/geometries/geom2/fromOutlines.js deleted file mode 100644 index cf4b7666c..000000000 --- a/packages/modeling/src/geometries/geom2/fromOutlines.js +++ /dev/null @@ -1,36 +0,0 @@ -import * as vec2 from '../../maths/vec2/index.js' - -import create from './create.js' - -/** - * Create a new 2D geometry from the given outlines. - * The geometry must not self intersect, i.e. the sides cannot cross. - * @param {Array} outlines - list of outlines in 2D space - * @returns {geom2} a new geometry - * @alias module:modeling/geometries/geom2.fromOutlines - */ -export const fromOutlines = (outlines) => { - if (!Array.isArray(outlines)) { - throw new Error('the given outlines must be an array') - } - if (outlines.length > 0 && !Array.isArray(outlines[0])) { - throw new Error('the given outlines must be arrays of points') - } - - const sides = [] - outlines.forEach((outline) => { - let length = outline.length - if (vec2.equals(outline[0], outline[length - 1])) { - length-- - } - let previous = outline[length - 1] - for (let i = 0; i < length; i++) { - const point = outline[i] - sides.push([vec2.clone(previous), vec2.clone(point)]) - previous = point - } - }) - return create(sides) -} - -export default fromOutlines diff --git a/packages/modeling/src/geometries/geom2/fromSides.js b/packages/modeling/src/geometries/geom2/fromSides.js new file mode 100644 index 000000000..9169c56b4 --- /dev/null +++ b/packages/modeling/src/geometries/geom2/fromSides.js @@ -0,0 +1,118 @@ +import * as vec2 from '../../maths/vec2/index.js' +import create from './create.js' + +/* + * Create a list of edges which SHARE vertices. + * This allows the edges to be traversed in order. + */ +const toSharedVertices = (sides) => { + const unique = new Map() // {key: vertex} + const getUniqueVertex = (vertex) => { + const key = vertex.toString() + if (unique.has(key)) { + return unique.get(key) + } else { + unique.set(key, vertex) + return vertex + } + } + + return sides.map((side) => side.map(getUniqueVertex)) +} + +/* + * Convert a list of sides into a map from vertex to edges. + */ +const toVertexMap = (sides) => { + const vertexMap = new Map() + // first map to edges with shared vertices + const edges = toSharedVertices(sides) + // construct adjacent edges map + edges.forEach((edge) => { + if (vertexMap.has(edge[0])) { + vertexMap.get(edge[0]).push(edge) + } else { + vertexMap.set(edge[0], [edge]) + } + }) + return vertexMap +} + +/** + * Create the outline(s) of the given geometry. + * @param {geom2} geometry - geometry to create outlines from + * @returns {Array} an array of outlines, where each outline is an array of ordered points + * @alias module:modeling/geometries/geom2.toOutlines + * + * @example + * let geometry = subtract(rectangle({size: [5, 5]}), rectangle({size: [3, 3]})) + * let outlines = toOutlines(geometry) // returns two outlines + */ +export const fromSides = (sides) => { + const vertexMap = toVertexMap(sides) // {vertex: [edges]} + const outlines = [] + while (true) { + let startSide + for (const [vertex, edges] of vertexMap) { + startSide = edges.shift() + if (!startSide) { + vertexMap.delete(vertex) + continue + } + break + } + if (startSide === undefined) break // all starting sides have been visited + + const connectedVertexPoints = [] + const startVertex = startSide[0] + while (true) { + connectedVertexPoints.push(startSide[0]) + const nextVertex = startSide[1] + if (nextVertex === startVertex) break // the outline has been closed + const nextPossibleSides = vertexMap.get(nextVertex) + if (!nextPossibleSides) { + throw new Error(`geometry is not closed at vertex ${nextVertex}`) + } + const nextSide = popNextSide(startSide, nextPossibleSides) + if (nextPossibleSides.length === 0) { + vertexMap.delete(nextVertex) + } + startSide = nextSide + } // inner loop + + // due to the logic of fromPoints() + // move the first point to the last + if (connectedVertexPoints.length > 0) { + connectedVertexPoints.push(connectedVertexPoints.shift()) + } + outlines.push(connectedVertexPoints) + } // outer loop + vertexMap.clear() + return create(outlines) +} + +// find the first counter-clockwise edge from startSide and pop from nextSides +const popNextSide = (startSide, nextSides) => { + if (nextSides.length === 1) { + return nextSides.pop() + } + const v0 = vec2.create() + const startAngle = vec2.angleDegrees(vec2.subtract(v0, startSide[1], startSide[0])) + let bestAngle + let bestIndex + nextSides.forEach((nextSide, index) => { + const nextAngle = vec2.angleDegrees(vec2.subtract(v0, nextSide[1], nextSide[0])) + let angle = nextAngle - startAngle + if (angle < -180) angle += 360 + if (angle >= 180) angle -= 360 + if (bestIndex === undefined || angle > bestAngle) { + bestIndex = index + bestAngle = angle + } + }) + const nextSide = nextSides[bestIndex] + nextSides.splice(bestIndex, 1) // remove side from list + return nextSide +} + +export default fromSides diff --git a/packages/modeling/src/geometries/geom2/index.d.ts b/packages/modeling/src/geometries/geom2/index.d.ts index 76f97bf5c..d937b0e8f 100644 --- a/packages/modeling/src/geometries/geom2/index.d.ts +++ b/packages/modeling/src/geometries/geom2/index.d.ts @@ -1,6 +1,5 @@ export { default as clone } from './clone' export { default as create } from './create' -export { default as fromOutlines } from './fromOutlines' export { default as fromPoints } from './fromPoints' export { default as fromCompactBinary } from './fromCompactBinary' export { default as isA } from './isA' diff --git a/packages/modeling/src/geometries/geom2/index.js b/packages/modeling/src/geometries/geom2/index.js index 2bfe93e83..a1b9ef437 100644 --- a/packages/modeling/src/geometries/geom2/index.js +++ b/packages/modeling/src/geometries/geom2/index.js @@ -15,8 +15,8 @@ */ export { clone } from './clone.js' export { create } from './create.js' -export { fromOutlines } from './fromOutlines.js' export { fromPoints } from './fromPoints.js' +export { fromSides } from './fromSides.js' export { fromCompactBinary } from './fromCompactBinary.js' export { isA } from './isA.js' export { reverse } from './reverse.js' diff --git a/packages/modeling/src/geometries/geom2/toOutlines.js b/packages/modeling/src/geometries/geom2/toOutlines.js index dca9b9429..8f28c599d 100644 --- a/packages/modeling/src/geometries/geom2/toOutlines.js +++ b/packages/modeling/src/geometries/geom2/toOutlines.js @@ -10,7 +10,7 @@ import applyTransforms from './applyTransforms.js' * let geometry = subtract(rectangle({size: [5, 5]}), rectangle({size: [3, 3]})) * let outlines = toOutlines(geometry) // returns two outlines */ -const toOutlines = (geometry) => { +export const toOutlines = (geometry) => { return applyTransforms(geometry).outlines } diff --git a/packages/modeling/src/geometries/geom2/toPoints.js b/packages/modeling/src/geometries/geom2/toPoints.js index 09c10e9ce..6a2b9a51d 100644 --- a/packages/modeling/src/geometries/geom2/toPoints.js +++ b/packages/modeling/src/geometries/geom2/toPoints.js @@ -1,3 +1,4 @@ +import toOutlines from './toOutlines.js' /** * Produces an array of points from the given geometry. @@ -12,7 +13,7 @@ */ export const toPoints = (geometry) => { const points = [] - geometry.outlines.forEach((outline) => { + toOutlines(geometry).forEach((outline) => { outline.forEach((point) => { points.push(point) }) diff --git a/packages/modeling/src/geometries/geom2/validate.js b/packages/modeling/src/geometries/geom2/validate.js index b01a3090a..13bbbdd03 100644 --- a/packages/modeling/src/geometries/geom2/validate.js +++ b/packages/modeling/src/geometries/geom2/validate.js @@ -23,7 +23,7 @@ export const validate = (object) => { for (let i = 0; i < outline.length; i++) { const j = (i + 1) % outline.length if (vec2.equals(outline[i], outline[j])) { - throw new Error(`geom2 self-edge ${side[0]}`) + throw new Error(`geom2 self-edge ${outline[i]}`) } } }) diff --git a/packages/modeling/src/operations/booleans/martinez/index.js b/packages/modeling/src/operations/booleans/martinez/index.js index 6feb3c206..d229167ea 100644 --- a/packages/modeling/src/operations/booleans/martinez/index.js +++ b/packages/modeling/src/operations/booleans/martinez/index.js @@ -38,7 +38,7 @@ const trivialOperation = (subject, clipping, operation) => { if (result === EMPTY) { return geom2.create() } else if (result) { - return geom2.fromOutlines(result.flat()) + return fromOutlines(result.flat()) } else { return null } @@ -66,7 +66,7 @@ const compareBBoxes = (subject, clipping, sbbox, cbbox, operation) => { if (result === EMPTY) { return geom2.create() } else if (result) { - return geom2.fromOutlines(result.flat()) + return fromOutlines(result.flat()) } else { return null } @@ -84,6 +84,16 @@ const toMartinez = (geometry) => { return [outlines] } +/* + * Convert martinez data structure to geom2 + */ +const fromOutlines = (outlines) => { + outlines.forEach((outline) => { + outline.pop() // first == last point + }) + return geom2.create(outlines) +} + export default function boolean (subjectGeom, clippingGeom, operation) { // Convert from geom2 to outlines const subject = toMartinez(subjectGeom) @@ -134,7 +144,7 @@ export default function boolean (subjectGeom, clippingGeom, operation) { } if (polygons) { - return geom2.fromOutlines(polygons.flat()) + return fromOutlines(polygons.flat()) } else { return geom2.create() } diff --git a/packages/modeling/src/operations/booleans/unionGeom2.test.js b/packages/modeling/src/operations/booleans/unionGeom2.test.js index 354006f65..6d4bb6e86 100644 --- a/packages/modeling/src/operations/booleans/unionGeom2.test.js +++ b/packages/modeling/src/operations/booleans/unionGeom2.test.js @@ -42,13 +42,14 @@ test('union of one or more geom2 objects produces expected geometry', (t) => { [-2, 0], [-1.4142135623730954, -1.414213562373095], [0, -2], - [8, 12], + [1.4142135623730947, -1.4142135623730954], [8, 8], [12, 8], [12, 12], - [1.4142135623730947, -1.4142135623730954] + [8, 12] ] t.notThrows(() => geom2.validate(result2)) + t.is(obs.length, 12) t.true(comparePoints(obs, exp)) // union of two partially overlapping objects @@ -99,7 +100,7 @@ test('union of one or more geom2 objects produces expected geometry', (t) => { }) test('union of geom2 with closing issues #15', (t) => { - const c = geom2.create([ + const c = geom2.fromSides([ [[-45.82118740347841168159, -16.85726810555620147625], [-49.30331715865012398581, -14.68093629710870118288]], [[-49.10586702080816223770, -15.27604177352110781385], [-48.16645938811709015681, -15.86317173589183227023]], [[-49.60419521731581937729, -14.89550781504266296906], [-49.42407001323204696064, -15.67605088949303393520]], @@ -115,7 +116,7 @@ test('union of geom2 with closing issues #15', (t) => { [[-49.42407001323204696064, -15.67605088949303393520], [-49.30706235399220815907, -15.81529674600091794900]], [[-48.16645938811709015681, -15.86317173589183227023], [-49.05727291218684626983, -15.48661638542171203881]] ]) - const d = geom2.create([ + const d = geom2.fromSides([ [[-49.03431352173912216585, -15.58610714407888764299], [-49.21443872582289458251, -14.80556406962851667686]], [[-68.31614651314507113966, -3.10790373951434872879], [-49.34036769611472550423, -15.79733157434056778357]], [[-49.58572929483430868913, -14.97552686612213790340], [-49.53755741140093959984, -15.18427183431472826669]], @@ -151,15 +152,13 @@ test('union of geom2 with closing issues #15', (t) => { [-49.302794903461326, -14.68126270670841], [-68.09792828135777, -2.7727075661152867], [-68.24753735887461, -2.7462335017957002], - [-54.61235529924313, -11.790667693213138], + [-68.37258141465594, -2.8325337698763633], [-49.58572929483431, -14.975526866122138], [-49.53755741140094, -15.184271834314728], - [-49.10586702080816, -15.276041773521108], [-48.16645938811709, -15.863171735891832], - [-49.057272912186846, -15.486616385421712], - [-68.37258141465594, -2.8325337698763633] + [-49.057272912186846, -15.486616385421712] ] t.notThrows(() => geom2.validate(obs)) - t.is(pts.length, 20) // number of sides in union + t.is(pts.length, 18) // number of sides in union t.true(comparePoints(pts, exp)) }) diff --git a/packages/modeling/src/operations/expansions/expand.test.js b/packages/modeling/src/operations/expansions/expand.test.js index 6f330a71e..1899ca4d0 100644 --- a/packages/modeling/src/operations/expansions/expand.test.js +++ b/packages/modeling/src/operations/expansions/expand.test.js @@ -78,7 +78,6 @@ test('expand: expanding of a geom2 produces expected changes to points', (t) => const obs = expand({ delta: 2, corners: 'round', segments: 8 }, geometry) const pts = geom2.toPoints(obs) const exp = [ - [-9.414213562373096, -9.414213562373096], [-8, -10], [8, -10], [9.414213562373096, -9.414213562373096], @@ -89,7 +88,8 @@ test('expand: expanding of a geom2 produces expected changes to points', (t) => [-8, 10], [-9.414213562373096, 9.414213562373096], [-10, 8], - [-10, -8] + [-10, -8], + [-9.414213562373096, -9.414213562373096] ] t.notThrows(() => geom2.validate(obs)) t.is(pts.length, 12) @@ -134,7 +134,7 @@ test('expand: expanding of a geom3 produces expected changes to polygons', (t) = }) test('expand (options): offsetting of a complex geom2 produces expected offset geom2', (t) => { - const geometry = geom2.create([ + const geometry = geom2.fromSides([ [[-75, 75], [-75, -75]], [[-75, -75], [75, -75]], [[75, -75], [75, 75]], @@ -161,26 +161,26 @@ test('expand (options): offsetting of a complex geom2 produces expected offset g const obs = expand({ delta: 2, corners: 'edge' }, geometry) const pts = geom2.toPoints(obs) const exp = [ - [77, -77], [77, 77], [38, 77], [38, 2], [-38, 2], [-37.99999999999999, 77], [-77, 77], - [16.999999999999996, -42], - [6, -42], + [-77, -77], + [77, -77], [6, -27], [-6, -27], [-6.000000000000001, -42], [-17, -42], [-16.999999999999996, -8], [17, -8.000000000000004], - [-4, -21], - [3.9999999999999996, -21], + [16.999999999999996, -42], + [6, -42], [4, -13], [-4, -13], - [-77, -77] + [-4, -21], + [3.9999999999999996, -21] ] t.notThrows(() => geom2.validate(obs)) t.is(pts.length, 20) diff --git a/packages/modeling/src/operations/expansions/expandGeom2.js b/packages/modeling/src/operations/expansions/expandGeom2.js index 6ad88a81c..99d04d075 100644 --- a/packages/modeling/src/operations/expansions/expandGeom2.js +++ b/packages/modeling/src/operations/expansions/expandGeom2.js @@ -37,7 +37,7 @@ export const expandGeom2 = (options, geometry) => { // create a composite geometry from the new outlines const allsides = newoutlines.reduce((sides, newoutline) => sides.concat(geom2.toSides(geom2.fromPoints(newoutline))), []) - return geom2.create(allsides) + return geom2.fromSides(allsides) } export default expandGeom2 diff --git a/packages/modeling/src/operations/expansions/expandPath2.js b/packages/modeling/src/operations/expansions/expandPath2.js index 4d18d0cb7..22d381c33 100644 --- a/packages/modeling/src/operations/expansions/expandPath2.js +++ b/packages/modeling/src/operations/expansions/expandPath2.js @@ -20,7 +20,7 @@ const createGeometryFromClosedOffsets = (paths) => { const externalSides = geom2.toSides(geom2.fromPoints(path2.toPoints(externalPath))) const internalSides = geom2.toSides(geom2.fromPoints(path2.toPoints(internalPath))) externalSides.push(...internalSides) - return geom2.create(externalSides) + return geom2.fromSides(externalSides) } const createGeometryFromExpandedOpenPath = (paths, segments, corners, delta) => { diff --git a/packages/modeling/src/operations/expansions/offset.test.js b/packages/modeling/src/operations/expansions/offset.test.js index 4439dd58f..a8ac0a4fa 100644 --- a/packages/modeling/src/operations/expansions/offset.test.js +++ b/packages/modeling/src/operations/expansions/offset.test.js @@ -328,7 +328,6 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge obs = offset({ delta: 1, corners: 'round' }, geometry) pts = geom2.toPoints(obs) exp = [ - [-5, -6], [5, -6], [6, -5], [6, 5], @@ -341,7 +340,8 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge [-3, 6], [-5, 6], [-6, 5], - [-6, -5] + [-6, -5], + [-5, -6] ] t.notThrows(() => geom2.validate(obs)) t.true(comparePoints(pts, exp)) @@ -350,7 +350,6 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge obs = offset({ delta: -0.5, corners: 'round' }, geometry) pts = geom2.toPoints(obs) exp = [ - [-4.5, -4.5], [4.5, -4.5], [4.5, 4.5], [3.5, 4.5], @@ -359,7 +358,8 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge [-3, -0.5], [-3.5, 3.0616171314629196e-17], [-3.5, 4.5], - [-4.5, 4.5] + [-4.5, 4.5], + [-4.5, -4.5] ] t.notThrows(() => geom2.validate(obs)) t.true(comparePoints(pts, exp)) @@ -368,14 +368,14 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge obs = offset({ delta: 1, corners: 'edge' }, geometry) pts = geom2.toPoints(obs) exp = [ - [6, -6], [6, 6], [2, 6], [2, 1], [-2, 1], [-1.9999999999999996, 6], [-6, 6], - [-6, -6] + [-6, -6], + [6, -6] ] t.notThrows(() => geom2.validate(obs)) t.true(comparePoints(pts, exp)) @@ -384,7 +384,6 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge obs = offset({ delta: -0.5, corners: 'round', segments: 16 }, geometry) pts = geom2.toPoints(obs) exp = [ - [-4.5, -4.5], [4.5, -4.5], [4.5, 4.5], [3.5, 4.5], @@ -399,7 +398,8 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge [-3.4619397662556435, -0.19134171618254495], [-3.5, 3.061616997868383e-17], [-3.5, 4.5], - [-4.5, 4.5] + [-4.5, 4.5], + [-4.5, -4.5] ] t.notThrows(() => geom2.validate(obs)) t.true(comparePoints(pts, exp)) @@ -407,52 +407,53 @@ test('offset (options): offsetting of a simple geom2 produces expected offset ge test('offset (options): offsetting of a complex geom2 produces expected offset geom2', (t) => { const geometry = geom2.create([ - [[-75, 75], [-75, -75]], - [[-75, -75], [75, -75]], - [[75, -75], [75, 75]], - [[-40, 75], [-75, 75]], - [[75, 75], [40, 75]], - [[40, 75], [40, 0]], - [[40, 0], [-40, 0]], - [[-40, 0], [-40, 75]], - [[15, -10], [15, -40]], - [[-15, -10], [15, -10]], - [[-15, -40], [-15, -10]], - [[-8, -40], [-15, -40]], - [[15, -40], [8, -40]], - [[-8, -25], [-8, -40]], - [[8, -25], [-8, -25]], - [[8, -40], [8, -25]], - [[-2, -15], [-2, -19]], - [[-2, -19], [2, -19]], - [[2, -19], [2, -15]], - [[2, -15], [-2, -15]] + [ + [-75, -75], + [75, -75], + [75, 75], + [40, 75], + [40, 0], + [-40, 0], + [-40, 75], + [-75, 75] + ], + [ + [15, -40], + [8, -40], + [8, -25], + [-8, -25], + [-8, -40], + [-15, -40], + [-15, -10], + [15, -10] + ], + [[-2, -19], [2, -19], [2, -15], [-2, -15]] ]) // expand + const obs = offset({ delta: 2, corners: 'edge' }, geometry) const pts = geom2.toPoints(obs) const exp = [ - [77, -77], [77, 77], [38, 77], [38, 2], [-38, 2], [-37.99999999999999, 77], [-77, 77], - [13, -12], - [13, -38], + [-77, -77], + [77, -77], [10, -38], [10, -23], [-10, -23], [-10, -38], [-13, -38], [-13, -12], - [-4, -21], - [3.9999999999999996, -21], + [13, -12], + [13, -38], [4, -13], [-4, -13], - [-77, -77] + [-4, -21], + [3.9999999999999996, -21] ] t.notThrows(() => geom2.validate(obs)) t.is(pts.length, 20) @@ -482,7 +483,6 @@ test('offset (options): offsetting of round geom2 produces expected offset geom2 const obs = offset({ delta: -0.5, corners: 'round' }, geometry) const pts = geom2.toPoints(obs) const exp = [ - [9.490204518135641, 0], [8.767810140100096, 3.6317399864658007], [6.710590060510285, 6.7105900605102855], [3.6317399864658024, 8.767810140100096], @@ -497,7 +497,8 @@ test('offset (options): offsetting of round geom2 produces expected offset geom2 [4.440892098500626e-16, -9.490204518135641], [3.6317399864658007, -8.767810140100096], [6.7105900605102855, -6.710590060510285], - [8.767810140100096, -3.6317399864658024] + [8.767810140100096, -3.6317399864658024], + [9.490204518135641, 0] ] t.notThrows(() => geom2.validate(obs)) t.is(pts.length, 16) diff --git a/packages/modeling/src/operations/expansions/offsetGeom2.js b/packages/modeling/src/operations/expansions/offsetGeom2.js index bf0188253..2529d343a 100644 --- a/packages/modeling/src/operations/expansions/offsetGeom2.js +++ b/packages/modeling/src/operations/expansions/offsetGeom2.js @@ -41,7 +41,7 @@ export const offsetGeom2 = (options, geometry) => { // create a composite geometry from the new outlines const allsides = newoutlines.reduce((sides, newoutline) => sides.concat(geom2.toSides(geom2.fromPoints(newoutline))), []) - return geom2.create(allsides) + return geom2.fromSides(allsides) } export default offsetGeom2 diff --git a/packages/modeling/src/operations/extrusions/earcut/polygonHierarchy.js b/packages/modeling/src/operations/extrusions/earcut/polygonHierarchy.js index bd12bd36e..c3404bb67 100644 --- a/packages/modeling/src/operations/extrusions/earcut/polygonHierarchy.js +++ b/packages/modeling/src/operations/extrusions/earcut/polygonHierarchy.js @@ -31,7 +31,7 @@ export class PolygonHierarchy { const projected = slice.edges.map((e) => e.map((v) => this.to2D(v))) // compute polygon hierarchies, assign holes to solids - const geometry = geom2.create(projected) + const geometry = geom2.fromSides(projected) this.roots = assignHoles(geometry) } diff --git a/packages/modeling/src/operations/extrusions/extrudeFromSlices.test.js b/packages/modeling/src/operations/extrusions/extrudeFromSlices.test.js index 05594f321..f28b4e555 100644 --- a/packages/modeling/src/operations/extrusions/extrudeFromSlices.test.js +++ b/packages/modeling/src/operations/extrusions/extrudeFromSlices.test.js @@ -17,23 +17,23 @@ test('extrudeFromSlices (defaults)', (t) => { let geometry3 = extrudeFromSlices({ }, geometry2) let pts = geom3.toPoints(geometry3) const exp = [ - [[10, -10, 0], [10, 10, 0], [10, 10, 1]], - [[10, -10, 0], [10, 10, 1], [10, -10, 1]], [[10, 10, 0], [-10, 10, 0], [-10, 10, 1]], [[10, 10, 0], [-10, 10, 1], [10, 10, 1]], [[-10, 10, 0], [-10, -10, 0], [-10, -10, 1]], [[-10, 10, 0], [-10, -10, 1], [-10, 10, 1]], [[-10, -10, 0], [10, -10, 0], [10, -10, 1]], [[-10, -10, 0], [10, -10, 1], [-10, -10, 1]], - [[-10, -10, 1], [10, -10, 1], [10, 10, 1]], - [[10, 10, 1], [-10, 10, 1], [-10, -10, 1]], - [[10, 10, 0], [10, -10, 0], [-10, -10, 0]], - [[-10, -10, 0], [-10, 10, 0], [10, 10, 0]] + [[10, -10, 0], [10, 10, 0], [10, 10, 1]], + [[10, -10, 0], [10, 10, 1], [10, -10, 1]], + [[10, -10, 1], [10, 10, 1], [-10, 10, 1]], + [[-10, 10, 1], [-10, -10, 1], [10, -10, 1]], + [[-10, 10, 0], [10, 10, 0], [10, -10, 0]], + [[10, -10, 0], [-10, -10, 0], [-10, 10, 0]] ] t.is(pts.length, 12) t.true(comparePolygonsAsPoints(pts, exp)) - const poly2 = poly3.create([[10, 10, 0], [-10, 10, 0], [-10, -10, 0], [10, -10, 0]]) + const poly2 = poly3.create([[-10, 10, 0], [-10, -10, 0], [10, -10, 0], [10, 10, 0]]) geometry3 = extrudeFromSlices({ }, poly2) pts = geom3.toPoints(geometry3) @@ -113,18 +113,10 @@ test('extrudeFromSlices (changing shape, changing dimensions)', (t) => { }) test('extrudeFromSlices (holes)', (t) => { - const geometry2 = geom2.create( - [ - [[-10, 10], [-10, -10]], - [[-10, -10], [10, -10]], - [[10, -10], [10, 10]], - [[10, 10], [-10, 10]], - [[-5, -5], [-5, 5]], - [[5, -5], [-5, -5]], - [[5, 5], [5, -5]], - [[-5, 5], [5, 5]] - ] - ) + const geometry2 = geom2.create([ + [[-10, 10], [-10, -10], [10, -10], [10, 10]], + [[-5, -5], [-5, 5], [5, 5], [5, -5]] + ]) const geometry3 = extrudeFromSlices({ }, geometry2) const pts = geom3.toPoints(geometry3) const exp = [ @@ -138,12 +130,12 @@ test('extrudeFromSlices (holes)', (t) => { [[10, 10, 0], [-10, 10, 1], [10, 10, 1]], [[-5, -5, 0], [-5, 5, 0], [-5, 5, 1]], [[-5, -5, 0], [-5, 5, 1], [-5, -5, 1]], - [[5, -5, 0], [-5, -5, 0], [-5, -5, 1]], - [[5, -5, 0], [-5, -5, 1], [5, -5, 1]], - [[5, 5, 0], [5, -5, 0], [5, -5, 1]], - [[5, 5, 0], [5, -5, 1], [5, 5, 1]], [[-5, 5, 0], [5, 5, 0], [5, 5, 1]], [[-5, 5, 0], [5, 5, 1], [-5, 5, 1]], + [[5, 5, 0], [5, -5, 0], [5, -5, 1]], + [[5, 5, 0], [5, -5, 1], [5, 5, 1]], + [[5, -5, 0], [-5, -5, 0], [-5, -5, 1]], + [[5, -5, 0], [-5, -5, 1], [5, -5, 1]], [[10, -10, 1], [10, 10, 1], [5, 5, 1]], [[-5, 5, 1], [5, 5, 1], [10, 10, 1]], [[10, -10, 1], [5, 5, 1], [5, -5, 1]], diff --git a/packages/modeling/src/operations/extrusions/extrudeLinear.test.js b/packages/modeling/src/operations/extrusions/extrudeLinear.test.js index 24b10af6b..a3e98f39d 100644 --- a/packages/modeling/src/operations/extrusions/extrudeLinear.test.js +++ b/packages/modeling/src/operations/extrusions/extrudeLinear.test.js @@ -9,7 +9,7 @@ import { geom2, geom3, path2 } from '../../geometries/index.js' import { extrudeLinear } from './index.js' test('extrudeLinear (defaults)', (t) => { - const geometry2 = geom2.fromPoints([[5, 5], [-5, 5], [-5, -5], [5, -5]]) + const geometry2 = geom2.fromPoints([[5, -5], [5, 5], [-5, 5], [-5, -5]]) const geometry3 = extrudeLinear({ }, geometry2) const pts = geom3.toPoints(geometry3) @@ -33,7 +33,7 @@ test('extrudeLinear (defaults)', (t) => { }) test('extrudeLinear (no twist)', (t) => { - const geometry2 = geom2.fromPoints([[5, 5], [-5, 5], [-5, -5], [5, -5]]) + const geometry2 = geom2.fromPoints([[5, -5], [5, 5], [-5, 5], [-5, -5]]) let geometry3 = extrudeLinear({ height: 15 }, geometry2) let pts = geom3.toPoints(geometry3) @@ -77,7 +77,7 @@ test('extrudeLinear (no twist)', (t) => { }) test('extrudeLinear (twist)', (t) => { - const geometry2 = geom2.fromPoints([[5, 5], [-5, 5], [-5, -5], [5, -5]]) + const geometry2 = geom2.fromPoints([[5, -5], [5, 5], [-5, 5], [-5, -5]]) let geometry3 = extrudeLinear({ height: 15, twistAngle: -TAU / 8 }, geometry2) let pts = geom3.toPoints(geometry3) @@ -150,14 +150,8 @@ test('extrudeLinear (twist)', (t) => { test('extrudeLinear (holes)', (t) => { const geometry2 = geom2.create([ - [[-5, 5], [-5, -5]], - [[-5, -5], [5, -5]], - [[5, -5], [5, 5]], - [[5, 5], [-5, 5]], - [[-2, -2], [-2, 2]], - [[2, -2], [-2, -2]], - [[2, 2], [2, -2]], - [[-2, 2], [2, 2]] + [[-5, 5], [-5, -5], [5, -5], [5, 5]], + [[-2, -2], [-2, 2], [2, 2], [2, -2]] ]) const geometry3 = extrudeLinear({ height: 15 }, geometry2) const pts = geom3.toPoints(geometry3) @@ -172,12 +166,12 @@ test('extrudeLinear (holes)', (t) => { [[5, 5, 0], [-5, 5, 15], [5, 5, 15]], [[-2, -2, 0], [-2, 2, 0], [-2, 2, 15]], [[-2, -2, 0], [-2, 2, 15], [-2, -2, 15]], - [[2, -2, 0], [-2, -2, 0], [-2, -2, 15]], - [[2, -2, 0], [-2, -2, 15], [2, -2, 15]], - [[2, 2, 0], [2, -2, 0], [2, -2, 15]], - [[2, 2, 0], [2, -2, 15], [2, 2, 15]], [[-2, 2, 0], [2, 2, 0], [2, 2, 15]], [[-2, 2, 0], [2, 2, 15], [-2, 2, 15]], + [[2, 2, 0], [2, -2, 0], [2, -2, 15]], + [[2, 2, 0], [2, -2, 15], [2, 2, 15]], + [[2, -2, 0], [-2, -2, 0], [-2, -2, 15]], + [[2, -2, 0], [-2, -2, 15], [2, -2, 15]], [[5, -5, 15], [5, 5, 15], [2, 2, 15]], [[-2, 2, 15], [2, 2, 15], [5, 5, 15]], [[5, -5, 15], [2, 2, 15], [2, -2, 15]], @@ -201,7 +195,7 @@ test('extrudeLinear (holes)', (t) => { }) test('extrudeLinear (path2)', (t) => { - const geometry2 = path2.fromPoints({ closed: true }, [[0, 0], [12, 0], [6, 10]]) + const geometry2 = path2.fromPoints({ closed: true }, [[6, 10], [0, 0], [12, 0]]) const geometry3 = extrudeLinear({ height: 15 }, geometry2) t.notThrows(() => geom3.validate(geometry3)) const pts = geom3.toPoints(geometry3) diff --git a/packages/modeling/src/operations/extrusions/extrudeRectangular.test.js b/packages/modeling/src/operations/extrusions/extrudeRectangular.test.js index 6876b0020..5a9c7db66 100644 --- a/packages/modeling/src/operations/extrusions/extrudeRectangular.test.js +++ b/packages/modeling/src/operations/extrusions/extrudeRectangular.test.js @@ -55,14 +55,8 @@ test('extrudeRectangular (segments = 8, round)', (t) => { test('extrudeRectangular (holes)', (t) => { const geometry2 = geom2.create([ - [[15, 15], [-15, 15]], - [[-15, 15], [-15, -15]], - [[-15, -15], [15, -15]], - [[15, -15], [15, 15]], - [[-5, 5], [5, 5]], - [[5, 5], [5, -5]], - [[5, -5], [-5, -5]], - [[-5, -5], [-5, 5]] + [[-15, 15], [-15, -15], [15, -15], [15, 15]], + [[5, 5], [5, -5], [-5, -5], [-5, 5]] ]) const obs = extrudeRectangular({ size: 2, height: 15, segments: 16, corners: 'round' }, geometry2) diff --git a/packages/modeling/src/operations/extrusions/extrudeRectangularGeom2.js b/packages/modeling/src/operations/extrusions/extrudeRectangularGeom2.js index bdde0b4f9..ba4c3ae48 100644 --- a/packages/modeling/src/operations/extrusions/extrudeRectangularGeom2.js +++ b/packages/modeling/src/operations/extrusions/extrudeRectangularGeom2.js @@ -38,7 +38,7 @@ export const extrudeRectangularGeom2 = (options, geometry) => { // create a composite geometry const allsides = newparts.reduce((sides, part) => sides.concat(geom2.toSides(part)), []) - const newgeometry = geom2.create(allsides) + const newgeometry = geom2.fromSides(allsides) return extrudeLinearGeom2(options, newgeometry) } diff --git a/packages/modeling/src/operations/extrusions/extrudeRotate.js b/packages/modeling/src/operations/extrusions/extrudeRotate.js index 099c9a3f8..2281a2bc1 100644 --- a/packages/modeling/src/operations/extrusions/extrudeRotate.js +++ b/packages/modeling/src/operations/extrusions/extrudeRotate.js @@ -93,7 +93,7 @@ export const extrudeRotate = (options, geometry) => { return [point0, point1] }) // recreate the geometry from the (-) capped points - geometry = geom2.reverse(geom2.create(shapeSides)) + geometry = geom2.reverse(geom2.fromSides(shapeSides)) geometry = mirrorX(geometry) } else if (pointsWithPositiveX.length >= pointsWithNegativeX.length) { shapeSides = shapeSides.map((side) => { @@ -104,7 +104,7 @@ export const extrudeRotate = (options, geometry) => { return [point0, point1] }) // recreate the geometry from the (+) capped points - geometry = geom2.create(shapeSides) + geometry = geom2.fromSides(shapeSides) } } diff --git a/packages/modeling/src/operations/extrusions/extrudeRotate.test.js b/packages/modeling/src/operations/extrusions/extrudeRotate.test.js index 3d135347f..605cbded8 100644 --- a/packages/modeling/src/operations/extrusions/extrudeRotate.test.js +++ b/packages/modeling/src/operations/extrusions/extrudeRotate.test.js @@ -24,18 +24,18 @@ test('extrudeRotate: (angle) extruding of a geom2 produces an expected geom3', ( let geometry3 = extrudeRotate({ segments: 4, angle: TAU / 8 }, geometry2) let pts = geom3.toPoints(geometry3) const exp = [ - [[10, 0, 8], [26, 0, 8], [18.38477631085024, 18.384776310850235, 8]], - [[10, 0, 8], [18.38477631085024, 18.384776310850235, 8], [7.0710678118654755, 7.071067811865475, 8]], [[10, 0, -8], [10, 0, 8], [7.0710678118654755, 7.071067811865475, 8]], [[10, 0, -8], [7.0710678118654755, 7.071067811865475, 8], [7.0710678118654755, 7.071067811865475, -8]], [[26, 0, -8], [10, 0, -8], [7.0710678118654755, 7.071067811865475, -8]], [[26, 0, -8], [7.0710678118654755, 7.071067811865475, -8], [18.38477631085024, 18.384776310850235, -8]], [[26, 0, 8], [26, 0, -8], [18.38477631085024, 18.384776310850235, -8]], [[26, 0, 8], [18.38477631085024, 18.384776310850235, -8], [18.38477631085024, 18.384776310850235, 8]], - [[7.0710678118654755, 7.071067811865475, -8], [7.0710678118654755, 7.071067811865475, 8], [18.38477631085024, 18.384776310850235, 8]], - [[18.38477631085024, 18.384776310850235, 8], [18.38477631085024, 18.384776310850235, -8], [7.0710678118654755, 7.071067811865475, -8]], - [[26, 0, 8], [10, 0, 8], [10, 0, -8]], - [[10, 0, -8], [26, 0, -8], [26, 0, 8]] + [[10, 0, 8], [26, 0, 8], [18.38477631085024, 18.384776310850235, 8]], + [[10, 0, 8], [18.38477631085024, 18.384776310850235, 8], [7.0710678118654755, 7.071067811865475, 8]], + [[18.38477631085024, 18.384776310850235, -8], [7.0710678118654755, 7.071067811865475, -8], [7.0710678118654755, 7.071067811865475, 8]], + [[7.0710678118654755, 7.071067811865475, 8], [18.38477631085024, 18.384776310850235, 8], [18.38477631085024, 18.384776310850235, -8]], + [[10, 0, 8], [10, 0, -8], [26, 0, -8]], + [[26, 0, -8], [26, 0, 8], [10, 0, 8]] ] t.notThrows(() => geom3.validate(geometry3)) t.is(pts.length, 12) @@ -65,7 +65,7 @@ test('extrudeRotate: (startAngle) extruding of a geom2 produces an expected geom ] t.notThrows(() => geom3.validate(geometry3)) t.is(pts.length, 40) - t.true(comparePoints(pts[0], exp)) + t.true(comparePoints(pts[6], exp)) geometry3 = extrudeRotate({ segments: 5, startAngle: -TAU / 8 }, geometry2) pts = geom3.toPoints(geometry3) @@ -76,7 +76,7 @@ test('extrudeRotate: (startAngle) extruding of a geom2 produces an expected geom ] t.notThrows(() => geom3.validate(geometry3)) t.is(pts.length, 40) - t.true(comparePoints(pts[0], exp)) + t.true(comparePoints(pts[6], exp)) }) test('extrudeRotate: (segments) extruding of a geom2 produces an expected geom3', (t) => { @@ -115,14 +115,14 @@ test('extrudeRotate: (overlap +/-) extruding of a geom2 produces an expected geo let obs = extrudeRotate({ segments: 4, angle: TAU / 4 }, geometry) let pts = geom3.toPoints(obs) let exp = [ - [[0, 0, 8], [7, 0, 8], [0, 7, 8]], [[7, 0, -8], [0, 0, -8], [0, 7, -8]], [[7, 0, 8], [7, 0, -8], [0, 7, -8]], [[7, 0, 8], [0, 7, -8], [0, 7, 8]], - [[0, 0, -8], [0, 0, 8], [0, 7, 8]], + [[0, 0, 8], [7, 0, 8], [0, 7, 8]], [[0, 7, 8], [0, 7, -8], [0, 0, -8]], - [[7, 0, 8], [0, 0, 8], [0, 0, -8]], - [[0, 0, -8], [7, 0, -8], [7, 0, 8]] + [[0, 0, -8], [0, 0, 8], [0, 7, 8]], + [[0, 0, -8], [7, 0, -8], [7, 0, 8]], + [[7, 0, 8], [0, 0, 8], [0, 0, -8]] ] t.notThrows(() => geom3.validate(obs)) t.is(pts.length, 8) @@ -134,24 +134,24 @@ test('extrudeRotate: (overlap +/-) extruding of a geom2 produces an expected geo obs = extrudeRotate({ segments: 8, angle: TAU / 4 }, geometry) pts = geom3.toPoints(obs) exp = [ + [[0, 0, 8], [1, 0, 8], [0.7071067811865476, 0.7071067811865475, 8]], [[1, 0, -8], [0, 0, -8], [0.7071067811865476, 0.7071067811865475, -8]], [[2, 0, 4], [1, 0, -8], [0.7071067811865476, 0.7071067811865475, -8]], [[2, 0, 4], [0.7071067811865476, 0.7071067811865475, -8], [1.4142135623730951, 1.414213562373095, 4]], [[1, 0, 8], [2, 0, 4], [1.4142135623730951, 1.414213562373095, 4]], [[1, 0, 8], [1.4142135623730951, 1.414213562373095, 4], [0.7071067811865476, 0.7071067811865475, 8]], - [[0, 0, 8], [1, 0, 8], [0.7071067811865476, 0.7071067811865475, 8]], + [[0, 0, 8], [0.7071067811865476, 0.7071067811865475, 8], [0, 1, 8]], [[0.7071067811865476, 0.7071067811865475, -8], [0, 0, -8], [0, 1, -8]], [[1.4142135623730951, 1.414213562373095, 4], [0.7071067811865476, 0.7071067811865475, -8], [0, 1, -8]], [[1.4142135623730951, 1.414213562373095, 4], [0, 1, -8], [0, 2, 4]], [[0.7071067811865476, 0.7071067811865475, 8], [1.4142135623730951, 1.414213562373095, 4], [0, 2, 4]], [[0.7071067811865476, 0.7071067811865475, 8], [0, 2, 4], [0, 1, 8]], - [[0, 0, 8], [0.7071067811865476, 0.7071067811865475, 8], [0, 1, 8]], - [[0, 1, -8], [0, 0, -8], [0, 0, 8]], - [[0, 0, 8], [0, 1, 8], [0, 2, 4]], - [[0, 2, 4], [0, 1, -8], [0, 0, 8]], - [[0, 0, 8], [0, 0, -8], [1, 0, -8]], - [[2, 0, 4], [1, 0, 8], [0, 0, 8]], - [[0, 0, 8], [1, 0, -8], [2, 0, 4]] + [[0, 0, -8], [0, 0, 8], [0, 1, 8]], + [[0, 1, 8], [0, 2, 4], [0, 1, -8]], + [[0, 1, -8], [0, 0, -8], [0, 1, 8]], + [[1, 0, 8], [0, 0, 8], [0, 0, -8]], + [[1, 0, -8], [2, 0, 4], [1, 0, 8]], + [[1, 0, 8], [0, 0, -8], [1, 0, -8]] ] t.notThrows(() => geom3.validate(obs)) t.is(pts.length, 18) diff --git a/packages/modeling/src/operations/modifiers/snap.js b/packages/modeling/src/operations/modifiers/snap.js index c0400ed2e..fee68ac4d 100644 --- a/packages/modeling/src/operations/modifiers/snap.js +++ b/packages/modeling/src/operations/modifiers/snap.js @@ -24,7 +24,7 @@ const snapGeom2 = (geometry) => { let newsides = sides.map((side) => [vec2.snap(vec2.create(), side[0], epsilon), vec2.snap(vec2.create(), side[1], epsilon)]) // snap can produce sides with zero (0) length, remove those newsides = newsides.filter((side) => !vec2.equals(side[0], side[1])) - return geom2.create(newsides) + return geom2.fromSides(newsides) } const snapGeom3 = (geometry) => { diff --git a/packages/modeling/src/operations/modifiers/snap.test.js b/packages/modeling/src/operations/modifiers/snap.test.js index 8a8e115ef..1ab4a4f88 100644 --- a/packages/modeling/src/operations/modifiers/snap.test.js +++ b/packages/modeling/src/operations/modifiers/snap.test.js @@ -68,20 +68,24 @@ test('snap: snap of a geom2 produces an expected geom2', (t) => { t.true(comparePoints(pts, exp)) pts = geom2.toPoints(results[1]) - exp = [[-0.5, -0.5], [0.5, -0.5], [0.5, 0.5], [-0.5, 0.5]] + exp = [[0.5, -0.5], [0.5, 0.5], [-0.5, 0.5], [-0.5, -0.5]] t.true(comparePoints(pts, exp)) pts = geom2.toPoints(results[2]) exp = [ - [-0.6666666666666666, -0.6666666666666666], [0.6666666666666666, -0.6666666666666666], - [0.6666666666666666, 0.6666666666666666], [-0.6666666666666666, 0.6666666666666666] + [0.6666666666666666, -0.6666666666666666], + [0.6666666666666666, 0.6666666666666666], + [-0.6666666666666666, 0.6666666666666666], + [-0.6666666666666666, -0.6666666666666666] ] t.true(comparePoints(pts, exp)) pts = geom2.toPoints(results[3]) exp = [ - [-1570.7963267948967, -1570.7963267948967], [1570.7963267948967, -1570.7963267948967], - [1570.7963267948967, 1570.7963267948967], [-1570.7963267948967, 1570.7963267948967] + [1570.7963267948967, -1570.7963267948967], + [1570.7963267948967, 1570.7963267948967], + [-1570.7963267948967, 1570.7963267948967], + [-1570.7963267948967, -1570.7963267948967] ] t.true(comparePoints(pts, exp)) }) From b2417b42f00c71a116394ccd495d7b061a37e35d Mon Sep 17 00:00:00 2001 From: Kenny Daniel Date: Tue, 27 Dec 2022 14:28:17 -0800 Subject: [PATCH 03/15] Project to 2D points --- .../modeling/src/geometries/geom2/validate.js | 5 ++++- .../src/operations/booleans/martinez/index.js | 15 +++++++++++---- .../src/operations/booleans/unionGeom2.test.js | 6 ++++-- .../modeling/src/operations/extrusions/project.js | 8 +++++++- .../src/operations/extrusions/project.test.js | 6 +++--- 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/packages/modeling/src/geometries/geom2/validate.js b/packages/modeling/src/geometries/geom2/validate.js index 13bbbdd03..fc66d1bf4 100644 --- a/packages/modeling/src/geometries/geom2/validate.js +++ b/packages/modeling/src/geometries/geom2/validate.js @@ -18,8 +18,11 @@ export const validate = (object) => { throw new Error('invalid geom2 structure') } - // check for duplicate points object.outlines.forEach((outline) => { + if (outline.length < 3) { + throw new Error('geom2 outlines must contain at least 3 points') + } + // check for duplicate points for (let i = 0; i < outline.length; i++) { const j = (i + 1) % outline.length if (vec2.equals(outline[i], outline[j])) { diff --git a/packages/modeling/src/operations/booleans/martinez/index.js b/packages/modeling/src/operations/booleans/martinez/index.js index d229167ea..0aedd10e9 100644 --- a/packages/modeling/src/operations/booleans/martinez/index.js +++ b/packages/modeling/src/operations/booleans/martinez/index.js @@ -16,6 +16,7 @@ import { XOR } from './operation.js' import * as geom2 from '../../../geometries/geom2/index.js' +import * as vec2 from '../../../maths/vec2/index.js' const EMPTY = [] @@ -76,10 +77,14 @@ const compareBBoxes = (subject, clipping, sbbox, cbbox, operation) => { * Convert from geom2 to martinez data structure */ const toMartinez = (geometry) => { - const outlines = geom2.toOutlines(geometry) - outlines.forEach((outline) => { + const outlines = [] + geom2.toOutlines(geometry).forEach((outline) => { // Martinez expects first point == last point - outline.push(outline[0]) + if (vec2.equals(outline[0], outline[outline.length - 1])) { + outlines.push(outline) + } else { + outlines.push([...outline, outline[0]]) + } }) return [outlines] } @@ -89,7 +94,9 @@ const toMartinez = (geometry) => { */ const fromOutlines = (outlines) => { outlines.forEach((outline) => { - outline.pop() // first == last point + if (vec2.equals(outline[0], outline[outline.length - 1])) { + outline.pop() // first == last point + } }) return geom2.create(outlines) } diff --git a/packages/modeling/src/operations/booleans/unionGeom2.test.js b/packages/modeling/src/operations/booleans/unionGeom2.test.js index 6d4bb6e86..1dc10af81 100644 --- a/packages/modeling/src/operations/booleans/unionGeom2.test.js +++ b/packages/modeling/src/operations/booleans/unionGeom2.test.js @@ -155,10 +155,12 @@ test('union of geom2 with closing issues #15', (t) => { [-68.37258141465594, -2.8325337698763633], [-49.58572929483431, -14.975526866122138], [-49.53755741140094, -15.184271834314728], + [-54.61235529924313, -11.790667693213138], [-48.16645938811709, -15.863171735891832], - [-49.057272912186846, -15.486616385421712] + [-49.057272912186846, -15.486616385421712], + [-49.10586702080816, -15.276041773521108] ] t.notThrows(() => geom2.validate(obs)) - t.is(pts.length, 18) // number of sides in union + t.is(pts.length, 20) // number of sides in union t.true(comparePoints(pts, exp)) }) diff --git a/packages/modeling/src/operations/extrusions/project.js b/packages/modeling/src/operations/extrusions/project.js index 9505ecaeb..4c2ca4baf 100644 --- a/packages/modeling/src/operations/extrusions/project.js +++ b/packages/modeling/src/operations/extrusions/project.js @@ -3,6 +3,7 @@ import flatten from '../../utils/flatten.js' import aboutEqualNormals from '../../maths/utils/aboutEqualNormals.js' import * as plane from '../../maths/plane/index.js' import * as mat4 from '../../maths/mat4/index.js' +import * as vec2 from '../../maths/vec2/index.js' import * as geom2 from '../../geometries/geom2/index.js' import * as geom3 from '../../geometries/geom3/index.js' @@ -48,7 +49,12 @@ const projectGeom3 = (options, geometry) => { projpolys = projpolys.sort((a, b) => poly3.measureArea(b) - poly3.measureArea(a)) // convert polygons to geometry, and union all pieces into a single geometry - const projgeoms = projpolys.map((p) => geom2.fromPoints(p.vertices)) + const projgeoms = projpolys.map((p) => { + // This clones the points from vec3 to vec2 + const cloned = p.vertices.map(vec2.clone) + return geom2.fromPoints(cloned) + }) + return unionGeom2(projgeoms) } diff --git a/packages/modeling/src/operations/extrusions/project.test.js b/packages/modeling/src/operations/extrusions/project.test.js index 7093bd6cd..a7b69e337 100644 --- a/packages/modeling/src/operations/extrusions/project.test.js +++ b/packages/modeling/src/operations/extrusions/project.test.js @@ -22,18 +22,18 @@ test('project (defaults)', (t) => { t.is(results[3], geometry3) t.is(results[4], geometry4) - const result = project({ }, torus({ outerSegments: 4 })) + const result = project({ }, torus({ innerSegments: 4, outerSegments: 4 })) t.notThrows(() => geom2.validate(result)) const pts = geom2.toPoints(result) const exp = [ [-5, 0], [0, -5], [5, 0], - [-3, 0], + [0, 5], [0, 3], [3, 0], [0, -3], - [0, 5] + [-3, 0] ] t.true(comparePoints(pts, exp)) }) From 49398f5e9209308f2d2b3240268430305c0178d6 Mon Sep 17 00:00:00 2001 From: Kenny Daniel Date: Mon, 2 Jan 2023 15:25:25 -0800 Subject: [PATCH 04/15] Add martinez tests and implement a possible fix --- .../booleans/martinez/connect_edges.js | 6 +++++- .../operations/booleans/unionGeom2.test.js | 20 +++++++++++++++++++ .../src/operations/extrusions/project.test.js | 12 ++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/packages/modeling/src/operations/booleans/martinez/connect_edges.js b/packages/modeling/src/operations/booleans/martinez/connect_edges.js index 3fa7e849f..5cf579486 100644 --- a/packages/modeling/src/operations/booleans/martinez/connect_edges.js +++ b/packages/modeling/src/operations/booleans/martinez/connect_edges.js @@ -99,7 +99,11 @@ const initializeContourFromContext = (event, contours, contourId) => { // result". const lowerContourId = prevInResult.outputContourId const lowerResultTransition = prevInResult.resultTransition - if (lowerResultTransition > 0) { + if (lowerContourId < 0) { + console.log("WTF contour invalid", lowerContourId, contours[lowerContourId]) + contour.holeOf = null + contour.depth = 0 + } else if (lowerResultTransition > 0) { // We are inside. Now we have to check if the thing below us is another hole or // an exterior contour. const lowerContour = contours[lowerContourId] diff --git a/packages/modeling/src/operations/booleans/unionGeom2.test.js b/packages/modeling/src/operations/booleans/unionGeom2.test.js index 1dc10af81..a0addf503 100644 --- a/packages/modeling/src/operations/booleans/unionGeom2.test.js +++ b/packages/modeling/src/operations/booleans/unionGeom2.test.js @@ -164,3 +164,23 @@ test('union of geom2 with closing issues #15', (t) => { t.is(pts.length, 20) // number of sides in union t.true(comparePoints(pts, exp)) }) + +test('union of geom2 with colinear edge (martinez issue #155)', (t) => { + // This test is a minimal example extracted from: + // project({ axis: [0, 1, 0], origin: [0, -1, 0] }, torus({ innerSegments: 8, outerSegments: 4 })) + const g1 = geom2.create([[ + [ -5, 0 ], + [ 4.707106781186547, 0.7071067811865477 ], + [ 3.477592250072517, 0.7836116248912244 ], + [ 4, 1 ], + ]]) + const g2 = geom2.create([[ + [ 4.707106781186547, 0.7071067811865477 ], + [ 4, 1 ], + [ 0, 1 ], + ]]) + const result = union(g1, g2) + const pts = geom2.toPoints(result) + t.notThrows(() => geom2.validate(result)) + t.is(pts.length, 5) +}) diff --git a/packages/modeling/src/operations/extrusions/project.test.js b/packages/modeling/src/operations/extrusions/project.test.js index a7b69e337..ad975b102 100644 --- a/packages/modeling/src/operations/extrusions/project.test.js +++ b/packages/modeling/src/operations/extrusions/project.test.js @@ -38,7 +38,7 @@ test('project (defaults)', (t) => { t.true(comparePoints(pts, exp)) }) -test('project (X and Y axis)', (t) => { +test('project torus (X and Y axis)', (t) => { let result = project({ axis: [1, 0, 0], origin: [1, 0, 0] }, torus({ outerSegments: 4 })) t.notThrows(() => geom2.validate(result)) let pts = geom2.toPoints(result) @@ -127,3 +127,13 @@ test('project (X and Y axis)', (t) => { ] t.true(comparePoints(pts, exp)) }) + +test('project torus (martinez issue #155)', (t) => { + const result = project( + { axis: [0, 1, 0], origin: [0, -1, 0] }, + torus({ innerSegments: 8, outerSegments: 4}) + ) + const pts = geom2.toPoints(result) + t.notThrows(() => geom2.validate(result)) + t.is(pts.length, 5) +}) From dc4a2deedfa5a91234f698ee66e0f14eab6a1289 Mon Sep 17 00:00:00 2001 From: Kenny Daniel Date: Tue, 3 Jan 2023 00:39:12 -0800 Subject: [PATCH 05/15] Filter out invalid outlines from martinez --- .../booleans/martinez/connect_edges.js | 1 - .../src/operations/booleans/martinez/index.js | 1 + .../operations/booleans/unionGeom2.test.js | 22 +++++++++++++------ .../src/operations/extrusions/project.test.js | 3 --- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/modeling/src/operations/booleans/martinez/connect_edges.js b/packages/modeling/src/operations/booleans/martinez/connect_edges.js index 5cf579486..b6103b77e 100644 --- a/packages/modeling/src/operations/booleans/martinez/connect_edges.js +++ b/packages/modeling/src/operations/booleans/martinez/connect_edges.js @@ -100,7 +100,6 @@ const initializeContourFromContext = (event, contours, contourId) => { const lowerContourId = prevInResult.outputContourId const lowerResultTransition = prevInResult.resultTransition if (lowerContourId < 0) { - console.log("WTF contour invalid", lowerContourId, contours[lowerContourId]) contour.holeOf = null contour.depth = 0 } else if (lowerResultTransition > 0) { diff --git a/packages/modeling/src/operations/booleans/martinez/index.js b/packages/modeling/src/operations/booleans/martinez/index.js index 0aedd10e9..5ee6005e1 100644 --- a/packages/modeling/src/operations/booleans/martinez/index.js +++ b/packages/modeling/src/operations/booleans/martinez/index.js @@ -98,6 +98,7 @@ const fromOutlines = (outlines) => { outline.pop() // first == last point } }) + outlines = outlines.filter((o) => o.length >= 3) return geom2.create(outlines) } diff --git a/packages/modeling/src/operations/booleans/unionGeom2.test.js b/packages/modeling/src/operations/booleans/unionGeom2.test.js index a0addf503..5d2a20674 100644 --- a/packages/modeling/src/operations/booleans/unionGeom2.test.js +++ b/packages/modeling/src/operations/booleans/unionGeom2.test.js @@ -169,18 +169,26 @@ test('union of geom2 with colinear edge (martinez issue #155)', (t) => { // This test is a minimal example extracted from: // project({ axis: [0, 1, 0], origin: [0, -1, 0] }, torus({ innerSegments: 8, outerSegments: 4 })) const g1 = geom2.create([[ - [ -5, 0 ], - [ 4.707106781186547, 0.7071067811865477 ], - [ 3.477592250072517, 0.7836116248912244 ], - [ 4, 1 ], + [-5, 0], + [4.707106781186547, 0.7071067811865477], + [3.477592250072517, 0.7836116248912244], + [4, 1], ]]) const g2 = geom2.create([[ - [ 4.707106781186547, 0.7071067811865477 ], - [ 4, 1 ], - [ 0, 1 ], + [4.707106781186547, 0.7071067811865477], + [4, 1], + [0, 1], ]]) + const exp = [ + [-5, 0], + [4.707106781186547, 0.7071067811865477], + [4, 1], + [0, 1], + [2.564081902288895, 0.8404535446987661] + ] const result = union(g1, g2) const pts = geom2.toPoints(result) t.notThrows(() => geom2.validate(result)) t.is(pts.length, 5) + t.true(comparePoints(pts, exp)) }) diff --git a/packages/modeling/src/operations/extrusions/project.test.js b/packages/modeling/src/operations/extrusions/project.test.js index ad975b102..3cf61bc60 100644 --- a/packages/modeling/src/operations/extrusions/project.test.js +++ b/packages/modeling/src/operations/extrusions/project.test.js @@ -121,8 +121,6 @@ test('project torus (X and Y axis)', (t) => { [-4.707106781186547, 0.7071067811865477], [-4.831469612302545, 0.5555702330196022], [-4.923879532511286, 0.3826834323650904], - [0, -0.8314696123025452], - [0, -0.8314696123025453], [-4.98078528040323, 0.19509032201612872] ] t.true(comparePoints(pts, exp)) @@ -135,5 +133,4 @@ test('project torus (martinez issue #155)', (t) => { ) const pts = geom2.toPoints(result) t.notThrows(() => geom2.validate(result)) - t.is(pts.length, 5) }) From 0f87582be81c4a2aa80bbc31e4bb23b948c3306c Mon Sep 17 00:00:00 2001 From: Kenny Daniel Date: Tue, 3 Jan 2023 00:52:02 -0800 Subject: [PATCH 06/15] Remove uses of geom2.fromSides --- .../modeling/src/geometries/geom2/create.d.ts | 3 +- .../src/geometries/geom2/toOutlines.test.js | 52 +++++++------- .../src/geometries/geom2/transform.test.js | 2 +- .../operations/booleans/unionGeom2.test.js | 71 ++++++++++--------- .../src/operations/expansions/expand.test.js | 48 +++++++------ .../modeling/src/operations/modifiers/snap.js | 11 +-- .../src/operations/modifiers/snap.test.js | 10 +-- 7 files changed, 104 insertions(+), 93 deletions(-) diff --git a/packages/modeling/src/geometries/geom2/create.d.ts b/packages/modeling/src/geometries/geom2/create.d.ts index 28ef2a358..1b043f15b 100644 --- a/packages/modeling/src/geometries/geom2/create.d.ts +++ b/packages/modeling/src/geometries/geom2/create.d.ts @@ -1,7 +1,6 @@ import Geom2 from './type' -import Poly2 from './type' import Vec2 from '../../maths/vec2/type' export default create -declare function create(polygons?: Array): Geom2 +declare function create(outlines?: Array>): Geom2 diff --git a/packages/modeling/src/geometries/geom2/toOutlines.test.js b/packages/modeling/src/geometries/geom2/toOutlines.test.js index b7da8bd46..e4fd60cf2 100644 --- a/packages/modeling/src/geometries/geom2/toOutlines.test.js +++ b/packages/modeling/src/geometries/geom2/toOutlines.test.js @@ -12,43 +12,43 @@ test('geom2: toOutlines() should return no outlines for empty geom2', (t) => { }) test('geom2: toOutlines() should return one or more outlines', (t) => { - const shp1 = create([{vertices: [[-1, -1], [1, -1], [1, 1]]}]) + const shp1 = create([[[-1, -1], [1, -1], [1, 1]]]) const ret1 = toOutlines(shp1) const exp1 = [ [[-1, -1], [1, -1], [1, 1]] ] - t.true(comparePoints(ret1[0].vertices, exp1[0])) + t.true(comparePoints(ret1[0], exp1[0])) const shp2 = create([ - {vertices: [[-1, -1], [1, -1], [1, 1]]}, - {vertices: [[4, 4], [6, 4], [6, 6]]} + [[-1, -1], [1, -1], [1, 1]], + [[4, 4], [6, 4], [6, 6]] ]) const ret2 = toOutlines(shp2) const exp2 = [ [[-1, -1], [1, -1], [1, 1]], [[4, 4], [6, 4], [6, 6]] ] - t.true(comparePoints(ret2[0].vertices, exp2[0])) - t.true(comparePoints(ret2[1].vertices, exp2[1])) + t.true(comparePoints(ret2[0], exp2[0])) + t.true(comparePoints(ret2[1], exp2[1])) }) test('geom2: toOutlines() should return outlines for holes in geom2', (t) => { const shp1 = create([ - {vertices: [[10, 10], [-10, -10], [10, -10]]}, - {vertices: [[5, -5], [6, -4], [6, -4]]} + [[10, 10], [-10, -10], [10, -10]], + [[5, -5], [6, -4], [6, -4]] ]) const ret1 = toOutlines(shp1) const exp1 = [ [[10, 10], [-10, -10], [10, -10]], [[5, -5], [6, -4], [6, -4]] ] - t.true(comparePoints(ret1[0].vertices, exp1[0])) - t.true(comparePoints(ret1[1].vertices, exp1[1])) + t.true(comparePoints(ret1[0], exp1[0])) + t.true(comparePoints(ret1[1], exp1[1])) const shp2 = create([ - {vertices: [[6, -4], [5, -5], [6, -5]]}, - {vertices: [[10, 10], [-10, -10], [10, -10]]}, - {vertices: [[-6, -8], [8, 6], [8, -8]]} + [[6, -4], [5, -5], [6, -5]], + [[10, 10], [-10, -10], [10, -10]], + [[-6, -8], [8, 6], [8, -8]] ]) const ret2 = toOutlines(shp2) const exp2 = [ @@ -56,30 +56,30 @@ test('geom2: toOutlines() should return outlines for holes in geom2', (t) => { [[10, 10], [-10, -10], [10, -10]], [[-6, -8], [8, 6], [8, -8]] ] - t.true(comparePoints(ret2[0].vertices, exp2[0])) - t.true(comparePoints(ret2[1].vertices, exp2[1])) - t.true(comparePoints(ret2[2].vertices, exp2[2])) + t.true(comparePoints(ret2[0], exp2[0])) + t.true(comparePoints(ret2[1], exp2[1])) + t.true(comparePoints(ret2[2], exp2[2])) }) test('geom2: toOutlines() should return outlines for edges that touch in geom2', (t) => { const shp1 = create([ - {vertices: [[5, 15], [5, 5], [15, 5], [15, 15]]}, - {vertices: [[-5, 5], [-5, -5], [5, -5], [5, 5]]} + [[5, 15], [5, 5], [15, 5], [15, 15]], + [[-5, 5], [-5, -5], [5, -5], [5, 5]] ]) const ret1 = toOutlines(shp1) const exp1 = [ [[5, 15], [5, 5], [15, 5], [15, 15]], [[-5, 5], [-5, -5], [5, -5], [5, 5]] ] - t.true(comparePoints(ret1[0].vertices, exp1[0])) - t.true(comparePoints(ret1[1].vertices, exp1[1])) + t.true(comparePoints(ret1[0], exp1[0])) + t.true(comparePoints(ret1[1], exp1[1])) }) test('geom2: toOutlines() should return outlines for holes that touch in geom2', (t) => { const shp1 = create([ - {vertices: [[-20, 20], [-20, -20], [20, -20], [20, 20]]}, - {vertices: [[5, 5], [5, 15], [15, 15], [15, 5]]}, - {vertices: [[-5, -5], [-5, 5], [5, 5], [5, -5]]} + [[-20, 20], [-20, -20], [20, -20], [20, 20]], + [[5, 5], [5, 15], [15, 15], [15, 5]], + [[-5, -5], [-5, 5], [5, 5], [5, -5]] ]) const ret1 = toOutlines(shp1) const exp1 = [ @@ -87,9 +87,9 @@ test('geom2: toOutlines() should return outlines for holes that touch in geom2', [[5, 5], [5, 15], [15, 15], [15, 5]], [[-5, -5], [-5, 5], [5, 5], [5, -5]] ] - t.true(comparePoints(ret1[0].vertices, exp1[0])) - t.true(comparePoints(ret1[1].vertices, exp1[1])) - t.true(comparePoints(ret1[2].vertices, exp1[2])) + t.true(comparePoints(ret1[0], exp1[0])) + t.true(comparePoints(ret1[1], exp1[1])) + t.true(comparePoints(ret1[2], exp1[2])) }) // touching holes diff --git a/packages/modeling/src/geometries/geom2/transform.test.js b/packages/modeling/src/geometries/geom2/transform.test.js index 170321535..5888d502b 100644 --- a/packages/modeling/src/geometries/geom2/transform.test.js +++ b/packages/modeling/src/geometries/geom2/transform.test.js @@ -31,7 +31,7 @@ test('transform: adjusts the transforms of geom2', (t) => { t.true(compareVectors(another.transforms, expected.transforms)) // expect application of the transforms to the sides - expectedSides = [[[5, 10], [5, 11]], [[5, 11], [4, 10]], [[4, 10], [5, 10]]] + const expectedSides = [[[5, 10], [5, 11]], [[5, 11], [4, 10]], [[4, 10], [5, 10]]] const sides = toSides(another) t.true(comparePoints(sides[0], expectedSides[0])) t.true(comparePoints(sides[1], expectedSides[1])) diff --git a/packages/modeling/src/operations/booleans/unionGeom2.test.js b/packages/modeling/src/operations/booleans/unionGeom2.test.js index 5d2a20674..2e58465dc 100644 --- a/packages/modeling/src/operations/booleans/unionGeom2.test.js +++ b/packages/modeling/src/operations/booleans/unionGeom2.test.js @@ -100,43 +100,48 @@ test('union of one or more geom2 objects produces expected geometry', (t) => { }) test('union of geom2 with closing issues #15', (t) => { - const c = geom2.fromSides([ - [[-45.82118740347841168159, -16.85726810555620147625], [-49.30331715865012398581, -14.68093629710870118288]], - [[-49.10586702080816223770, -15.27604177352110781385], [-48.16645938811709015681, -15.86317173589183227023]], - [[-49.60419521731581937729, -14.89550781504266296906], [-49.42407001323204696064, -15.67605088949303393520]], - [[-49.05727291218684626983, -15.48661638542171203881], [-49.10586702080816223770, -15.27604177352110781385]], - [[-49.30706235399220815907, -15.81529674600091794900], [-46.00505780290426827150, -17.21108547999804727624]], - [[-46.00505780290426827150, -17.21108547999804727624], [-45.85939703723252591772, -17.21502856394236857795]], - [[-45.85939703723252591772, -17.21502856394236857795], [-45.74972032664388166268, -17.11909303495791334626]], - [[-45.74972032664388166268, -17.11909303495791334626], [-45.73424573227583067592, -16.97420292661295349035]], - [[-45.73424573227583067592, -16.97420292661295349035], [-45.82118740347841168159, -16.85726810555620147625]], - [[-49.30331715865012398581, -14.68093629710870118288], [-49.45428884427643367871, -14.65565769658912387285]], - [[-49.45428884427643367871, -14.65565769658912387285], [-49.57891661679624917269, -14.74453612941635327616]], - [[-49.57891661679624917269, -14.74453612941635327616], [-49.60419521731581937729, -14.89550781504266296906]], - [[-49.42407001323204696064, -15.67605088949303393520], [-49.30706235399220815907, -15.81529674600091794900]], - [[-48.16645938811709015681, -15.86317173589183227023], [-49.05727291218684626983, -15.48661638542171203881]] + const c = geom2.create([ + [ + [-49.303317158650124, -14.680936297108701], + [-49.454288844276434, -14.655657696589124], + [-49.57891661679625, -14.744536129416353], + [-49.60419521731582, -14.895507815042663], + [-49.42407001323205, -15.676050889493034], + [-49.30706235399221, -15.815296746000918], + [-46.00505780290427, -17.211085479998047], + [-45.859397037232526, -17.21502856394237], + [-45.74972032664388, -17.119093034957913], + [-45.73424573227583, -16.974202926612953], + [-45.82118740347841, -16.8572681055562] + ], + [ + [-48.16645938811709, -15.863171735891832], + [-49.057272912186846, -15.486616385421712], + [-49.10586702080816, -15.276041773521108] + ] ]) - const d = geom2.fromSides([ - [[-49.03431352173912216585, -15.58610714407888764299], [-49.21443872582289458251, -14.80556406962851667686]], - [[-68.31614651314507113966, -3.10790373951434872879], [-49.34036769611472550423, -15.79733157434056778357]], - [[-49.58572929483430868913, -14.97552686612213790340], [-49.53755741140093959984, -15.18427183431472826669]], - [[-49.53755741140093959984, -15.18427183431472826669], [-54.61235529924312714911, -11.79066769321313756791]], - [[-49.30227466841120076424, -14.68159232649114187552], [-68.09792828135776687759, -2.77270756611528668145]], - [[-49.21443872582289458251, -14.80556406962851667686], [-49.30227466841120076424, -14.68159232649114187552]], - [[-49.34036769611472550423, -15.79733157434056778357], [-49.18823337756091262918, -15.82684012194931710837]], - [[-49.18823337756091262918, -15.82684012194931710837], [-49.06069007212390431505, -15.73881563386780157998]], - [[-49.06069007212390431505, -15.73881563386780157998], [-49.03431352173912216585, -15.58610714407888764299]], - [[-68.09792828135776687759, -2.77270756611528668145], [-68.24753735887460948106, -2.74623350179570024920]], - [[-68.24753735887460948106, -2.74623350179570024920], [-68.37258141465594007968, -2.83253376987636329432]], - [[-68.37258141465594007968, -2.83253376987636329432], [-68.40089829889257089235, -2.98180502037078554167]], - [[-68.40089829889257089235, -2.98180502037078554167], [-68.31614651314507113966, -3.10790373951434872879]], - [[-54.61235529924312714911, -11.79066769321313756791], [-49.58572929483430868913, -14.97552686612213790340]] + const d = geom2.create([ + [ + [-49.214438725822895, -14.805564069628517], + [-49.3022746684112, -14.681592326491142], + [-68.09792828135777, -2.7727075661152867], + [-68.24753735887461, -2.7462335017957002], + [-68.37258141465594, -2.8325337698763633], + [-68.40089829889257, -2.9818050203707855], + [-68.31614651314507, -3.1079037395143487], + [-49.340367696114726, -15.797331574340568], + [-49.18823337756091, -15.826840121949317], + [-49.060690072123904, -15.738815633867802], + [-49.03431352173912, -15.586107144078888] + ], + [ + [-49.53755741140094, -15.184271834314728], + [-54.61235529924313, -11.790667693213138], + [-49.58572929483431, -14.975526866122138] + ] ]) - // geom2.toOutlines(c) - // geom2.toOutlines(d) const obs = union(c, d) - // const outlines = geom2.toOutlines(obs) const pts = geom2.toPoints(obs) const exp = [ [-68.40089829889257, -2.9818050203707855], diff --git a/packages/modeling/src/operations/expansions/expand.test.js b/packages/modeling/src/operations/expansions/expand.test.js index 1899ca4d0..6f1469b46 100644 --- a/packages/modeling/src/operations/expansions/expand.test.js +++ b/packages/modeling/src/operations/expansions/expand.test.js @@ -134,27 +134,33 @@ test('expand: expanding of a geom3 produces expected changes to polygons', (t) = }) test('expand (options): offsetting of a complex geom2 produces expected offset geom2', (t) => { - const geometry = geom2.fromSides([ - [[-75, 75], [-75, -75]], - [[-75, -75], [75, -75]], - [[75, -75], [75, 75]], - [[-40, 75], [-75, 75]], - [[75, 75], [40, 75]], - [[40, 75], [40, 0]], - [[40, 0], [-40, 0]], - [[-40, 0], [-40, 75]], - [[15, -10], [15, -40]], - [[-15, -10], [15, -10]], - [[-15, -40], [-15, -10]], - [[-8, -40], [-15, -40]], - [[15, -40], [8, -40]], - [[-8, -25], [-8, -40]], - [[8, -25], [-8, -25]], - [[8, -40], [8, -25]], - [[-2, -15], [-2, -19]], - [[-2, -19], [2, -19]], - [[2, -19], [2, -15]], - [[2, -15], [-2, -15]] + const geometry = geom2.create([ + [ + [-75, -75], + [75, -75], + [75, 75], + [40, 75], + [40, 0], + [-40, 0], + [-40, 75], + [-75, 75] + ], + [ + [15, -40], + [8, -40], + [8, -25], + [-8, -25], + [-8, -40], + [-15, -40], + [-15, -10], + [15, -10] + ], + [ + [-2, -19], + [2, -19], + [2, -15], + [-2, -15] + ] ]) // expand + diff --git a/packages/modeling/src/operations/modifiers/snap.js b/packages/modeling/src/operations/modifiers/snap.js index fee68ac4d..f14575b20 100644 --- a/packages/modeling/src/operations/modifiers/snap.js +++ b/packages/modeling/src/operations/modifiers/snap.js @@ -20,11 +20,12 @@ const snapPath2 = (geometry) => { const snapGeom2 = (geometry) => { const epsilon = measureEpsilon(geometry) - const sides = geom2.toSides(geometry) - let newsides = sides.map((side) => [vec2.snap(vec2.create(), side[0], epsilon), vec2.snap(vec2.create(), side[1], epsilon)]) - // snap can produce sides with zero (0) length, remove those - newsides = newsides.filter((side) => !vec2.equals(side[0], side[1])) - return geom2.fromSides(newsides) + const outlines = geom2.toOutlines(geometry) + const newOutlines = outlines.map((outline) => { + return outline.map((point) => vec2.snap(vec2.create(), point, epsilon)) + }) + // TODO: remove duplicate points, and zero-area outlines + return geom2.create(newOutlines) } const snapGeom3 = (geometry) => { diff --git a/packages/modeling/src/operations/modifiers/snap.test.js b/packages/modeling/src/operations/modifiers/snap.test.js index 1ab4a4f88..2bccfdbd7 100644 --- a/packages/modeling/src/operations/modifiers/snap.test.js +++ b/packages/modeling/src/operations/modifiers/snap.test.js @@ -68,24 +68,24 @@ test('snap: snap of a geom2 produces an expected geom2', (t) => { t.true(comparePoints(pts, exp)) pts = geom2.toPoints(results[1]) - exp = [[0.5, -0.5], [0.5, 0.5], [-0.5, 0.5], [-0.5, -0.5]] + exp = [[-0.5, -0.5], [0.5, -0.5], [0.5, 0.5], [-0.5, 0.5]] t.true(comparePoints(pts, exp)) pts = geom2.toPoints(results[2]) exp = [ + [-0.6666666666666666, -0.6666666666666666], [0.6666666666666666, -0.6666666666666666], [0.6666666666666666, 0.6666666666666666], - [-0.6666666666666666, 0.6666666666666666], - [-0.6666666666666666, -0.6666666666666666] + [-0.6666666666666666, 0.6666666666666666] ] t.true(comparePoints(pts, exp)) pts = geom2.toPoints(results[3]) exp = [ + [-1570.7963267948967, -1570.7963267948967], [1570.7963267948967, -1570.7963267948967], [1570.7963267948967, 1570.7963267948967], - [-1570.7963267948967, 1570.7963267948967], - [-1570.7963267948967, -1570.7963267948967] + [-1570.7963267948967, 1570.7963267948967] ] t.true(comparePoints(pts, exp)) }) From 07f372903f30fe6ac5a0056cd5e239d394c41831 Mon Sep 17 00:00:00 2001 From: Kenny Daniel Date: Tue, 3 Jan 2023 02:18:02 -0800 Subject: [PATCH 07/15] Reduce usage of geom2.toSides --- packages/modeling/src/geometries/geom2/toSides.js | 5 ++--- .../src/measurements/measureBoundingSphere.js | 12 ++++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/modeling/src/geometries/geom2/toSides.js b/packages/modeling/src/geometries/geom2/toSides.js index 8f63e44c1..bd48c50eb 100644 --- a/packages/modeling/src/geometries/geom2/toSides.js +++ b/packages/modeling/src/geometries/geom2/toSides.js @@ -1,4 +1,4 @@ -import applyTransforms from './applyTransforms.js' +import toOutlines from './toOutlines.js' /** * Produces an array of sides from the given geometry. @@ -12,9 +12,8 @@ import applyTransforms from './applyTransforms.js' * let sharedsides = toSides(geometry) */ export const toSides = (geometry) => { - applyTransforms(geometry) const sides = [] - geometry.outlines.forEach((outline) => { + toOutlines(geometry).forEach((outline) => { outline.forEach((point, i) => { const j = (i + 1) % outline.length sides.push([point, outline[j]]) diff --git a/packages/modeling/src/measurements/measureBoundingSphere.js b/packages/modeling/src/measurements/measureBoundingSphere.js index 2dc5d9833..3d3d89acb 100644 --- a/packages/modeling/src/measurements/measureBoundingSphere.js +++ b/packages/modeling/src/measurements/measureBoundingSphere.js @@ -57,21 +57,21 @@ const measureBoundingSphereOfGeom2 = (geometry) => { const centroid = vec3.create() let radius = 0 - const sides = geom2.toSides(geometry) + const points = geom2.toPoints(geometry) - if (sides.length > 0) { + if (points.length > 0) { // calculate the centroid of the geometry let numPoints = 0 const temp = vec3.create() - sides.forEach((side) => { - vec3.add(centroid, centroid, vec3.fromVec2(temp, side[0], 0)) + points.forEach((point) => { + vec3.add(centroid, centroid, vec3.fromVec2(temp, point, 0)) numPoints++ }) vec3.scale(centroid, centroid, 1 / numPoints) // find the farthest point from the centroid - sides.forEach((side) => { - radius = Math.max(radius, vec2.squaredDistance(centroid, side[0])) + points.forEach((point) => { + radius = Math.max(radius, vec2.squaredDistance(centroid, point)) }) radius = Math.sqrt(radius) } From 428d3d4061726fa35a526c7c4316e7c3bda4397b Mon Sep 17 00:00:00 2001 From: Kenny Daniel Date: Thu, 5 Jan 2023 18:12:18 -0800 Subject: [PATCH 08/15] PR feedback part 1 --- .../modeling/src/geometries/geom2/applyTransforms.js | 2 +- .../src/geometries/geom2/applyTransforms.test.js | 2 +- packages/modeling/src/geometries/geom2/create.js | 4 ++-- packages/modeling/src/geometries/geom2/fromSides.js | 10 ++++------ packages/modeling/src/geometries/geom2/type.d.ts | 4 ++-- packages/modeling/src/geometries/geom2/validate.js | 6 +++--- packages/modeling/src/geometries/poly2/create.js | 2 +- .../modeling/src/operations/booleans/martinez/index.js | 1 + 8 files changed, 15 insertions(+), 16 deletions(-) diff --git a/packages/modeling/src/geometries/geom2/applyTransforms.js b/packages/modeling/src/geometries/geom2/applyTransforms.js index 7bf450afd..807701138 100644 --- a/packages/modeling/src/geometries/geom2/applyTransforms.js +++ b/packages/modeling/src/geometries/geom2/applyTransforms.js @@ -3,7 +3,7 @@ import * as vec2 from '../../maths/vec2/index.js' /* * Apply the transforms of the given geometry. - * NOTE: This function must be called BEFORE exposing any data. See toSides(). + * NOTE: This function must be called BEFORE exposing any data. See toOutlines(). * @param {geom2} geometry - the geometry to transform * @returns {geom2} the given geometry * diff --git a/packages/modeling/src/geometries/geom2/applyTransforms.test.js b/packages/modeling/src/geometries/geom2/applyTransforms.test.js index e52e700c0..a39aef013 100644 --- a/packages/modeling/src/geometries/geom2/applyTransforms.test.js +++ b/packages/modeling/src/geometries/geom2/applyTransforms.test.js @@ -4,7 +4,7 @@ import { fromPoints } from './index.js' import applyTransforms from './applyTransforms.js' -test('applyTransforms: Updates a populated geom2 with transformed sides', (t) => { +test('applyTransforms: Updates a populated geom2 with transforms', (t) => { const points = [[0, 0], [1, 0], [0, 1]] const expected = { outlines: [[[0, 0], [1, 0], [0, 1]]], diff --git a/packages/modeling/src/geometries/geom2/create.js b/packages/modeling/src/geometries/geom2/create.js index 599aa7d2f..54edf38a6 100644 --- a/packages/modeling/src/geometries/geom2/create.js +++ b/packages/modeling/src/geometries/geom2/create.js @@ -1,7 +1,7 @@ import * as mat4 from '../../maths/mat4/index.js' /** - * Represents a 2D geometry consisting of a list of 2D polygons. + * Represents a 2D geometry consisting of a outlines, where each outline is an ordered list of points. * @typedef {Object} geom2 * @property {Array} outlines - list of polygon outlines * @property {mat4} transforms - transforms to apply to the geometry, see transform() @@ -9,7 +9,7 @@ import * as mat4 from '../../maths/mat4/index.js' /** * Create a new 2D geometry composed of polygon outlines. - * @param {Array} [outlines] - list outlines where each outline is an array of points + * @param {Array} [outlines] - list of outlines where each outline is an array of points * @returns {geom2} a new geometry * @alias module:modeling/geometries/geom2.create */ diff --git a/packages/modeling/src/geometries/geom2/fromSides.js b/packages/modeling/src/geometries/geom2/fromSides.js index 9169c56b4..485b47948 100644 --- a/packages/modeling/src/geometries/geom2/fromSides.js +++ b/packages/modeling/src/geometries/geom2/fromSides.js @@ -39,14 +39,12 @@ const toVertexMap = (sides) => { } /** - * Create the outline(s) of the given geometry. - * @param {geom2} geometry - geometry to create outlines from - * @returns {Array} an array of outlines, where each outline is an array of ordered points - * @alias module:modeling/geometries/geom2.toOutlines + * Create a new 2D geometry from a list of sides. + * @param {geom2} sides - geometry to create outlines from + * @returns {geom2} a new geometry * * @example - * let geometry = subtract(rectangle({size: [5, 5]}), rectangle({size: [3, 3]})) - * let outlines = toOutlines(geometry) // returns two outlines + * let geometry = fromSides([[[0, 0], [1, 0]], [[1, 0], [1, 1]], [[1, 1], [0, 0]]]) */ export const fromSides = (sides) => { const vertexMap = toVertexMap(sides) // {vertex: [edges]} diff --git a/packages/modeling/src/geometries/geom2/type.d.ts b/packages/modeling/src/geometries/geom2/type.d.ts index be9b2e311..72baf28c9 100644 --- a/packages/modeling/src/geometries/geom2/type.d.ts +++ b/packages/modeling/src/geometries/geom2/type.d.ts @@ -1,11 +1,11 @@ -import Poly2 from '../../maths/poly2/type' +import Vec2 from '../../maths/vec2/type' import Mat4 from '../../maths/mat4/type' import { Color } from '../types' export default Geom2 declare interface Geom2 { - outlines: Array + outlines: Array> transforms: Mat4 color?: Color } diff --git a/packages/modeling/src/geometries/geom2/validate.js b/packages/modeling/src/geometries/geom2/validate.js index fc66d1bf4..d59e19de2 100644 --- a/packages/modeling/src/geometries/geom2/validate.js +++ b/packages/modeling/src/geometries/geom2/validate.js @@ -18,15 +18,15 @@ export const validate = (object) => { throw new Error('invalid geom2 structure') } - object.outlines.forEach((outline) => { + object.outlines.forEach((outline, i) => { if (outline.length < 3) { - throw new Error('geom2 outlines must contain at least 3 points') + throw new Error(`geom2 outline ${i} must contain at least 3 points`) } // check for duplicate points for (let i = 0; i < outline.length; i++) { const j = (i + 1) % outline.length if (vec2.equals(outline[i], outline[j])) { - throw new Error(`geom2 self-edge ${outline[i]}`) + throw new Error(`geom2 outline ${i} found duplicate point ${outline[i]}`) } } }) diff --git a/packages/modeling/src/geometries/poly2/create.js b/packages/modeling/src/geometries/poly2/create.js index 77e05b278..613ec4a07 100644 --- a/packages/modeling/src/geometries/poly2/create.js +++ b/packages/modeling/src/geometries/poly2/create.js @@ -18,7 +18,7 @@ export const create = (vertices) => { if (vertices === undefined || vertices.length < 3) { vertices = [] // empty contents } - return { vertices } + return { vertices: vertices } } export default create diff --git a/packages/modeling/src/operations/booleans/martinez/index.js b/packages/modeling/src/operations/booleans/martinez/index.js index 5ee6005e1..2ef36d700 100644 --- a/packages/modeling/src/operations/booleans/martinez/index.js +++ b/packages/modeling/src/operations/booleans/martinez/index.js @@ -98,6 +98,7 @@ const fromOutlines = (outlines) => { outline.pop() // first == last point } }) + // Martinez sometime returns empty outlines, filter them out outlines = outlines.filter((o) => o.length >= 3) return geom2.create(outlines) } From e13fbd5bf20963f5fc08e41154cd9bd15f6377c0 Mon Sep 17 00:00:00 2001 From: Kenny Daniel Date: Thu, 5 Jan 2023 19:00:01 -0800 Subject: [PATCH 09/15] Don't mutate input array in geom2.fromPoints --- .../modeling/src/geometries/geom2/fromPoints.js | 4 +++- .../src/geometries/geom2/fromPoints.test.js | 13 ++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/packages/modeling/src/geometries/geom2/fromPoints.js b/packages/modeling/src/geometries/geom2/fromPoints.js index 5ded4a9e6..5d82596b6 100644 --- a/packages/modeling/src/geometries/geom2/fromPoints.js +++ b/packages/modeling/src/geometries/geom2/fromPoints.js @@ -20,7 +20,9 @@ export const fromPoints = (points) => { throw new Error('the given points must define a closed geometry with three or more points') } // adjust length if the given points are closed by the same point - if (vec2.equals(points[0], points[length - 1])) points.length-- + if (vec2.equals(points[0], points[length - 1])) { + points = points.slice(0, points.length - 1) + } return create([points]) } diff --git a/packages/modeling/src/geometries/geom2/fromPoints.test.js b/packages/modeling/src/geometries/geom2/fromPoints.test.js index b2f0cb7ee..fd8e20afa 100644 --- a/packages/modeling/src/geometries/geom2/fromPoints.test.js +++ b/packages/modeling/src/geometries/geom2/fromPoints.test.js @@ -9,9 +9,6 @@ test('fromPoints: creates populated geom2', (t) => { transforms: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] } t.deepEqual(fromPoints(points), expected) - - const points2 = [[0, 0], [1, 0], [0, 1], [0, 0]] - t.deepEqual(fromPoints(points2), expected) }) test('fromPoints: throws for improper points', (t) => { @@ -20,3 +17,13 @@ test('fromPoints: throws for improper points', (t) => { t.throws(() => fromPoints([]), { instanceOf: Error }) t.throws(() => fromPoints([[0, 0]]), { instanceOf: Error }) }) + +test('fromPoints: remove duplicate start/end', (t) => { + const points = [[0, 0], [2, 0], [0, 3], [0, 0]] + const expected = { + outlines: [[[0, 0], [2, 0], [0, 3]]], + transforms: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1] + } + t.deepEqual(fromPoints(points), expected) + t.is(points.length, 4) // don't mutate the input array +}) From 954f5f233fbf3992fff3eca8ae774ea957f25fb5 Mon Sep 17 00:00:00 2001 From: Kenny Daniel Date: Thu, 5 Jan 2023 19:06:59 -0800 Subject: [PATCH 10/15] Don't mutate outlines in geom2.reverse --- packages/modeling/src/geometries/geom2/reverse.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/modeling/src/geometries/geom2/reverse.js b/packages/modeling/src/geometries/geom2/reverse.js index 58c92b4ce..ed7c3d397 100644 --- a/packages/modeling/src/geometries/geom2/reverse.js +++ b/packages/modeling/src/geometries/geom2/reverse.js @@ -12,7 +12,7 @@ import clone from './clone.js' */ export const reverse = (geometry) => { const reversed = clone(geometry) - reversed.outlines.forEach((outline) => outline.reverse()) + reversed.outlines.forEach((outline) => outline.slice().reverse()) return reversed } From 68eaf63ca58f1344eef68dda3610188a5bc94383 Mon Sep 17 00:00:00 2001 From: Kenny Daniel Date: Thu, 5 Jan 2023 19:18:12 -0800 Subject: [PATCH 11/15] Update geom2.toString and add tests --- .../modeling/src/geometries/geom2/toString.js | 10 +++++----- .../src/geometries/geom2/toString.test.js | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 packages/modeling/src/geometries/geom2/toString.test.js diff --git a/packages/modeling/src/geometries/geom2/toString.js b/packages/modeling/src/geometries/geom2/toString.js index 7f790271f..57358798b 100644 --- a/packages/modeling/src/geometries/geom2/toString.js +++ b/packages/modeling/src/geometries/geom2/toString.js @@ -1,6 +1,6 @@ import * as vec2 from '../../maths/vec2/index.js' -import toSides from './toSides.js' +import toOutlines from './toOutlines.js' /** * Create a string representing the contents of the given geometry. @@ -12,10 +12,10 @@ import toSides from './toSides.js' * console.out(toString(geometry)) */ export const toString = (geometry) => { - const sides = toSides(geometry) - let result = 'geom2 (' + sides.length + ' sides):\n[\n' - sides.forEach((side) => { - result += ' [' + vec2.toString(side[0]) + ', ' + vec2.toString(side[1]) + ']\n' + const outlines = toOutlines(geometry) + let result = 'geom2 (' + outlines.length + ' outlines):\n[\n' + outlines.forEach((outline) => { + result += ' [' + outline.map(vec2.toString).join() + ']\n' }) result += ']\n' return result diff --git a/packages/modeling/src/geometries/geom2/toString.test.js b/packages/modeling/src/geometries/geom2/toString.test.js new file mode 100644 index 000000000..f29b63061 --- /dev/null +++ b/packages/modeling/src/geometries/geom2/toString.test.js @@ -0,0 +1,19 @@ +import test from 'ava' + +import create from './create.js' +import fromPoints from './fromPoints.js' +import toString from './toString.js' + +test('toString: serialize empty geom2 into a string', (t) => { + const geometry = create() + const compacted = toString(geometry) + const expected = 'geom2 (0 outlines):\n[\n]\n' + t.is(compacted, expected) +}) + +test('toString: serialize geom2 into a string', (t) => { + const geometry = fromPoints([[0, 0], [0, 1], [2, 0]]) + const compacted = toString(geometry) + const expected = 'geom2 (1 outlines):\n[\n [[0.0000000, 0.0000000],[0.0000000, 1.0000000],[2.0000000, 0.0000000]]\n]\n' + t.is(compacted, expected) +}) From 82a0d7fe65c46ba6c024b4c56f5f6e6710b201a0 Mon Sep 17 00:00:00 2001 From: Kenny Daniel Date: Fri, 6 Jan 2023 11:45:21 -0800 Subject: [PATCH 12/15] Remove duplicate points and empty outlines after snap --- .../modeling/src/operations/modifiers/snap.js | 18 +++++++++++++++--- .../src/operations/modifiers/snap.test.js | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/packages/modeling/src/operations/modifiers/snap.js b/packages/modeling/src/operations/modifiers/snap.js index f14575b20..808665c86 100644 --- a/packages/modeling/src/operations/modifiers/snap.js +++ b/packages/modeling/src/operations/modifiers/snap.js @@ -5,6 +5,7 @@ import * as vec2 from '../../maths/vec2/index.js' import * as geom2 from '../../geometries/geom2/index.js' import * as geom3 from '../../geometries/geom3/index.js' import * as path2 from '../../geometries/path2/index.js' +import * as poly2 from '../../geometries/poly2/index.js' import measureEpsilon from '../../measurements/measureEpsilon.js' @@ -21,10 +22,21 @@ const snapPath2 = (geometry) => { const snapGeom2 = (geometry) => { const epsilon = measureEpsilon(geometry) const outlines = geom2.toOutlines(geometry) - const newOutlines = outlines.map((outline) => { - return outline.map((point) => vec2.snap(vec2.create(), point, epsilon)) + let newOutlines = outlines.map((outline) => { + let prev = vec2.snap(vec2.create(), outline[outline.length - 1], epsilon) + const newOutline = [] + outline.forEach((point) => { + const snapped = vec2.snap(vec2.create(), point, epsilon) + // remove duplicate points + if (!vec2.equals(prev, snapped)) { + newOutline.push(snapped) + } + prev = snapped + }) + return newOutline }) - // TODO: remove duplicate points, and zero-area outlines + // remove zero-area outlines + newOutlines = newOutlines.filter((outline) => poly2.measureArea(poly2.create(outline))) return geom2.create(newOutlines) } diff --git a/packages/modeling/src/operations/modifiers/snap.test.js b/packages/modeling/src/operations/modifiers/snap.test.js index 2bccfdbd7..6234c8e20 100644 --- a/packages/modeling/src/operations/modifiers/snap.test.js +++ b/packages/modeling/src/operations/modifiers/snap.test.js @@ -90,6 +90,24 @@ test('snap: snap of a geom2 produces an expected geom2', (t) => { t.true(comparePoints(pts, exp)) }) +test('snap: snap of a geom2 removes duplicate points after snap', (t) => { + const geometry = geom2.fromPoints([[0, 0], [0, 1], [2, 0], [1.999999, 0]]) + const result = snap(geometry) + let pts = geom2.toPoints(result) + let exp = [[0, 0], [0, 1.000005], [1.9999950000000002, 0]] + t.is(pts.length, 3) + t.true(comparePoints(pts, exp)) +}) + +test('snap: snap of a geom2 removes empty outlines after snap', (t) => { + const geometry = geom2.fromPoints([[0, 0], [0, 1.000001], [0, 0.999999]]) + const result = snap(geometry) + let pts = geom2.toPoints(result) + let exp = [] + t.is(pts.length, 0) + t.true(comparePoints(pts, exp)) +}) + test('snap: snap of a geom3 produces an expected geom3', (t) => { const geometry1 = geom3.create() const geometry2 = cuboid({ size: [1, 1, 1] }) From e30f715f22e22af901e192416eb59640111d68a0 Mon Sep 17 00:00:00 2001 From: Kenny Daniel Date: Fri, 6 Jan 2023 12:06:14 -0800 Subject: [PATCH 13/15] More PR feedback --- packages/modeling/src/geometries/geom2/reverse.js | 2 +- packages/modeling/src/primitives/polygon.js | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/modeling/src/geometries/geom2/reverse.js b/packages/modeling/src/geometries/geom2/reverse.js index ed7c3d397..db122c591 100644 --- a/packages/modeling/src/geometries/geom2/reverse.js +++ b/packages/modeling/src/geometries/geom2/reverse.js @@ -12,7 +12,7 @@ import clone from './clone.js' */ export const reverse = (geometry) => { const reversed = clone(geometry) - reversed.outlines.forEach((outline) => outline.slice().reverse()) + reversed.outlines = reversed.outlines.map((outline) => outline.reverse()) return reversed } diff --git a/packages/modeling/src/primitives/polygon.js b/packages/modeling/src/primitives/polygon.js index f0a167add..5d1aaffe5 100644 --- a/packages/modeling/src/primitives/polygon.js +++ b/packages/modeling/src/primitives/polygon.js @@ -58,11 +58,10 @@ export const polygon = (options) => { const allpoints = [] listofpolys.forEach((list) => list.forEach((point) => allpoints.push(point))) - let outlines = [] + const outlines = [] listofpaths.forEach((path) => { const setofpoints = path.map((index) => allpoints[index]) - const geometry = geom2.fromPoints(setofpoints) - outlines = outlines.concat(geometry.outlines) + outlines.push(setofpoints) }) return geom2.create(outlines) } From 00917037fcc2f00b2cc12581aff5a4a2552a1ea6 Mon Sep 17 00:00:00 2001 From: Kenny Daniel Date: Fri, 6 Jan 2023 16:31:28 -0800 Subject: [PATCH 14/15] Update geom2 index docs --- packages/modeling/src/geometries/geom2/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/modeling/src/geometries/geom2/index.js b/packages/modeling/src/geometries/geom2/index.js index a1b9ef437..e9480f4a7 100644 --- a/packages/modeling/src/geometries/geom2/index.js +++ b/packages/modeling/src/geometries/geom2/index.js @@ -1,5 +1,6 @@ /** - * Represents a 2D geometry consisting of a list of sides. + * Represents a 2D geometry consisting of a outlines, where each outline is an ordered list of points. + * The outline is always closed between the first and last points. * @see {@link geom2} for data structure information. * @module modeling/geometries/geom2 * @@ -8,7 +9,7 @@ * * @example * { - * "sides": [[[-1,1],[-1,-1]],[[-1,-1],[1,-1]],[[1,-1],[1,1]],[[1,1],[-1,1]]], + * "outlines": [[[-1,-1],[1,-1],[1,1],[-1,1]]], * "transforms": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1], * "color": [0.5,0,1,1] * } From b820c63d2e9094a85cf87855a1697ed7adacb1c2 Mon Sep 17 00:00:00 2001 From: Kenny Daniel Date: Sat, 7 Jan 2023 18:18:23 -0800 Subject: [PATCH 15/15] PR feedback --- packages/modeling/src/geometries/geom2/create.js | 5 +---- packages/modeling/src/geometries/geom2/fromSides.d.ts | 6 ++++++ packages/modeling/src/geometries/geom2/fromSides.js | 2 +- packages/modeling/src/geometries/geom2/index.d.ts | 1 + packages/modeling/src/geometries/geom2/index.js | 5 ++--- packages/modeling/src/geometries/geom2/reverse.js | 2 +- packages/modeling/src/geometries/geom2/transform.js | 4 ++-- packages/modeling/src/geometries/geom2/transform.test.js | 8 +++++++- packages/modeling/src/geometries/index.js | 2 +- packages/modeling/src/geometries/path2/transform.test.js | 2 +- 10 files changed, 23 insertions(+), 14 deletions(-) create mode 100644 packages/modeling/src/geometries/geom2/fromSides.d.ts diff --git a/packages/modeling/src/geometries/geom2/create.js b/packages/modeling/src/geometries/geom2/create.js index 54edf38a6..6d9aabe58 100644 --- a/packages/modeling/src/geometries/geom2/create.js +++ b/packages/modeling/src/geometries/geom2/create.js @@ -13,10 +13,7 @@ import * as mat4 from '../../maths/mat4/index.js' * @returns {geom2} a new geometry * @alias module:modeling/geometries/geom2.create */ -export const create = (outlines) => { - if (outlines === undefined) { - outlines = [] // empty contents - } +export const create = (outlines = []) => { return { outlines, transforms: mat4.create() diff --git a/packages/modeling/src/geometries/geom2/fromSides.d.ts b/packages/modeling/src/geometries/geom2/fromSides.d.ts new file mode 100644 index 000000000..2bff60f3b --- /dev/null +++ b/packages/modeling/src/geometries/geom2/fromSides.d.ts @@ -0,0 +1,6 @@ +import Geom2 from './type' +import Vec2 from '../../maths/vec2/type' + +export default fromSides + +declare function fromSides(sides: Array<[Vec2, Vec2]>): Geom2 diff --git a/packages/modeling/src/geometries/geom2/fromSides.js b/packages/modeling/src/geometries/geom2/fromSides.js index 485b47948..37159ceb5 100644 --- a/packages/modeling/src/geometries/geom2/fromSides.js +++ b/packages/modeling/src/geometries/geom2/fromSides.js @@ -40,7 +40,7 @@ const toVertexMap = (sides) => { /** * Create a new 2D geometry from a list of sides. - * @param {geom2} sides - geometry to create outlines from + * @param {Array} sides - list of sides to create outlines from * @returns {geom2} a new geometry * * @example diff --git a/packages/modeling/src/geometries/geom2/index.d.ts b/packages/modeling/src/geometries/geom2/index.d.ts index d937b0e8f..d9611d7cd 100644 --- a/packages/modeling/src/geometries/geom2/index.d.ts +++ b/packages/modeling/src/geometries/geom2/index.d.ts @@ -1,6 +1,7 @@ export { default as clone } from './clone' export { default as create } from './create' export { default as fromPoints } from './fromPoints' +export { default as fromSides } from './fromSides' export { default as fromCompactBinary } from './fromCompactBinary' export { default as isA } from './isA' export { default as reverse } from './reverse' diff --git a/packages/modeling/src/geometries/geom2/index.js b/packages/modeling/src/geometries/geom2/index.js index e9480f4a7..9a70cab10 100644 --- a/packages/modeling/src/geometries/geom2/index.js +++ b/packages/modeling/src/geometries/geom2/index.js @@ -5,13 +5,12 @@ * @module modeling/geometries/geom2 * * @example - * colorize([0.5,0,1,1], square()) // purple square + * square() * * @example * { * "outlines": [[[-1,-1],[1,-1],[1,1],[-1,1]]], - * "transforms": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1], - * "color": [0.5,0,1,1] + * "transforms": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1] * } */ export { clone } from './clone.js' diff --git a/packages/modeling/src/geometries/geom2/reverse.js b/packages/modeling/src/geometries/geom2/reverse.js index db122c591..7c861d498 100644 --- a/packages/modeling/src/geometries/geom2/reverse.js +++ b/packages/modeling/src/geometries/geom2/reverse.js @@ -1,7 +1,7 @@ import clone from './clone.js' /** - * Reverses the given geometry so that the sides are flipped in the opposite order. + * Reverses the given geometry so that the outline points are flipped in the opposite order. * This swaps the left (interior) and right (exterior) edges. * @param {geom2} geometry - the geometry to reverse * @returns {geom2} the new reversed geometry diff --git a/packages/modeling/src/geometries/geom2/transform.js b/packages/modeling/src/geometries/geom2/transform.js index e1052f9d5..5130499bd 100644 --- a/packages/modeling/src/geometries/geom2/transform.js +++ b/packages/modeling/src/geometries/geom2/transform.js @@ -2,8 +2,8 @@ import * as mat4 from '../../maths/mat4/index.js' /** * Transform the given geometry using the given matrix. - * This is a lazy transform of the sides, as this function only adjusts the transforms. - * The transforms are applied when accessing the sides via toSides(). + * This is a lazy transform of the outlines, as this function only adjusts the transforms. + * The transforms are applied when accessing the outlines via toOutlines(). * @param {mat4} matrix - the matrix to transform with * @param {geom2} geometry - the geometry to transform * @returns {geom2} a new geometry diff --git a/packages/modeling/src/geometries/geom2/transform.test.js b/packages/modeling/src/geometries/geom2/transform.test.js index 5888d502b..a9cd60ad5 100644 --- a/packages/modeling/src/geometries/geom2/transform.test.js +++ b/packages/modeling/src/geometries/geom2/transform.test.js @@ -2,7 +2,7 @@ import test from 'ava' import { mat4 } from '../../maths/index.js' -import { transform, fromPoints, toSides } from './index.js' +import { transform, fromPoints, toOutlines, toSides } from './index.js' import { comparePoints, compareVectors } from '../../../test/helpers/index.js' @@ -37,6 +37,12 @@ test('transform: adjusts the transforms of geom2', (t) => { t.true(comparePoints(sides[1], expectedSides[1])) t.true(comparePoints(sides[2], expectedSides[2])) + // expect application of the transforms to the outlines + const expectedOutline = [[5, 10], [5, 11], [4, 10]] + const outlines = toOutlines(another) + t.is(outlines.length, 1) + t.true(comparePoints(outlines[0], expectedOutline)) + // expect lazy transform, i.e. only the transforms change expected.transforms = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 5, 10, 15, 1] another.outlines = [[[0, 0], [1, 0], [0, 1]]] diff --git a/packages/modeling/src/geometries/index.js b/packages/modeling/src/geometries/index.js index b1435523b..a9897bf3c 100644 --- a/packages/modeling/src/geometries/index.js +++ b/packages/modeling/src/geometries/index.js @@ -2,7 +2,7 @@ * Geometries are objects that represent the contents of primitives or the results of operations. * Note: Geometries are considered immutable, so never change the contents directly. * - * @see {@link geom2} - 2D geometry consisting of sides + * @see {@link geom2} - 2D geometry consisting of outlines * @see {@link geom3} - 3D geometry consisting of polygons * @see {@link path2} - 2D geometry consisting of ordered points * @see {@link poly2} - 2D polygon consisting of ordered vertices diff --git a/packages/modeling/src/geometries/path2/transform.test.js b/packages/modeling/src/geometries/path2/transform.test.js index 3d8f45b00..0eb375469 100644 --- a/packages/modeling/src/geometries/path2/transform.test.js +++ b/packages/modeling/src/geometries/path2/transform.test.js @@ -33,7 +33,7 @@ test('transform: adjusts the transforms of path', (t) => { t.false(another.isClosed) t.true(compareVectors(another.transforms, expected.transforms)) - // expect application of the transforms to the sides + // expect application of the transforms to the points expected.points = [[5, 10], [5, 11], [4, 10]] expected.transforms = mat4.create() toPoints(another)