Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(modeling): preserve color for offset and extrude #1275

Merged
merged 3 commits into from
Aug 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions packages/modeling/src/geometries/geom2/reverse.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { clone } from './clone.js'
import { create } from './create.js'
import { toOutlines } from './toOutlines.js'

/**
* Reverses the given geometry so that the outline points are flipped in the opposite order.
Expand All @@ -11,7 +12,9 @@ import { clone } from './clone.js'
* let newGeometry = reverse(geometry)
*/
export const reverse = (geometry) => {
const reversed = clone(geometry)
reversed.outlines = reversed.outlines.map((outline) => outline.slice().reverse())
const outlines = toOutlines(geometry)
.map((outline) => outline.slice().reverse())
const reversed = create(outlines)
if (geometry.color) reversed.color = geometry.color
return reversed
}
9 changes: 9 additions & 0 deletions packages/modeling/src/geometries/geom2/reverse.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import test from 'ava'

import { colorize } from '../../colors/index.js'

import { create, reverse, toPoints } from './index.js'

import { comparePoints, compareVectors } from '../../../test/helpers/index.js'
Expand Down Expand Up @@ -29,3 +31,10 @@ test('reverse: does not modify input geometry', (t) => {
t.true(comparePoints(toPoints(geometry), forward))
t.true(comparePoints(toPoints(another), backward))
})

test('reverse: preserves color', (t) => {
const points = [[0, 0], [1, 0], [0, 1]]
const geometry = colorize([1, 0, 0], create([points]))
const reversed = reverse(geometry)
t.deepEqual(reversed.color, [1, 0, 0, 1])
})
13 changes: 13 additions & 0 deletions packages/modeling/src/operations/extrusions/extrudeLinear.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { comparePolygonsAsPoints } from '../../../test/helpers/index.js'

import { TAU } from '../../maths/constants.js'

import { colorize } from '../../colors/index.js'

import { geom2, geom3, path2 } from '../../geometries/index.js'

import { measureVolume } from '../../measurements/index.js'
Expand Down Expand Up @@ -37,6 +39,17 @@ test('extrudeLinear (defaults)', (t) => {
t.true(comparePolygonsAsPoints(pts, exp))
})

test('extrudeLinear: preserves color', (t) => {
const redSquare = colorize([1, 0, 0], square())
const extruded = extrudeLinear({ }, redSquare)
t.deepEqual(extruded.color, [1, 0, 0, 1])
platypii marked this conversation as resolved.
Show resolved Hide resolved

// one red, one blue
const out = extrudeLinear({ }, [redSquare, square()])
t.deepEqual(out[0].color, [1, 0, 0, 1])
t.is(out[1].color, undefined)
})

