Skip to content

Commit 067bf7c

Browse files
committed
Fix hull and minkowski color caching
1 parent fdf81c1 commit 067bf7c

File tree

7 files changed

+333
-54
lines changed

7 files changed

+333
-54
lines changed

ShapeScript/Geometry.swift

Lines changed: 122 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -140,12 +140,13 @@ public final class Geometry: Hashable {
140140
debug: Bool = false
141141
) {
142142
var material = material
143+
var useMaterialForCache = false
143144
var children = children
144145
var type = type
145146
switch type {
146147
case var .extrude(paths, options):
147-
(paths, material) = paths.fixupColors(material: material)
148-
(options.along, material) = options.along.fixupColors(material: material)
148+
(paths, material) = paths.vertexColorsToMaterial(material: material)
149+
(options.along, material) = options.along.vertexColorsToMaterial(material: material)
149150
type = .extrude(paths, options)
150151
switch (paths.count, options.along.count) {
151152
case (0, 0):
@@ -158,7 +159,9 @@ public final class Geometry: Hashable {
158159
assert(children.isEmpty)
159160
type = .extrude([], .default)
160161
children = paths.map { path in
161-
Geometry(
162+
var path = path
163+
(path, material) = path.vertexColorsToMaterial(material: material)
164+
return Geometry(
162165
type: .extrude([path], options),
163166
name: nil,
164167
transform: .identity,
@@ -170,7 +173,7 @@ public final class Geometry: Hashable {
170173
}
171174
}
172175
case .lathe(var paths, let segments):
173-
(paths, material) = paths.fixupColors(material: material)
176+
(paths, material) = paths.vertexColorsToMaterial(material: material)
174177
type = .lathe(paths, segments: segments)
175178
switch paths.count {
176179
case 0:
@@ -196,7 +199,7 @@ public final class Geometry: Hashable {
196199
}
197200
case var .fill(paths):
198201
// TODO: why didn't we apply the same logic as above for fill?
199-
(paths, material) = paths.fixupColors(material: material)
202+
(paths, material) = paths.vertexColorsToMaterial(material: material)
200203
type = .fill(paths)
201204
switch paths.count {
202205
case 0:
@@ -208,7 +211,9 @@ public final class Geometry: Hashable {
208211
assert(children.isEmpty)
209212
case let .mesh(mesh):
210213
material = mesh.polygons.first?.material as? Material ?? material
211-
case .union, .xor, .difference, .intersection, .stencil, .hull, .minkowski:
214+
case .hull, .minkowski:
215+
useMaterialForCache = true
216+
case .union, .xor, .difference, .intersection, .stencil:
212217
material = children.first?.material ?? .default
213218
case .group:
214219
if debug {
@@ -225,9 +230,13 @@ public final class Geometry: Hashable {
225230
self._sourceLocation = sourceLocation
226231
self.debug = debug
227232

233+
var hasVariedMaterials = false
228234
var isOpaque = material.isOpaque
229235
func flattenedCacheKey(for geometry: Geometry) -> GeometryCache.Key {
230236
isOpaque = isOpaque && geometry.material.isOpaque
237+
if !hasVariedMaterials, geometry.material != material {
238+
hasVariedMaterials = true
239+
}
231240
return GeometryCache.Key(
232241
type: geometry.type,
233242
material: geometry.material == material ? nil : geometry.material,
@@ -238,18 +247,19 @@ public final class Geometry: Hashable {
238247
)
239248
}
240249

241-
self.cacheKey = GeometryCache.Key(
250+
let childKeys = type.isLeafGeometry ? [] : children.map(flattenedCacheKey)
251+
252+
// Must be set after child keys are generated
253+
self.isOpaque = isOpaque
254+
self.cacheKey = .init(
242255
type: type,
243-
material: nil,
256+
material: useMaterialForCache && hasVariedMaterials ? material : nil,
244257
smoothing: smoothing,
245258
transform: .identity,
246259
flipped: transform.isFlipped,
247-
children: type.isLeafGeometry ? [] : children.map(flattenedCacheKey)
260+
children: childKeys
248261
)
249262

250-
// Must be set after cache key is generated
251-
self.isOpaque = isOpaque
252-
253263
// Compute the overestimated, non-transformed bounds
254264
switch type {
255265
case .difference, .stencil:
@@ -601,9 +611,10 @@ private extension Geometry {
601611
case let .loft(paths):
602612
mesh = Mesh.loft(paths).makeWatertight()
603613
case let .hull(vertices):
604-
let m = Mesh.convexHull(of: vertices, material: Material.default, isCancelled: isCancelled)
614+
let m = Mesh.convexHull(of: vertices, material: material, isCancelled: isCancelled)
605615
let meshes = ([m] + childMeshes(callback)).map { $0.materialToVertexColors(material: material) }
606-
mesh = .convexHull(of: meshes, isCancelled: isCancelled).fixupColors(material: material)
616+
mesh = .convexHull(of: meshes, isCancelled: isCancelled)
617+
.vertexColorsToMaterial(material: material).replacing(material, with: nil)
607618
case .minkowski:
608619
var children = ArraySlice(children.enumerated().sorted {
609620
switch ($0.1.type, $1.1.type) {
@@ -648,14 +659,16 @@ private extension Geometry {
648659
sum = first.flattened(callback).materialToVertexColors(material: first.material)
649660
}
650661
while let next = children.popFirst() {
651-
if let path = next.path?.transformed(by: next.transform) {
662+
if var path = next.path?.transformed(by: next.transform) {
663+
path = path.materialToVertexColors(material: next.material)
652664
sum = sum.minkowskiSum(with: path, isCancelled: isCancelled)
653665
} else {
654666
let mesh = next.flattened(callback).materialToVertexColors(material: next.material)
655667
sum = sum.minkowskiSum(with: mesh, isCancelled: isCancelled)
656668
}
657669
}
658-
mesh = sum.fixupColors(material: material).makeWatertight()
670+
mesh = sum.vertexColorsToMaterial(material: material)
671+
.replacing(material, with: nil).makeWatertight()
659672
case let .fill(paths):
660673
mesh = Mesh.fill(paths.map { $0.closed() }, isCancelled: isCancelled).makeWatertight()
661674
case .union, .lathe, .extrude:
@@ -756,70 +769,130 @@ private extension Geometry {
756769
}
757770
}
758771

759-
private extension Collection<Path> {
772+
private extension [Path] {
773+
/// Returns the uniform color of all vertices, or nil if they have different colors
774+
var uniformVertexColor: Color? {
775+
let uniformColor = first?.uniformVertexColor ?? .white
776+
return allSatisfy { $0.uniformVertexColor == uniformColor } ? uniformColor : nil
777+
}
778+
760779
/// Convert uniform point colors to a material instead
761-
func fixupColors(material: Material) -> ([Path], Material) {
780+
func vertexColorsToMaterial(material: Material) -> ([Path], Material) {
762781
guard material.texture == nil else {
763-
return (Array(self), material)
764-
}
765-
var current: Color?
766-
for path in self {
767-
for point in path.points {
768-
if current == nil {
769-
current = point.color
770-
} else if point.color != current {
771-
var material = material
772-
material.albedo = .color(.white)
773-
return (Array(self), material)
774-
}
782+
return (self, material)
783+
}
784+
if let uniformVertexColor {
785+
if uniformVertexColor == .white {
786+
return (self, material)
775787
}
788+
var material = material
789+
material.albedo = .color(uniformVertexColor)
790+
return (map { $0.withColor(nil) }, material)
776791
}
777792
var material = material
778-
material.albedo = (current ?? material.color).map { .color($0) }
779-
return (map { $0.withColor(nil) }, material)
793+
material.albedo = .color(.white)
794+
return (self, material)
795+
}
796+
}
797+
798+
extension Path {
799+
/// Returns the uniform color of all vertices, or nil if they have different colors
800+
var uniformVertexColor: Color? {
801+
let uniformColor = points.first?.color
802+
return points.allSatisfy { $0.color == uniformColor } ? (uniformColor ?? .white) : nil
803+
}
804+
805+
/// Convert uniform point colors to a material instead
806+
func vertexColorsToMaterial(material: Material) -> (Path, Material) {
807+
guard material.texture == nil else {
808+
return (self, material)
809+
}
810+
if let uniformVertexColor {
811+
if uniformVertexColor == .white {
812+
return (self, material)
813+
}
814+
var material = material
815+
material.albedo = .color(uniformVertexColor)
816+
return (withColor(nil), material)
817+
}
818+
var material = material
819+
material.albedo = .color(.white)
820+
return (self, material)
821+
}
822+
823+
/// Convert material color to vertex colors
824+
func materialToVertexColors(material: ShapeScript.Material?) -> Path {
825+
guard let color = material?.color, color != .white, !hasColors else {
826+
return self
827+
}
828+
return withColor(color)
780829
}
781830
}
782831

783832
extension Polygon {
833+
/// Returns the uniform color of all vertices, or nil if they have different colors
834+
var uniformVertexColor: Color? {
835+
let uniformColor = vertices.first?.color ?? .white
836+
return vertices.allSatisfy { $0.color == uniformColor } ? uniformColor : nil
837+
}
838+
784839
/// Convert uniform vertex colors to a material instead
785-
func fixupColors(material: ShapeScript.Material) -> Polygon {
840+
func vertexColorsToMaterial(material: ShapeScript.Material) -> Polygon {
841+
var material = self.material as? ShapeScript.Material ?? material
786842
guard material.texture == nil else {
787843
return withMaterial(material)
788844
}
789-
var current: Color?
790-
for point in vertices {
791-
if current == nil {
792-
current = point.color
793-
} else if point.color != current {
794-
var material = material
795-
material.albedo = .color(.white)
845+
if let uniformVertexColor {
846+
if uniformVertexColor == .white {
796847
return withMaterial(material)
797848
}
849+
var material = material
850+
material.albedo = .color(uniformVertexColor)
851+
return withoutVertexColors().withMaterial(material)
798852
}
799-
var material = material
800-
material.albedo = (current ?? material.color).map { .color($0) }
801-
return mapVertexColors { _ in nil }.withMaterial(material)
853+
material.albedo = .color(.white)
854+
return withMaterial(material)
802855
}
803856

804-
/// Convert material colors to a vertex colors
857+
/// Convert material color to vertex colors
805858
func materialToVertexColors(material: ShapeScript.Material?) -> Polygon {
806859
guard var material = self.material as? ShapeScript.Material ?? material,
807-
let color = material.color
860+
let color = material.color, color != .white,
861+
!hasVertexColors
808862
else {
809863
return self
810864
}
811865
material.albedo = .color(.white)
812-
return mapVertexColors { $0 * color }.withMaterial(material)
866+
return mapVertexColors { _ in color }.withMaterial(material)
813867
}
814868
}
815869

816870
extension Mesh {
871+
/// Returns the uniform color of all vertices, or nil if they have different colors
872+
var uniformVertexColor: Color? {
873+
let uniformColor = polygons.first?.uniformVertexColor ?? .white
874+
return polygons.allSatisfy { $0.uniformVertexColor == uniformColor } ? uniformColor : nil
875+
}
876+
817877
/// Convert uniform vertex colors to a material instead
818-
func fixupColors(material: ShapeScript.Material) -> Mesh {
819-
.init(polygons.map { $0.fixupColors(material: material) })
878+
func vertexColorsToMaterial(material: ShapeScript.Material) -> Mesh {
879+
guard material.texture == nil else {
880+
return withMaterial(material)
881+
}
882+
if let uniformVertexColor {
883+
if uniformVertexColor == .white {
884+
return withMaterial(material)
885+
}
886+
var material = material
887+
material.albedo = .color(uniformVertexColor)
888+
return withoutVertexColors().withMaterial(material)
889+
}
890+
var material = material
891+
material.albedo = .color(.white)
892+
return withMaterial(material)
820893
}
821894

822-
/// Convert material colors to a vertex colors
895+
/// Convert material colors to vertex colors
823896
func materialToVertexColors(material: ShapeScript.Material?) -> Mesh {
824897
.init(polygons.map { $0.materialToVertexColors(material: material) })
825898
}

ShapeScript/GeometryCache.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ import LRUCache
1515
public final class GeometryCache {
1616
private let cache: LRUCache<Key, (mesh: Mesh, associatedData: [Material: Any])>
1717

18+
/// The number of entries currently stored in the cache
19+
public var count: Int { cache.count }
20+
21+
/// Initialize the cache with a given storage limit
22+
/// - Parameter memoryLimit: The maximum amount of data to cache (in bytes)
1823
public init(memoryLimit: Int = 1_000_000_000) {
1924
self.cache = LRUCache(totalCostLimit: memoryLimit)
2025
}

ShapeScript/Interpreter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -948,7 +948,7 @@ extension EvaluationContext {
948948
case let .polygon(p):
949949
children.append(.polygon(p
950950
.transformed(by: childTransform)
951-
.fixupColors(material: material)))
951+
.vertexColorsToMaterial(material: material)))
952952
case let .path(path):
953953
children.append(.path(path.transformed(by: childTransform)))
954954
case _ where childTypes.subtypes.contains(.text):

ShapeScript/Scene+SceneKit.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,7 @@ public extension Geometry {
223223
width: options.lineWidth,
224224
detail: 5
225225
))
226-
let lineColor = path.hasColors ?
227-
Color.white : material.color ?? options.lineColor
226+
let lineColor = path.hasColors ? Color.white : material.color ?? options.lineColor
228227
let material = SCNMaterial()
229228
material.lightingModel = .constant
230229
material.diffuse.contents = OSColor(lineColor)

ShapeScript/StandardLibrary.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ extension Symbols {
225225
"fill": .block(.builder) { context in
226226
.mesh(Geometry(type: .fill(context.paths), in: context))
227227
},
228-
"hull": .block(.init(.hull, [:], .union([.point, .path, .mesh]), .mesh)) { context in
228+
"hull": .block(.hull) { context in
229229
let vertices = try context.children.flatMap { child -> [Vertex] in
230230
switch child {
231231
case let .point(point):
@@ -245,7 +245,7 @@ extension Symbols {
245245
}
246246
return .mesh(Geometry(type: .hull(vertices), in: context))
247247
},
248-
"minkowski": .block(.group) { context in
248+
"minkowski": .block(.minkowski) { context in
249249
.mesh(Geometry(type: .minkowski, in: context))
250250
},
251251
// mesh

ShapeScript/Types.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,8 @@ extension BlockType {
307307
static let group: Self = .init(.group, [:], .mesh, .mesh)
308308
static let path: Self = .init(.path, [:], .union([.point, .path]), .path)
309309
static let pathShape: Self = .init(.pathShape, [:], .void, .path)
310+
static let hull: Self = .init(.hull, [:], .union([.point, .path, .mesh]), .mesh)
311+
static let minkowski: Self = .init(.group, [:], .mesh, .mesh)
310312
}
311313

312314
// MARK: Inference

0 commit comments

Comments
 (0)