git.iko.soy/metal-metaballs
on master help sign in
Refactored shader and renderer to accept graph data.
7fdd9e1vinivendra
PixelImage/Matrix.swift+10−4
@@ -5,6 +5,12 @@ class Graph<T> {
5 var adjacencyMatrix: Matrix
5 var adjacencyMatrix: Matrix
6 var vertices: [T]
6 var vertices: [T]
7
7
8+ var size: Int {
9+ get {
10+ return vertices.count
11+ }
12+ }
13+
8 init(vertices: [T]) {
14 init(vertices: [T]) {
9 let size = vertices.count
15 let size = vertices.count
10
16
@@ -24,20 +30,20 @@ class Graph<T> {
24}
30}
25
31
26struct Matrix: CustomStringConvertible {
32struct Matrix: CustomStringConvertible {
27− private var buffer: [CGFloat]
33+ var buffer: [Float]
28
34
29 let size: Int
35 let size: Int
30
36
31 init(size: Int) {
37 init(size: Int) {
32− buffer = [CGFloat](count: size * size, repeatedValue: 0.0)
38+ buffer = [Float](count: size * size, repeatedValue: 0.0)
33 self.size = size
39 self.size = size
34 }
40 }
35
41
36− func get(i: Int, j: Int) -> CGFloat {
42+ func get(i: Int, j: Int) -> Float {
37 return buffer[index(i, j)]
43 return buffer[index(i, j)]
38 }
44 }
39
45
40− mutating func set(i: Int, _ j: Int, value: CGFloat = 1.0) {
46+ mutating func set(i: Int, _ j: Int, value: Float = 1.0) {
41 buffer[index(i, j)] = value
47 buffer[index(i, j)] = value
42 }
48 }
43
49
PixelImage/MetalMetaballRenderer.swift+10−4
@@ -1,8 +1,6 @@
1import Metal
1import Metal
2import UIKit
2import UIKit
3
3
4−var i = 0
5−
6class MetalMetaballRenderer {
4class MetalMetaballRenderer {
7
5
8 typealias TargetView = UIImageView
6 typealias TargetView = UIImageView
@@ -54,8 +52,6 @@ class MetalMetaballRenderer {
54 let timeout = dispatch_time(DISPATCH_TIME_NOW, 1000000000)
52 let timeout = dispatch_time(DISPATCH_TIME_NOW, 1000000000)
55 dispatch_semaphore_wait(semaphore, timeout)
53 dispatch_semaphore_wait(semaphore, timeout)
56
54
57− print("Update \(i++)!")
58−
59 state = .Ending
55 state = .Ending
60
56
61 dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.rawValue), 0)) { () -> Void in
57 dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.rawValue), 0)) { () -> Void in
@@ -108,6 +104,8 @@ class MetalMetaballRenderer {
108 commandEncoder.setTexture(computeContext.texture, atIndex: 0)
104 commandEncoder.setTexture(computeContext.texture, atIndex: 0)
109 let metaballInfoBuffer = metaballBuffer()
105 let metaballInfoBuffer = metaballBuffer()
110 commandEncoder.setBuffer(metaballInfoBuffer, offset: 0, atIndex: 0)
106 commandEncoder.setBuffer(metaballInfoBuffer, offset: 0, atIndex: 0)
107+ let edgesBuffer = metaballEdgesBuffer()
108+ commandEncoder.setBuffer(edgesBuffer, offset: 0, atIndex: 1)
111 commandEncoder.dispatchThreadgroups(threadGroups,
109 commandEncoder.dispatchThreadgroups(threadGroups,
112 threadsPerThreadgroup: threadGroupCounts)
110 threadsPerThreadgroup: threadGroupCounts)
113 commandEncoder.endEncoding()
111 commandEncoder.endEncoding()
@@ -163,6 +161,14 @@ class MetalMetaballRenderer {
163 return image
161 return image
164 }
162 }
165
163
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+
166 func metaballBuffer() -> MTLBuffer {
172 func metaballBuffer() -> MTLBuffer {
167 // Create Float array for buffer
173 // Create Float array for buffer
168 // Exclude metaballs far from the view's bounds
174 // Exclude metaballs far from the view's bounds
PixelImage/ViewController.swift+7−1
@@ -20,10 +20,16 @@ class ViewController: UIViewController, MetaballDataSource {
20 let recognizer = UIPanGestureRecognizer(target: self, action: "handlePan:")
20 let recognizer = UIPanGestureRecognizer(target: self, action: "handlePan:")
21 view.addGestureRecognizer(recognizer)
21 view.addGestureRecognizer(recognizer)
22
22
23− let metaballs = [CGPoint(x: 70, y: 70), CGPoint(x: 270, y: 470), CGPoint(x: 270, y: 70), CGPoint(x: 70, y: 470)].map {
23+ let metaballs = [CGPoint(x: 70, y: 70), CGPoint(x: 270, y: 70), CGPoint(x: 270, y: 470), CGPoint(x: 70, y: 470)].map {
24 Metaball(position: $0)
24 Metaball(position: $0)
25 }
25 }
26 metaballGraph = Graph(vertices: metaballs)
26 metaballGraph = Graph(vertices: metaballs)
27+ metaballGraph.addEdge(0, 1)
28+ metaballGraph.addEdge(0, 3)
29+ metaballGraph.addEdge(1, 2)
30+ metaballGraph.addEdge(2, 3)
31+ metaballGraph.removeEdge(2, 3)
32+ print(metaballGraph.adjacencyMatrix)
27
33
28 let border = 20
34 let border = 20
29 let metaballViewFrame = CGRect(x: border/2, y: border/2, width: width, height: height)
35 let metaballViewFrame = CGRect(x: border/2, y: border/2, width: width, height: height)
PixelImage/shaders.metal+21−15
@@ -5,40 +5,46 @@ using namespace metal;
5
5
6kernel void
6kernel void
7 drawMetaballs(texture2d<float, access::write> outTexture[[texture(0)]],
7 drawMetaballs(texture2d<float, access::write> outTexture[[texture(0)]],
8+ constant float *edgesBuffer[[buffer(1)]],
8− constant float *metaballBuffer [[buffer(0)]],
9+ constant float *metaballBuffer[[buffer(0)]],
9 uint2 gid[[thread_position_in_grid]]) {
10 uint2 gid[[thread_position_in_grid]]) {
10
11
11− char numberOfMetaballs = metaballBuffer[0];
12+ uint numberOfMetaballs = metaballBuffer[0];
12− char metaballArraySize = numberOfMetaballs * 2 + 1;
13+ uint metaballArraySize = numberOfMetaballs * 2 + 1;
13
14
14 float sum = 0;
15 float sum = 0;
15
16
16− for (char i = 1; i < metaballArraySize; i += 2) {
17+ uint i, j, x, y;
17− for (char j = i + 2; j < metaballArraySize; j += 2) {
18− float2 metaball1 = float2(metaballBuffer[i],
19− metaballBuffer[i + 1]);
20
18
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+
21− float2 metaball2 = float2(metaballBuffer[j],
23+ float2 metaball2 = float2(metaballBuffer[j], metaballBuffer[j + 1]);
22− metaballBuffer[j + 1]);
23
24
24 float2 metaball1Vector
25 float2 metaball1Vector
25 = float2(metaball1.x - gid.x, metaball1.y - gid.y);
26 = float2(metaball1.x - gid.x, metaball1.y - gid.y);
26 float2 metaball2Vector
27 float2 metaball2Vector
27 = float2(metaball2.x - gid.x, metaball2.y - gid.y);
28 = float2(metaball2.x - gid.x, metaball2.y - gid.y);
28
29
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];
29 float2 direction1 = normalize(metaball1Vector);
39 float2 direction1 = normalize(metaball1Vector);
30 float2 direction2 = normalize(metaball2Vector);
40 float2 direction2 = normalize(metaball2Vector);
31
41
32 float cosine = dot(direction1, direction2);
42 float cosine = dot(direction1, direction2);
33
43
34− float value1 = 2048 / (dot(metaball1Vector, metaball1Vector) + 1);
35− float value2 = 2048 / (dot(metaball2Vector, metaball2Vector) + 1);
36−
37− float v = value1 + value2;
38 float link = pow(((1 - cosine) * 0.5), 100);
44 float link = pow(((1 - cosine) * 0.5), 100);
39
45
40− float weightedLink = 0.6 * link;
46+ float weightedLink = 0.6 * link * edgeWeight;
41− float weightedValue = 0.5 * v;
47+
42
48
43 sum += mix(0.0, 1.0, step(0.5, weightedValue + weightedLink));
49 sum += mix(0.0, 1.0, step(0.5, weightedValue + weightedLink));
44 }
50 }