test('extrudeLinear (no twist)', (t) => {
const geometry2 = square({ size: 10 })

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,7 @@ export const extrudeLinearGeom2 = (options, geometry) => {
repair,
callback: createTwist
}
return extrudeFromSlices(options, baseSlice)
const output = extrudeFromSlices(options, baseSlice)
if (geometry.color) output.color = geometry.color
return output
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ export const extrudeLinearPath2 = (options, geometry) => {
// Convert path2 to geom2
const points = path2.toPoints(geometry)
const geometry2 = geom2.create([points])
if (geometry.color) geometry2.color = geometry.color
return extrudeLinearGeom2(options, geometry2)
}
15 changes: 9 additions & 6 deletions packages/modeling/src/operations/extrusions/extrudeRotate.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ export const extrudeRotate = (options, geometry) => {

// convert geometry to an array of sides, easier to deal with
let shapeSides = geom2.toSides(geometry)
if (shapeSides.length === 0) throw new Error('the given geometry cannot be empty')
if (shapeSides.length === 0) return geometry
let sliceGeometry = geometry
z3dev marked this conversation as resolved.
Show resolved Hide resolved

// determine if the extrusion can be computed in the first place
// ie all the points have to be either x > 0 or x < 0
Expand Down Expand Up @@ -87,8 +88,8 @@ export const extrudeRotate = (options, geometry) => {
return [point0, point1]
})
// recreate the geometry from the (-) capped points
geometry = geom2.reverse(geom2.fromSides(shapeSides))
geometry = mirrorX(geometry)
sliceGeometry = geom2.reverse(geom2.fromSides(shapeSides))
sliceGeometry = mirrorX(sliceGeometry)
} else if (pointsWithPositiveX.length >= pointsWithNegativeX.length) {
shapeSides = shapeSides.map((side) => {
let point0 = side[0]
Expand All @@ -98,13 +99,13 @@ export const extrudeRotate = (options, geometry) => {
return [point0, point1]
})
// recreate the geometry from the (+) capped points
geometry = geom2.fromSides(shapeSides)
sliceGeometry = geom2.fromSides(shapeSides)
}
}

const rotationPerSlice = totalRotation / segments
const isCapped = Math.abs(totalRotation) < TAU
let baseSlice = slice.fromGeom2(geometry)
let baseSlice = slice.fromGeom2(sliceGeometry)
baseSlice = slice.reverse(baseSlice)

const matrix = mat4.create()
Expand All @@ -126,5 +127,7 @@ export const extrudeRotate = (options, geometry) => {
close: !isCapped,
callback: createSlice
}
return extrudeFromSlices(options, baseSlice)
const output = extrudeFromSlices(options, baseSlice)
if (geometry.color) output.color = geometry.color
return output
}
10 changes: 10 additions & 0 deletions packages/modeling/src/operations/extrusions/extrudeRotate.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import { comparePoints, comparePolygonsAsPoints } from '../../../test/helpers/in

import { TAU } from '../../maths/constants.js'

import { colorize } from '../../colors/index.js'

import { geom2, geom3 } from '../../geometries/index.js'

import { measureVolume } from '../../measurements/index.js'

import { square } from '../../primitives/index.js'

import { extrudeRotate } from './index.js'

