Skip to content

Commit a51b299

Browse files
committed
feat(modeling): preserve color for offset and extrude
1 parent f88669b commit a51b299

File tree

14 files changed

+105
-30
lines changed

14 files changed

+105
-30
lines changed

packages/modeling/src/geometries/geom2/reverse.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ import { clone } from './clone.js'
1313
export const reverse = (geometry) => {
1414
const reversed = clone(geometry)
1515
reversed.outlines = reversed.outlines.map((outline) => outline.slice().reverse())
16+
if (geometry.color) reversed.color = geometry.color
1617
return reversed
1718
}

packages/modeling/src/geometries/geom2/reverse.test.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,10 @@ test('reverse: does not modify input geometry', (t) => {
2929
t.true(comparePoints(toPoints(geometry), forward))
3030
t.true(comparePoints(toPoints(another), backward))
3131
})
32+
33+
test('reverse: preserves color', (t) => {
34+
const points = [[0, 0], [1, 0], [0, 1]]
35+
const geometry = colorize([1, 0, 0], create([points]))
36+
const reversed = reverse(geometry)
37+
t.deepEqual(reversed.color, [1, 0, 0, 1])
38+
})

packages/modeling/src/operations/extrusions/extrudeLinear.test.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { comparePolygonsAsPoints } from '../../../test/helpers/index.js'
44

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

7+
import { colorize } from '../../colors/index.js'
8+
79
import { geom2, geom3, path2 } from '../../geometries/index.js'
810

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

42+
test('extrudeLinear: preserves color', (t) => {
43+
const red = colorize([1, 0, 0], square())
44+
const extruded = extrudeLinear({ }, red)
45+
t.deepEqual(extruded.color, [1, 0, 0, 1])
46+
47+
// one red, one blue
48+
const out = extrudeLinear({ }, [red, square()])
49+
t.deepEqual(out[0].color, [1, 0, 0, 1])
50+
t.is(out[1].color, undefined)
51+
})
52+
4053
test('extrudeLinear (no twist)', (t) => {
4154
const geometry2 = square({ size: 10 })
4255

packages/modeling/src/operations/extrusions/extrudeLinearGeom2.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,7 @@ export const extrudeLinearGeom2 = (options, geometry) => {
5353
repair,
5454
callback: createTwist
5555
}
56-
return extrudeFromSlices(options, baseSlice)
56+
const output = extrudeFromSlices(options, baseSlice)
57+
if (geometry.color) output.color = geometry.color
58+
return output
5759
}

packages/modeling/src/operations/extrusions/extrudeLinearPath2.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ export const extrudeLinearPath2 = (options, geometry) => {
1818
// Convert path2 to geom2
1919
const points = path2.toPoints(geometry)
2020
const geometry2 = geom2.create([points])
21+
if (geometry.color) geometry2.color = geometry.color
2122
return extrudeLinearGeom2(options, geometry2)
2223
}

packages/modeling/src/operations/extrusions/extrudeRotate.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export const extrudeRotate = (options, geometry) => {
5959
// convert geometry to an array of sides, easier to deal with
6060
let shapeSides = geom2.toSides(geometry)
6161
if (shapeSides.length === 0) throw new Error('the given geometry cannot be empty')
62+
let sliceGeometry = geometry
6263

6364
// determine if the extrusion can be computed in the first place
6465
// ie all the points have to be either x > 0 or x < 0
@@ -87,8 +88,8 @@ export const extrudeRotate = (options, geometry) => {
8788
return [point0, point1]
8889
})
8990
// recreate the geometry from the (-) capped points
90-
geometry = geom2.reverse(geom2.fromSides(shapeSides))
91-
geometry = mirrorX(geometry)
91+
sliceGeometry = geom2.reverse(geom2.fromSides(shapeSides))
92+
sliceGeometry = mirrorX(sliceGeometry)
9293
} else if (pointsWithPositiveX.length >= pointsWithNegativeX.length) {
9394
shapeSides = shapeSides.map((side) => {
9495
let point0 = side[0]
@@ -98,13 +99,13 @@ export const extrudeRotate = (options, geometry) => {
9899
return [point0, point1]
99100
})
100101
// recreate the geometry from the (+) capped points
101-
geometry = geom2.fromSides(shapeSides)
102+
sliceGeometry = geom2.fromSides(shapeSides)
102103
}
103104
}
104105

