Skip to content

Commit 16847b4

Browse files
committed
Fix color blending
1 parent 969ae80 commit 16847b4

File tree

4 files changed

+211
-65
lines changed

4 files changed

+211
-65
lines changed

ShapeScript/Geometry.swift

Lines changed: 107 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -145,19 +145,22 @@ public final class Geometry: Hashable {
145145
var type = type
146146
switch type {
147147
case var .extrude(paths, options):
148-
(paths, material) = paths.vertexColorsToMaterial(material: material)
149-
(options.along, material) = options.along.vertexColorsToMaterial(material: material)
150148
switch (paths.count, options.along.count) {
151149
case (0, 0):
152150
break
153-
case (1, 1), (1, 0):
154-
assert(children.isEmpty)
151+
case (1, 0):
152+
(paths, material) = paths.vertexColorsToMaterial(material: material)
155153
type = .extrude(paths, options)
154+
case (1, 1):
155+
var pair = (paths + options.along)
156+
(pair, material) = pair.vertexColorsToMaterial(material: material)
157+
options.along = [pair[1]]
158+
type = .extrude([pair[0]], options)
156159
case (_, 0):
157-
assert(children.isEmpty)
158160
type = .extrude([], .default)
159161
children = paths.map { path in
160-
Geometry(
162+
let (path, material) = path.vertexColorsToMaterial(material: material)
163+
return Geometry(
161164
type: .extrude([path], options),
162165
name: nil,
163166
transform: .identity,
@@ -167,17 +170,18 @@ public final class Geometry: Hashable {
167170
sourceLocation: sourceLocation
168171
)
169172
}
173+
material = children.first?.material ?? .default
170174
default:
171175
// For extrusions with multiple paths, convert each path to a
172176
// separate child geometry so they can be renderered individually
173-
assert(children.isEmpty)
174177
type = .extrude([], .default)
175178
children = paths.flatMap { path in
176179
options.along.map { along in
180+
let (pair, material) = [path, along].vertexColorsToMaterial(material: material)
177181
var options = options
178-
options.along = [along]
182+
options.along = [pair[1]]
179183
return Geometry(
180-
type: .extrude([path], options),
184+
type: .extrude([pair[0]], options),
181185
name: nil,
182186
transform: .identity,
183187
material: material,
@@ -187,23 +191,23 @@ public final class Geometry: Hashable {
187191
)
188192
}
189193
}
194+
material = children.first?.material ?? .default
190195
}
191196
case .lathe(var paths, let segments):
192-
(paths, material) = paths.vertexColorsToMaterial(material: material)
193197
switch paths.count {
194198
case 0:
195199
break
196200
case 1:
197-
assert(children.isEmpty)
201+
(paths, material) = paths.vertexColorsToMaterial(material: material)
198202
type = .lathe(paths, segments: segments)
199203
default:
200204
// For lathes with multiple paths, convert each path to a
201205
// separate child geometry so they can be renderered individually
202-
assert(children.isEmpty)
203206
type = .lathe([], segments: 0)
204-
children = paths.map {
205-
Geometry(
206-
type: .lathe([$0], segments: segments),
207+
children = paths.map { path in
208+
let (path, material) = path.vertexColorsToMaterial(material: material)
209+
return Geometry(
210+
type: .lathe([path], segments: segments),
207211
name: nil,
208212
transform: .identity,
209213
material: material,
@@ -212,23 +216,23 @@ public final class Geometry: Hashable {
212216
sourceLocation: sourceLocation
213217
)
214218
}
219+
material = children.first?.material ?? .default
215220
}
216221
case var .fill(paths):
217-
(paths, material) = paths.vertexColorsToMaterial(material: material)
218222
switch paths.count {
219223
case 0:
220224
break
221225
case 1:
222-
assert(children.isEmpty)
226+
(paths, material) = paths.vertexColorsToMaterial(material: material)
223227
type = .fill(paths)
224228
default:
225229
// For fills with multiple paths, convert each path to a
226230
// separate child geometry so they can be renderered individually
227-
assert(children.isEmpty)
228231
type = .fill([])
229-
children = paths.map {
230-
Geometry(
231-
type: .fill([$0]),
232+
children = paths.map { path in
233+
let (path, material) = path.vertexColorsToMaterial(material: material)
234+
return Geometry(
235+
type: .fill([path]),
232236
name: nil,
233237
transform: .identity,
234238
material: material,
@@ -237,7 +241,14 @@ public final class Geometry: Hashable {
237241
sourceLocation: sourceLocation
238242
)
239243
}
244+
material = children.first?.material ?? .default
240245
}
246+
case var .loft(paths):
247+
(paths, material) = paths.vertexColorsToMaterial(material: material)
248+
type = .loft(paths)
249+
case var .path(path):
250+
(path, material) = path.vertexColorsToMaterial(material: material)
251+
type = .path(path)
241252
case let .mesh(mesh):
242253
material = mesh.polygons.first?.material as? Material ?? material
243254
case .hull, .minkowski:
@@ -248,7 +259,7 @@ public final class Geometry: Hashable {
248259
if debug {
249260
children.forEach { $0.debug = true }
250261
}
251-
case .cone, .cylinder, .sphere, .cube, .loft, .path, .camera, .light:
262+
case .cone, .cylinder, .sphere, .cube, .camera, .light:
252263
break
253264
}
254265

@@ -626,26 +637,30 @@ private extension Geometry {
626637
case .cube:
627638
mesh = .cube()
628639
case let .extrude(paths, .default) where paths.count == 1:
629-
mesh = Mesh.extrude(paths[0]).makeWatertight()
640+
mesh = .extrude(paths[0]).makeWatertight()
630641
case let .extrude(paths, options) where paths.count == 1 && options.along.count == 1:
631-
mesh = Mesh.extrude(
632-
paths[0],
633-
along: options.along[0],
642+
mesh = .extrude(
643+
paths[0].materialToVertexColors(material: material),
644+
along: options.along[0].materialToVertexColors(material: material).predividedBy(material),
634645
twist: options.twist,
635646
align: options.align,
636647
isCancelled: isCancelled
637-
).makeWatertight()
648+
)
649+
.vertexColorsToMaterial(material: material)
650+
.replacing(material, with: nil)
651+
.makeWatertight()
638652
case let .lathe(paths, segments: segments) where paths.count == 1:
639-
mesh = Mesh.lathe(paths[0], slices: segments).makeWatertight()
653+
mesh = .lathe(paths[0], slices: segments, isCancelled: isCancelled).makeWatertight()
640654
case let .fill(paths) where paths.count == 1:
641-
mesh = Mesh.fill([paths[0].closed()], isCancelled: isCancelled).makeWatertight()
655+
mesh = .fill(paths[0].closed()).makeWatertight()
642656
case let .loft(paths):
643-
mesh = Mesh.loft(paths).makeWatertight()
657+
mesh = .loft(paths, isCancelled: isCancelled).makeWatertight()
644658
case let .hull(vertices):
645-
let m = Mesh.convexHull(of: vertices, material: material, isCancelled: isCancelled)
646-
let meshes = ([m] + childMeshes(callback)).map { $0.materialToVertexColors(material: material) }
659+
let base = Mesh.convexHull(of: vertices, material: material, isCancelled: isCancelled)
660+
let meshes = ([base] + childMeshes(callback)).map { $0.materialToVertexColors(material: material) }
647661
mesh = .convexHull(of: meshes, isCancelled: isCancelled)
648-
.vertexColorsToMaterial(material: material).replacing(material, with: nil)
662+
.vertexColorsToMaterial(material: material)
663+
.replacing(material, with: nil)
649664
case .minkowski:
650665
var children = ArraySlice(children.enumerated().sorted {
651666
switch ($0.1.type, $1.1.type) {
@@ -676,45 +691,55 @@ private extension Geometry {
676691
var sum: Mesh
677692
if let shape = first.path?.transformed(by: first.transform) {
678693
guard let next = children.popFirst() else {
679-
sum = .empty
694+
mesh = .empty
680695
break
681696
}
697+
let shape = shape.materialToVertexColors(material: first.material)
682698
if let path = next.path?.transformed(by: next.transform) {
683-
let mesh = Mesh.fill(shape).materialToVertexColors(material: first.material)
684-
sum = mesh.minkowskiSum(with: path, isCancelled: isCancelled)
699+
sum = .fill(shape).minkowskiSum(
700+
with: path.materialToVertexColors(material: next.material),
701+
isCancelled: isCancelled
702+
)
685703
} else {
686-
let mesh = next.flattened(callback).materialToVertexColors(material: next.material)
687-
sum = mesh.minkowskiSum(with: shape, isCancelled: isCancelled)
704+
sum = next.flattened(callback).materialToVertexColors(material: next.material).minkowskiSum(
705+
with: shape,
706+
isCancelled: isCancelled
707+
)
688708
}
689709
} else {
690710
sum = first.flattened(callback).materialToVertexColors(material: first.material)
691711
}
692712
while let next = children.popFirst() {
693-
if var path = next.path?.transformed(by: next.transform) {
694-
path = path.materialToVertexColors(material: next.material)
695-
sum = sum.minkowskiSum(with: path, isCancelled: isCancelled)
713+
if let path = next.path?.transformed(by: next.transform) {
714+
sum = sum.minkowskiSum(
715+
with: path.materialToVertexColors(material: next.material).predividedBy(first.material),
716+
isCancelled: isCancelled
717+
)
696718
} else {
697-
let mesh = next.flattened(callback).materialToVertexColors(material: next.material)
698-
sum = sum.minkowskiSum(with: mesh, isCancelled: isCancelled)
719+
sum = sum.minkowskiSum(
720+
with: next.flattened(callback).materialToVertexColors(material: next.material),
721+
isCancelled: isCancelled
722+
)
699723
}
700724
}
701725
mesh = sum.vertexColorsToMaterial(material: material)
702-
.replacing(material, with: nil).makeWatertight()
726+
.replacing(material, with: nil)
727+
.makeWatertight()
703728
case .union, .lathe, .extrude, .fill:
704-
mesh = Mesh.union(childMeshes(callback), isCancelled: isCancelled).makeWatertight()
729+
mesh = .union(childMeshes(callback), isCancelled: isCancelled).makeWatertight()
705730
case .xor:
706-
mesh = Mesh.symmetricDifference(flattenedChildren(callback), isCancelled: isCancelled).makeWatertight()
731+
mesh = .symmetricDifference(flattenedChildren(callback), isCancelled: isCancelled).makeWatertight()
707732
case .difference:
708733
let first = flattenedFirstChild(callback)
709734
let meshes = [first] + children.dropFirst().meshes(with: material, callback)
710-
mesh = Mesh.difference(meshes, isCancelled: isCancelled).makeWatertight()
735+
mesh = .difference(meshes, isCancelled: isCancelled).makeWatertight()
711736
case .intersection:
712737
let meshes = flattenedChildren(callback)
713-
mesh = Mesh.intersection(meshes, isCancelled: isCancelled).makeWatertight()
738+
mesh = .intersection(meshes, isCancelled: isCancelled).makeWatertight()
714739
case .stencil:
715740
let first = flattenedFirstChild(callback)
716741
let meshes = [first] + children.dropFirst().meshes(with: material, callback)
717-
mesh = Mesh.stencil(meshes, isCancelled: isCancelled).makeWatertight()
742+
mesh = .stencil(meshes, isCancelled: isCancelled).makeWatertight()
718743
case let .mesh(mesh):
719744
self.mesh = mesh
720745
}
@@ -798,11 +823,34 @@ private extension Geometry {
798823
}
799824
}
800825

826+
private extension Color {
827+
func predividedBy(_ other: Color) -> Color {
828+
.init(
829+
other.r > 0 ? r / other.r : r,
830+
other.g > 0 ? g / other.g : g,
831+
other.b > 0 ? b / other.b : b,
832+
other.a > 0 ? a / other.a : a
833+
)
834+
}
835+
}
836+
837+
private extension Material {
838+
func predividedBy(_ other: Material) -> Material {
839+
var result = self
840+
result.albedo = .color({
841+
let lhs = color ?? .white
842+
let rhs = other.color ?? .white
843+
return lhs.predividedBy(rhs)
844+
}())
845+
return result
846+
}
847+
}
848+
801849
private extension [Path] {
802850
/// Returns the uniform color of all vertices, or nil if they have different colors
803851
var uniformVertexColor: Color? {
804852
let uniformColor = first?.uniformVertexColor ?? .white
805-
return allSatisfy { $0.uniformVertexColor == uniformColor } ? uniformColor : nil
853+
return allSatisfy { [uniformColor, .white].contains($0.uniformVertexColor) } ? uniformColor : nil
806854
}
807855

808856
/// Convert uniform point colors to a material instead
@@ -827,8 +875,8 @@ private extension [Path] {
827875
extension Path {
828876
/// Returns the uniform color of all vertices, or nil if they have different colors
829877
var uniformVertexColor: Color? {
830-
let uniformColor = points.first?.color
831-
return points.allSatisfy { $0.color == uniformColor } ? (uniformColor ?? .white) : nil
878+
let uniformColor = points.first?.color ?? .white
879+
return points.allSatisfy { $0.color ?? .white == uniformColor } ? uniformColor : nil
832880
}
833881

834882
/// Convert uniform point colors to a material instead
@@ -849,13 +897,17 @@ extension Path {
849897
return (self, material)
850898
}
851899

852-
/// Convert material color to vertex colors
900+
/// Convert material color to vertex colors, preserving the existing vertex colors if set
853901
func materialToVertexColors(material: ShapeScript.Material?) -> Path {
854902
guard let color = material?.color, color != .white, !hasColors else {
855903
return self
856904
}
857905
return withColor(color)
858906
}
907+
908+
func predividedBy(_ other: Material) -> Path {
909+
mapColors { $0?.predividedBy(other.color ?? .white) }
910+
}
859911
}
860912

861913
extension Polygon {
@@ -883,7 +935,7 @@ extension Polygon {
883935
return withMaterial(material)
884936
}
885937

886-
/// Convert material color to vertex colors
938+
/// Convert material colors to vertex colors, preserving the existing vertex colors if set
887939
func materialToVertexColors(material: ShapeScript.Material?) -> Polygon {
888940
guard var material = self.material as? ShapeScript.Material ?? material,
889941
let color = material.color, color != .white,
@@ -921,7 +973,7 @@ extension Mesh {
921973
return withMaterial(material)
922974
}
923975

924-
/// Convert material colors to vertex colors
976+
/// Convert material colors to vertex colors, preserving the existing vertex colors if set
925977
func materialToVertexColors(material: ShapeScript.Material?) -> Mesh {
926978
.init(polygons.map { $0.materialToVertexColors(material: material) })
927979
}

ShapeScript/StandardLibrary.swift

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -233,10 +233,7 @@ extension Symbols {
233233
case let .path(path):
234234
return path.subpaths.flatMap(\.edgeVertices)
235235
case let .mesh(geometry):
236-
if let path = geometry.path {
237-
return path.subpaths.flatMap(\.edgeVertices)
238-
}
239-
return [] // handled at mesh generation time
236+
return geometry.path?.subpaths.flatMap(\.edgeVertices) ?? []
240237
default:
241238
throw RuntimeErrorType.assertionFailure(
242239
"Unexpected child of type \(child.type) in hull"

0 commit comments

Comments
 (0)