git.iko.soy/metal-metaballs
on master help sign in
Merged release-graph-logic into master
commitef01b885b165251455581e31f6b1886f89c1b530 authorvinivendra <vinivendra@gmail.com> dateFri 12 Feb 2016, 00:47 (11 years ago) parent59406ea95887c9
7 files changed+204−29
PixelImage.xcodeproj/project.pbxproj+8 PixelImage/Interpolation.swift+17 PixelImage/Matrix.swift+92 PixelImage/MetaballDataSource.swift+9−1 PixelImage/MetalMetaballRenderer.swift+10−10 PixelImage/ViewController.swift+47−3 PixelImage/shaders.metal+21−15
PixelImage.xcodeproj/project.pbxproj+8
@@ -12,6 +12,8 @@
1212 2426868D1C63B72A00D62456 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2426868B1C63B72A00D62456 /* Main.storyboard */; };
1313 2426868F1C63B72A00D62456 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2426868E1C63B72A00D62456 /* Assets.xcassets */; };
1414 242686921C63B72A00D62456 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 242686901C63B72A00D62456 /* LaunchScreen.storyboard */; };
15+ 2461651B1C6DA4F400A913C8 /* Interpolation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2461651A1C6DA4F400A913C8 /* Interpolation.swift */; };
16+ 249CF84C1C6D113C008B0C78 /* Matrix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 249CF84B1C6D113C008B0C78 /* Matrix.swift */; };
1517 24E1E7D71C67C8B200ECF1C4 /* UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E1E7D61C67C8B200ECF1C4 /* UIKit.swift */; };
1618 24E1E7E21C67D37F00ECF1C4 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24E1E7E11C67D37F00ECF1C4 /* Metal.framework */; };
1719 24E1E7E41C67D39300ECF1C4 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24E1E7E31C67D39300ECF1C4 /* MetalKit.framework */; };
@@ -57,6 +59,8 @@
5759 2426868E1C63B72A00D62456 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
5860 242686911C63B72A00D62456 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
5961 242686931C63B72A00D62456 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
62+ 2461651A1C6DA4F400A913C8 /* Interpolation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Interpolation.swift; path = PixelImage/Interpolation.swift; sourceTree = SOURCE_ROOT; };
63+ 249CF84B1C6D113C008B0C78 /* Matrix.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Matrix.swift; sourceTree = "<group>"; };
6064 24E1E7D61C67C8B200ECF1C4 /* UIKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKit.swift; sourceTree = "<group>"; };
6165 24E1E7E11C67D37F00ECF1C4 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; };
6266 24E1E7E31C67D39300ECF1C4 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; };
@@ -135,6 +139,7 @@
135139 24ED21CC1C6C2397009C40FC /* MetaballDataSource.swift */,
136140 24E1E7EA1C67D70300ECF1C4 /* MetalMetaballRenderer.swift */,
137141 24E1E7EE1C67D74300ECF1C4 /* Metal.swift */,
142+ 249CF84B1C6D113C008B0C78 /* Matrix.swift */,
138143 24E1E7EF1C67D74300ECF1C4 /* shaders.metal */,
139144 2426868B1C63B72A00D62456 /* Main.storyboard */,
140145 2426868E1C63B72A00D62456 /* Assets.xcassets */,
@@ -148,6 +153,7 @@
148153 isa = PBXGroup;
149154 children = (
150155 24FFA84C1C6BA038005ACD60 /* ApproximationOperator.swift */,
156+ 2461651A1C6DA4F400A913C8 /* Interpolation.swift */,
151157 24FFA84D1C6BA038005ACD60 /* Collections.swift */,
152158 24FFA84E1C6BA038005ACD60 /* ComparatorChains.swift */,
153159 24FFA84F1C6BA038005ACD60 /* Dictionary.swift */,
@@ -283,6 +289,7 @@
283289 24FFA8841C6BA038005ACD60 /* TakeDrop.swift in Sources */,
284290 24FFA8771C6BA038005ACD60 /* ChunkWindowSplit.swift in Sources */,
285291 24FFA86F1C6BA038005ACD60 /* NSNumber.swift in Sources */,
292+ 2461651B1C6DA4F400A913C8 /* Interpolation.swift in Sources */,
286293 24FFA8861C6BA038005ACD60 /* TestCommons.swift in Sources */,
287294 24FFA8741C6BA038005ACD60 /* SignOperator.swift in Sources */,
288295 24FFA8821C6BA038005ACD60 /* ScanReduce.swift in Sources */,
@@ -295,6 +302,7 @@
295302 24FFA8711C6BA038005ACD60 /* PowerOperator.swift in Sources */,
296303 24E1E7D71C67C8B200ECF1C4 /* UIKit.swift in Sources */,
297304 24FFA8851C6BA038005ACD60 /* Zip.swift in Sources */,
305+ 249CF84C1C6D113C008B0C78 /* Matrix.swift in Sources */,
298306 24FFA8791C6BA038005ACD60 /* Cycle.swift in Sources */,
299307 24E1E7F11C67D74300ECF1C4 /* shaders.metal in Sources */,
300308 24FFA86E1C6BA038005ACD60 /* Dictionary.swift in Sources */,
PixelImage/Interpolation.swiftnew file+17
@@ -0,0 +1,17 @@
1+
2+func interpolateCubeEaseIn<T: Numeric>(linear: T) -> T {
3+ return linear ^ 3
4+}
5+
6+func interpolateSquareEaseIn<T: Numeric>(linear: T) -> T {
7+ return linear ^ 2
8+}
9+
10+func interpolateSquareEaseOut<T: Numeric>(linear: T) -> T {
11+ return 1 - (1 - linear) * (1 - linear)
12+}
13+
14+func interpolateSmooth<T: Numeric>(linear: T) -> T {
15+ return ((linear) * (linear) * (linear) * ((linear) * ((linear) * 6 - 15) + 10))
16+}
17+
PixelImage/Matrix.swiftnew file+92
@@ -0,0 +1,92 @@
1+
2+import UIKit
3+
4+class Graph<T> {
5+ var adjacencyMatrix: Matrix
6+ var vertices: [T]
7+
8+ var size: Int {
9+ get {
10+ return vertices.count
11+ }
12+ }
13+
14+ init(vertices: [T]) {
15+ let size = vertices.count
16+
17+ self.vertices = vertices
18+ self.adjacencyMatrix = Matrix(size: size)
19+ }
20+
21+ func addEdge(i: Int, _ j: Int) {
22+ adjacencyMatrix.set(i, j)
23+ adjacencyMatrix.set(j, i)
24+ }
25+
26+ func removeEdge(i: Int, _ j: Int) {
27+ adjacencyMatrix.reset(i, j)
28+ adjacencyMatrix.reset(j, i)
29+ }
30+}
31+
32+struct Matrix: CustomStringConvertible {
33+ var buffer: [Float]
34+
35+ let size: Int
36+
37+ init(size: Int) {
38+ buffer = [Float](count: size * size, repeatedValue: 0.0)
39+ self.size = size
40+ }
41+
42+ func get(i: Int, j: Int) -> Float {
43+ return buffer[index(i, j)]
44+ }
45+
46+ mutating func set(i: Int, _ j: Int, value: Float = 1.0) {
47+ buffer[index(i, j)] = value
48+ }
49+
50+ mutating func reset(i: Int, _ j: Int) {
51+ buffer[index(i, j)] = 0.0
52+ }
53+
54+ private func index(i: Int, _ j: Int) -> Int {
55+ return j * size + i
56+ }
57+
58+ var description: String {
59+ get {
60+ var result = ""
61+ for (index, value) in buffer.enumerate() {
62+ result = result + "\(value)"
63+ if index % size == size - 1 {
64+ result = result + "\n"
65+ } else {
66+ result = result + " "
67+ }
68+ }
69+ return result
70+ }
71+ }
72+}
73+
74+class EdgeAnimationParameters {
75+ let startDate: NSDate
76+ let duration: Float
77+ let fadeIn: Bool
78+ let i: Int
79+ let j: Int
80+
81+ func unpack() -> (startDate: NSDate, duration: Float, fadeIn: Bool, i: Int, j: Int) {
82+ return (startDate, duration, fadeIn, i, j)
83+ }
84+
85+ init(startDate: NSDate, duration: Float, fadeIn: Bool, i: Int, j: Int) {
86+ self.startDate = startDate
87+ self.duration = duration
88+ self.fadeIn = fadeIn
89+ self.i = i
90+ self.j = j
91+ }
92+}
PixelImage/MetaballDataSource.swift+9−1
@@ -2,5 +2,13 @@
22import Metal
33
44protocol MetaballDataSource {
5− var metaballs: [Metaball] { get }
5+ var metaballGraph: Graph<Metaball>! { get }
66}
7+
8+extension MetaballDataSource {
9+ var metaballs: [Metaball]! {
10+ get {
11+ return metaballGraph.vertices
12+ }
13+ }
14+}
PixelImage/MetalMetaballRenderer.swift+10−10
@@ -1,14 +1,6 @@
1−// TODO: Make graphics have edges only according to graph (instead of being a complete graph)
2−
3−// TODO: Interpolate metaball colors
4−// TODO: Use metaballs as mask: apply strong blur inside, weak blur outside
5−// TODO: Add antialiasing
6−
71import Metal
82import UIKit
93
10−var i = 0
11−
124class MetalMetaballRenderer {
135
146 typealias TargetView = UIImageView
@@ -60,8 +52,6 @@ class MetalMetaballRenderer {
6052 let timeout = dispatch_time(DISPATCH_TIME_NOW, 1000000000)
6153 dispatch_semaphore_wait(semaphore, timeout)
6254
63− print("Update \(i++)!")
64−
6555 state = .Ending
6656
6757 dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.rawValue), 0)) { () -> Void in
@@ -114,6 +104,8 @@ class MetalMetaballRenderer {
114104 commandEncoder.setTexture(computeContext.texture, atIndex: 0)
115105 let metaballInfoBuffer = metaballBuffer()
116106 commandEncoder.setBuffer(metaballInfoBuffer, offset: 0, atIndex: 0)
107+ let edgesBuffer = metaballEdgesBuffer()
108+ commandEncoder.setBuffer(edgesBuffer, offset: 0, atIndex: 1)
117109 commandEncoder.dispatchThreadgroups(threadGroups,
118110 threadsPerThreadgroup: threadGroupCounts)
119111 commandEncoder.endEncoding()
@@ -169,6 +161,14 @@ class MetalMetaballRenderer {
169161 return image
170162 }
171163
164+ func metaballEdgesBuffer() -> MTLBuffer {
165+ let floats = dataSource.metaballGraph.adjacencyMatrix.buffer
166+
167+ let buffer = context.device.newBufferWithBytes(floats, length: floats.count * sizeof(Float), options: .StorageModeShared)
168+
169+ return buffer
170+ }
171+
172172 func metaballBuffer() -> MTLBuffer {
173173 // Create Float array for buffer
174174 // Exclude metaballs far from the view's bounds
PixelImage/ViewController.swift+47−3
@@ -9,19 +9,28 @@ class ViewController: UIViewController, MetaballDataSource {
99 let width = 350
1010 let height = 600
1111
12− var metaballs = [CGPoint(x: 70, y: 70), CGPoint(x: 270, y: 470), CGPoint(x: 270, y: 70), CGPoint(x: 70, y: 470)].map {
13− Metaball(position: $0)
14− }
12+ var metaballGraph: Graph<Metaball>!
1513 var previousLocation: CGPoint!
1614
1715 var selectedMetaball: Metaball?
1816
17+ let edgeAnimationDuration: Float = 1
18+
1919 override func viewDidLoad() {
2020 super.viewDidLoad()
2121
2222 let recognizer = UIPanGestureRecognizer(target: self, action: "handlePan:")
2323 view.addGestureRecognizer(recognizer)
2424
25+ let metaballs = [CGPoint(x: 70, y: 70), CGPoint(x: 270, y: 70), CGPoint(x: 270, y: 470), CGPoint(x: 70, y: 470)].map {
26+ Metaball(position: $0)
27+ }
28+ metaballGraph = Graph(vertices: metaballs)
29+ metaballGraph.addEdge(0, 1)
30+ metaballGraph.addEdge(0, 3)
31+ metaballGraph.addEdge(1, 2)
32+ metaballGraph.addEdge(2, 3)
33+
2534 let border = 20
2635 let metaballViewFrame = CGRect(x: border/2, y: border/2, width: width, height: height)
2736 renderer = MetalMetaballRenderer(dataSource: self, frame: metaballViewFrame)
@@ -36,6 +45,41 @@ class ViewController: UIViewController, MetaballDataSource {
3645 metaballView.addSubview(metaball)
3746 }
3847
48+ addEdge(0, 2)
49+
50+ renderer.state = .Running
51+ }
52+
53+ func addEdge(i: Int, _ j: Int) {
54+ animateEdge(i, j, fadeIn: true)
55+ }
56+
57+ func removeEdge(i: Int, _ j: Int) {
58+ animateEdge(i, j, fadeIn: false)
59+ }
60+
61+ func animateEdge(i: Int, _ j: Int, fadeIn: Bool) {
62+ let parameters = EdgeAnimationParameters(startDate: NSDate(), duration: edgeAnimationDuration, fadeIn: fadeIn, i: i, j: j)
63+ NSTimer.scheduledTimerWithTimeInterval(1.0/60.0, target: self, selector: "animateEdgeWithTimer:", userInfo: parameters, repeats: true)
64+ }
65+
66+ func animateEdge(withTimer timer: NSTimer) {
67+ let (animationStart, duration, fadeIn, i, j) = (timer.userInfo as! EdgeAnimationParameters).unpack()
68+
69+ let now = NSDate()
70+ var timeElapsed = Float(now.timeIntervalSinceDate(animationStart))
71+
72+ if timeElapsed > duration {
73+ timer.invalidate()
74+ timeElapsed = duration
75+ }
76+
77+ let linearValue = timeElapsed / duration
78+ let interpolatedValue = fadeIn ? interpolateSquareEaseOut(linearValue) : (1 - interpolateSquareEaseIn(linearValue))
79+
80+ metaballGraph.adjacencyMatrix.set(i, j, value: interpolatedValue)
81+ metaballGraph.adjacencyMatrix.set(j, i, value: interpolatedValue)
82+
3983 renderer.state = .Running
4084 }
4185
PixelImage/shaders.metal+21−15
@@ -5,40 +5,46 @@ using namespace metal;
55
66kernel void
77 drawMetaballs(texture2d<float, access::write> outTexture[[texture(0)]],
8− constant float *metaballBuffer [[buffer(0)]],
8+ constant float *edgesBuffer[[buffer(1)]],
9+ constant float *metaballBuffer[[buffer(0)]],
910 uint2 gid[[thread_position_in_grid]]) {
1011
11− char numberOfMetaballs = metaballBuffer[0];
12− char metaballArraySize = numberOfMetaballs * 2 + 1;
12+ uint numberOfMetaballs = metaballBuffer[0];
13+ uint metaballArraySize = numberOfMetaballs * 2 + 1;
1314
1415 float sum = 0;
1516
16− for (char i = 1; i < metaballArraySize; i += 2) {
17− for (char j = i + 2; j < metaballArraySize; j += 2) {
18− float2 metaball1 = float2(metaballBuffer[i],
19− metaballBuffer[i + 1]);
17+ uint i, j, x, y;
2018
21− float2 metaball2 = float2(metaballBuffer[j],
22− metaballBuffer[j + 1]);
19+ for (i = 1, x = 0; i < metaballArraySize; i += 2, x += 1) {
20+ for (j = i + 2, y = x + 1; j < metaballArraySize; j += 2, y += 1) {
21+ float2 metaball1 = float2(metaballBuffer[i], metaballBuffer[i + 1]);
22+
23+ float2 metaball2 = float2(metaballBuffer[j], metaballBuffer[j + 1]);
2324
2425 float2 metaball1Vector
2526 = float2(metaball1.x - gid.x, metaball1.y - gid.y);
2627 float2 metaball2Vector
2728 = float2(metaball2.x - gid.x, metaball2.y - gid.y);
2829
30+ float value1 = 2048 / (dot(metaball1Vector, metaball1Vector) + 1);
31+ float value2 = 2048 / (dot(metaball2Vector, metaball2Vector) + 1);
32+
33+ float v = value1 + value2;
34+
35+ float weightedValue = 0.5 * v;
36+
37+ char edgeIndex = y * numberOfMetaballs + x;
38+ float edgeWeight = edgesBuffer[edgeIndex];
2939 float2 direction1 = normalize(metaball1Vector);
3040 float2 direction2 = normalize(metaball2Vector);
3141
3242 float cosine = dot(direction1, direction2);
3343
34− float value1 = 2048 / (dot(metaball1Vector, metaball1Vector) + 1);
35− float value2 = 2048 / (dot(metaball2Vector, metaball2Vector) + 1);
36−
37− float v = value1 + value2;
3844 float link = pow(((1 - cosine) * 0.5), 100);
3945
40− float weightedLink = 0.6 * link;
41− float weightedValue = 0.5 * v;
46+ float weightedLink = 0.6 * link * edgeWeight;
47+
4248
4349 sum += mix(0.0, 1.0, step(0.5, weightedValue + weightedLink));
4450 }