105106
const rotationPerSlice = totalRotation / segments
106107
const isCapped = Math.abs(totalRotation) < TAU
107-
let baseSlice = slice.fromGeom2(geometry)
108+
let baseSlice = slice.fromGeom2(sliceGeometry)
108109
baseSlice = slice.reverse(baseSlice)
109110

110111
const matrix = mat4.create()
@@ -126,5 +127,7 @@ export const extrudeRotate = (options, geometry) => {
126127
close: !isCapped,
127128
callback: createSlice
128129
}
129-
return extrudeFromSlices(options, baseSlice)
130+
const output = extrudeFromSlices(options, baseSlice)
131+
if (geometry.color) output.color = geometry.color
132+
return output
130133
}

packages/modeling/src/operations/extrusions/extrudeRotate.test.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@ import { comparePoints, comparePolygonsAsPoints } from '../../../test/helpers/in
44

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

7+
import { colorize } from '../../colors/index.js'
8+
79
import { geom2, geom3 } from '../../geometries/index.js'
810

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

13+
import { square } from '../../primitives/index.js'
14+
1115
import { extrudeRotate } from './index.js'
1216

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

27+
test('extrudeRotate: preserves color', (t) => {
28+
const red = colorize([1, 0, 0], square())
29+
const extruded = extrudeRotate({ }, red)
30+
t.deepEqual(extruded.color, [1, 0, 0, 1])
31+
})
32+
2333
test('extrudeRotate: (angle) extruding of a geom2 produces an expected geom3', (t) => {
2434
const geometry2 = geom2.create([[[10, 8], [10, -8], [26, -8], [26, 8]]])
2535

packages/modeling/src/operations/offsets/offsetGeom2.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,10 @@ export const offsetGeom2 = (options, geometry) => {
4646
}
4747
return offsetFromPoints(options, outline)
4848
})
49+
// TODO: union outlines that expanded into each other
4950

5051
// create a composite geometry from the new outlines
51-
return geom2.create(newOutlines)
52+
const output = geom2.create(newOutlines)
53+
if (geometry.color) output.color = geometry.color
54+
return output
5255
}

packages/modeling/src/operations/offsets/offsetGeom2.test.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import test from 'ava'
22

3+
import { colorize } from '../../colors/index.js'
34
import { geom2 } from '../../geometries/index.js'
4-
55
import { measureArea } from '../../measurements/index.js'
6-
76
import { roundedRectangle, square } from '../../primitives/index.js'
87

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

1312
test('offset: offset an empty geom2', (t) => {
1413
const empty = geom2.create()
15-
const obs = offset({ delta: 1 }, empty)
16-
const pts = geom2.toPoints(obs)
17-
const exp = []
18-
t.notThrows(() => geom2.validate(obs))
19-
t.is(measureArea(obs), 0)
20-
t.true(comparePoints(pts, exp))
14+
const result = offset({ delta: 1 }, empty)
15+
t.notThrows(() => geom2.validate(result))
16+
t.is(measureArea(result), 0)
17+
t.is(geom2.toPoints(result).length, 0)
2118
})
2219

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

38+
test('offset: offset geom2 preserves color', (t) => {
39+
const geometry = colorize([1, 0, 0], square({ }))
40+
const result = offset({ }, geometry)
41+
t.deepEqual(result.color, [1, 0, 0, 1])
42+
})
43+
4144
test('offset: offset of a geom2 produces expected changes to points', (t) => {
4245
const geometry = square({ size: 16 })
4346

packages/modeling/src/operations/offsets/offsetGeom3.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,9 @@ export const offsetGeom3 = (options, geometry) => {
2525
throw new Error('corners must be "round" for 3D geometries')
2626
}
2727

28-
const polygons = geom3.toPolygons(geometry)
29-
if (polygons.length === 0) throw new Error('the given geometry cannot be empty')
30-
3128
options = { delta, corners, segments }
3229
const expanded = offsetShell(options, geometry)
33-
return union(geometry, expanded)
30+
const output = union(geometry, expanded)
31+
if (geometry.color) output.color = geometry.color
32+
return output
3433
}

0 commit comments

Comments
 (0)