test('extrudeRotate: (defaults) extruding of a geom2 produces an expected geom3', (t) => {
Expand All @@ -20,6 +24,12 @@ test('extrudeRotate: (defaults) extruding of a geom2 produces an expected geom3'
t.is(pts.length, 96)
})

test('extrudeRotate: preserves color', (t) => {
const red = colorize([1, 0, 0], square())
const extruded = extrudeRotate({ }, red)
t.deepEqual(extruded.color, [1, 0, 0, 1])
})

test('extrudeRotate: (angle) extruding of a geom2 produces an expected geom3', (t) => {
const geometry2 = geom2.create([[[10, 8], [10, -8], [26, -8], [26, 8]]])

Expand Down
4 changes: 3 additions & 1 deletion packages/modeling/src/operations/extrusions/project.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,9 @@ const projectGeom3 = (options, geometry) => {
return geom2.create([cloned])
})

return unionGeom2(projGeoms)
const output = unionGeom2(projGeoms)
if (geometry.color) output.color = geometry.color
return output
}

/**
Expand Down
15 changes: 15 additions & 0 deletions packages/modeling/src/operations/extrusions/project.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import test from 'ava'

import { comparePoints } from '../../../test/helpers/index.js'

import { colorize } from '../../colors/index.js'

import { geom2, geom3 } from '../../geometries/index.js'

import { measureArea } from '../../measurements/index.js'
Expand Down Expand Up @@ -139,3 +141,16 @@ test('project torus (martinez issue #155)', (t) => {
t.notThrows(() => geom2.validate(result))
t.is(measureArea(result), 21.15545050788201)
})

test('project: preserves color', (t) => {
const redCube = colorize([1, 0, 0], cube())
const result = project({ }, redCube)
t.deepEqual(result.color, [1, 0, 0, 1])
})

test('project: empty geometry', (t) => {
const obj = geom3.create()
const result = project({ }, obj)
t.notThrows(() => geom2.validate(result))
t.is(measureArea(result), 0)
})
14 changes: 6 additions & 8 deletions packages/modeling/src/operations/hulls/hull.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import test from 'ava'

import { geom2, geom3, path2 } from '../../geometries/index.js'

import { measureArea } from '../../measurements/measureArea.js'
import { sphere, cuboid, ellipsoid } from '../../primitives/index.js'

import { center } from '../transforms/index.js'

import { hull } from './index.js'
Expand All @@ -15,15 +14,15 @@ test('hull (single, geom2)', (t) => {

let obs = hull(geometry)
let pts = geom2.toPoints(obs)

t.notThrows(() => geom2.validate(geometry))
t.notThrows(() => geom2.validate(obs))
t.is(measureArea(obs), 0)
t.is(pts.length, 0)

geometry = geom2.create([[[5, 5], [-5, 5], [-5, -5], [5, -5]]])
obs = hull(geometry)
pts = geom2.toPoints(obs)

t.notThrows(() => geom2.validate(geometry))
t.notThrows(() => geom2.validate(obs))
t.is(measureArea(obs), 100)
t.is(pts.length, 4)

// convex C shape
Expand All @@ -41,8 +40,7 @@ test('hull (single, geom2)', (t) => {
]])
obs = hull(geometry)
pts = geom2.toPoints(obs)

t.notThrows(() => geom2.validate(geometry))
t.notThrows(() => geom2.validate(obs))
t.is(pts.length, 7)
})

Expand Down
6 changes: 4 additions & 2 deletions packages/modeling/src/operations/hulls/hullChain.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ import { hull } from './hull.js'
*/
export const hullChain = (...geometries) => {
geometries = flatten(geometries)
if (geometries.length < 2) throw new Error('wrong number of arguments')

const hulls = []

if (geometries.length === 0) throw new Error('wrong number of arguments')
if (geometries.length === 1) hulls.push(geometries[0])

for (let i = 1; i < geometries.length; i++) {
hulls.push(hull(geometries[i - 1], geometries[i]))
}
Expand Down
9 changes: 9 additions & 0 deletions packages/modeling/src/operations/hulls/hullChain.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import test from 'ava'

import { geom2, geom3 } from '../../geometries/index.js'
import { measureArea } from '../../measurements/measureArea.js'
import { square } from '../../primitives/square.js'

import { hullChain } from './index.js'

test('hullChain: hullChain single geometry', (t) => {
const result = hullChain([ square({ size: 1 }) ])
t.notThrows(() => geom2.validate(result))
t.is(measureArea(result), 1)
t.is(geom2.toPoints(result).length, 4)
})

test('hullChain (two, geom2)', (t) => {
const geometry1 = geom2.create([[[6, 6], [3, 6], [3, 3], [6, 3]]])
const geometry2 = geom2.create([[[-6, -6], [-9, -6], [-9, -9], [-6, -9]]])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const offsetFromPoints = (options, points) => {
let { delta, corners, closed, segments } = Object.assign({ }, defaults, options)

if (Math.abs(delta) < EPS) return points
if (points.length < 2) return points

let rotation = options.closed ? area(points) : 1.0 // + counter clockwise, - clockwise
if (rotation === 0) rotation = 1.0
Expand Down
10 changes: 10 additions & 0 deletions packages/modeling/src/operations/offsets/offsetFromPoints.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ import { offsetFromPoints } from './index.js'

import { comparePoints } from '../../../test/helpers/index.js'

test('offset: offset empty points', (t) => {
const offsetPoints = offsetFromPoints({ }, [])
t.is(offsetPoints.length, 0)
})

test('offset: offset single point', (t) => {
const offsetPoints = offsetFromPoints({ corners: 'round' }, [[2, 2]])
t.is(offsetPoints.length, 1)
})

test('offset: offsetting a straight line produces expected geometry', (t) => {
const points = [[0, 0], [0, 10]]

Expand Down
5 changes: 4 additions & 1 deletion packages/modeling/src/operations/offsets/offsetGeom2.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ export const offsetGeom2 = (options, geometry) => {
}
return offsetFromPoints(options, outline)
})
// TODO: union outlines that expanded into each other

// create a composite geometry from the new outlines
return geom2.create(newOutlines)
const output = geom2.create(newOutlines)
if (geometry.color) output.color = geometry.color
return output
}
19 changes: 11 additions & 8 deletions packages/modeling/src/operations/offsets/offsetGeom2.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import test from 'ava'

import { colorize } from '../../colors/index.js'
import { geom2 } from '../../geometries/index.js'

import { measureArea } from '../../measurements/index.js'

import { roundedRectangle, square } from '../../primitives/index.js'

import { offset } from './index.js'
Expand All @@ -12,12 +11,10 @@ import { comparePoints } from '../../../test/helpers/index.js'

test('offset: offset an empty geom2', (t) => {
const empty = geom2.create()
const obs = offset({ delta: 1 }, empty)
const pts = geom2.toPoints(obs)
const exp = []
t.notThrows(() => geom2.validate(obs))
t.is(measureArea(obs), 0)
t.true(comparePoints(pts, exp))
const result = offset({ delta: 1 }, empty)
t.notThrows(() => geom2.validate(result))
t.is(measureArea(result), 0)
t.is(geom2.toPoints(result).length, 0)
})

test('offset: offset option validation', (t) => {
Expand All @@ -38,6 +35,12 @@ test('offset: offset option validation', (t) => {
t.throws(() => offset({ corners: 'fluffy' }, empty), { message: 'corners must be "edge", "chamfer", or "round"' })
})

test('offset: offset geom2 preserves color', (t) => {
const geometry = colorize([1, 0, 0], square({ }))
const result = offset({ }, geometry)
t.deepEqual(result.color, [1, 0, 0, 1])
})

test('offset: offset of a geom2 produces expected changes to points', (t) => {
const geometry = square({ size: 16 })

Expand Down
7 changes: 3 additions & 4 deletions packages/modeling/src/operations/offsets/offsetGeom3.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,9 @@ export const offsetGeom3 = (options, geometry) => {
throw new Error('corners must be "round" for 3D geometries')
}

const polygons = geom3.toPolygons(geometry)
if (polygons.length === 0) throw new Error('the given geometry cannot be empty')

options = { delta, corners, segments }
const expanded = offsetShell(options, geometry)
return union(geometry, expanded)
const output = union(geometry, expanded)
if (geometry.color) output.color = geometry.color
return output
}
20 changes: 18 additions & 2 deletions packages/modeling/src/operations/offsets/offsetGeom3.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,28 @@ import test from 'ava'

import { comparePoints } from '../../../test/helpers/index.js'

import { colorize } from '../../colors/index.js'
import { geom3, poly3 } from '../../geometries/index.js'

import { sphere } from '../../primitives/index.js'
import { measureVolume } from '../../measurements/index.js'
import { cube, sphere } from '../../primitives/index.js'

import { offset } from './index.js'

test('offset: offset empty geom3', (t) => {
const geometry = geom3.create()
const result = offset({ }, geometry)
t.notThrows(() => geom3.validate(result))
t.is(measureVolume(result), 0)
t.is(geom3.toPolygons(result).length, 0)
t.is(geom3.toPoints(result).length, 0)
})

test('offset: offset geom3 preserves color', (t) => {
const geometry = colorize([1, 0, 0], cube({ }))
const result = offset({ }, geometry)
t.deepEqual(result.color, [1, 0, 0, 1])
})

test('offset: offset of a geom3 produces expected changes to polygons', (t) => {
const polygonsAsPoints = [
[[-5, -5, -5], [-5, -5, 15], [-5, 15, 15], [-5, 15, -5]],
Expand Down
Loading