git.iko.soy/metal-metaballs
on master help sign in
30 files changed+2025−2
since splitsnapshots
unifiedsplit
.gitignore+8 .tailor.yml+14 Examples/Animation.gif PixelImage.xcodeproj/project.pbxproj+449 PixelImage.xcodeproj/project.xcworkspace/contents.xcworkspacedata+7 PixelImage.xcodeproj/xcuserdata/vini.xcuserdatad/xcschemes/PixelImage.xcscheme+91 PixelImage.xcodeproj/xcuserdata/vini.xcuserdatad/xcschemes/xcschememanagement.plist+22 PixelImage.xcworkspace/contents.xcworkspacedata+10 PixelImage.xcworkspace/xcuserdata/vini.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist+5 PixelImage/AppDelegate.swift+42 PixelImage/Assets.xcassets/AppIcon.appiconset/Contents.json+68 PixelImage/Base.lproj/LaunchScreen.storyboard+27 PixelImage/Base.lproj/Main.storyboard+26 PixelImage/Dispatch.swift+10 PixelImage/Info.plist+47 PixelImage/Interpolation.swift+67 PixelImage/Matrix.swift+72 PixelImage/Metaball.swift+67 PixelImage/MetaballDataSource.swift+14 PixelImage/Metal.swift+43 PixelImage/MetalMetaballRenderer.swift+232 PixelImage/SwiftSugar/Numeric.swift+55 PixelImage/SwiftSugar/PowerOperator.swift+21 PixelImage/UIColor.swift+15 PixelImage/UIKit.swift+219 PixelImage/ViewController.swift+226 PixelImage/shaders.metal+120 Podfile+6 Podfile.lock+23 README.md+19−2
.gitignorenew file+8
@@ -0,0 +1,8 @@
1+.DS*
2+PixelImage.xcodeproj/xcuserdata/*
3+PixelImage.xcodeproj/project.xcworkspace/xcuserdata/*
4+PixelImage.xcworkspace/xcuserdata/*
5+
6+Pods/*
7+
8+
.tailor.ymlnew file+14
@@ -0,0 +1,14 @@
1+
2+
3+# Tailor will check for violations to all rules except for the following ones
4+except:
5+ - constant-k-prefix
6+ - function-whitespace
7+ - leading-whitespace
8+
9+# include:
10+# - Source # Inspect all Swift files under "Source/"
11+exclude:
12+ - Pods # Ignore Swift files under "Pods/"
13+ - PixelImage/SwiftSugar/SwiftSequences
14+
Examples/Animation.gifnew file

Binary file not shown.

PixelImage.xcodeproj/project.pbxprojnew file+449
@@ -0,0 +1,449 @@
1+// !$*UTF8*$!
2+{
3+ archiveVersion = 1;
4+ classes = {
5+ };
6+ objectVersion = 46;
7+ objects = {
8+
9+/* Begin PBXBuildFile section */
10+ 242686881C63B72A00D62456 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 242686871C63B72A00D62456 /* AppDelegate.swift */; };
11+ 2426868A1C63B72A00D62456 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 242686891C63B72A00D62456 /* ViewController.swift */; };
12+ 2426868D1C63B72A00D62456 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 2426868B1C63B72A00D62456 /* Main.storyboard */; };
13+ 2426868F1C63B72A00D62456 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2426868E1C63B72A00D62456 /* Assets.xcassets */; };
14+ 242686921C63B72A00D62456 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 242686901C63B72A00D62456 /* LaunchScreen.storyboard */; };
15+ 2461651B1C6DA4F400A913C8 /* Interpolation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2461651A1C6DA4F400A913C8 /* Interpolation.swift */; };
16+ 246B0F831C70635D00A71D05 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 246B0F821C70635D00A71D05 /* UIColor.swift */; };
17+ 249CF84C1C6D113C008B0C78 /* Matrix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 249CF84B1C6D113C008B0C78 /* Matrix.swift */; };
18+ 24E1E7D71C67C8B200ECF1C4 /* UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E1E7D61C67C8B200ECF1C4 /* UIKit.swift */; };
19+ 24E1E7E21C67D37F00ECF1C4 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24E1E7E11C67D37F00ECF1C4 /* Metal.framework */; };
20+ 24E1E7E41C67D39300ECF1C4 /* MetalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24E1E7E31C67D39300ECF1C4 /* MetalKit.framework */; };
21+ 24E1E7EB1C67D70300ECF1C4 /* MetalMetaballRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E1E7EA1C67D70300ECF1C4 /* MetalMetaballRenderer.swift */; };
22+ 24E1E7F01C67D74300ECF1C4 /* Metal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E1E7EE1C67D74300ECF1C4 /* Metal.swift */; };
23+ 24E1E7F11C67D74300ECF1C4 /* shaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = 24E1E7EF1C67D74300ECF1C4 /* shaders.metal */; };
24+ 24ED21CD1C6C2397009C40FC /* MetaballDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24ED21CC1C6C2397009C40FC /* MetaballDataSource.swift */; };
25+ 24FC38341C7F931E0085C04A /* Metaball.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FC38331C7F931E0085C04A /* Metaball.swift */; };
26+ 24FC38361C7F9ABE0085C04A /* Dispatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FC38351C7F9ABE0085C04A /* Dispatch.swift */; };
27+ 24FFA8701C6BA038005ACD60 /* Numeric.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FFA8521C6BA038005ACD60 /* Numeric.swift */; };
28+ 24FFA8711C6BA038005ACD60 /* PowerOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FFA8531C6BA038005ACD60 /* PowerOperator.swift */; };
29+ 400CECBA73C835B415805169 /* Pods_PixelImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93D10DBF530542A19EEF699A /* Pods_PixelImage.framework */; };
30+/* End PBXBuildFile section */
31+
32+/* Begin PBXFileReference section */
33+ 0C9CE70F7D2C2896912B7B14 /* Pods-PixelImage.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PixelImage.release.xcconfig"; path = "Pods/Target Support Files/Pods-PixelImage/Pods-PixelImage.release.xcconfig"; sourceTree = "<group>"; };
34+ 242686841C63B72A00D62456 /* PixelImage.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = PixelImage.app; sourceTree = BUILT_PRODUCTS_DIR; };
35+ 242686871C63B72A00D62456 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
36+ 242686891C63B72A00D62456 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
37+ 2426868C1C63B72A00D62456 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
38+ 2426868E1C63B72A00D62456 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
39+ 242686911C63B72A00D62456 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
40+ 242686931C63B72A00D62456 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
41+ 2461651A1C6DA4F400A913C8 /* Interpolation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Interpolation.swift; path = PixelImage/Interpolation.swift; sourceTree = SOURCE_ROOT; };
42+ 246B0F821C70635D00A71D05 /* UIColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = "<group>"; };
43+ 249CF84B1C6D113C008B0C78 /* Matrix.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Matrix.swift; sourceTree = "<group>"; };
44+ 24E1E7D61C67C8B200ECF1C4 /* UIKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKit.swift; sourceTree = "<group>"; };
45+ 24E1E7E11C67D37F00ECF1C4 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; };
46+ 24E1E7E31C67D39300ECF1C4 /* MetalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MetalKit.framework; path = System/Library/Frameworks/MetalKit.framework; sourceTree = SDKROOT; };
47+ 24E1E7EA1C67D70300ECF1C4 /* MetalMetaballRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetalMetaballRenderer.swift; sourceTree = "<group>"; };
48+ 24E1E7EE1C67D74300ECF1C4 /* Metal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Metal.swift; sourceTree = "<group>"; };
49+ 24E1E7EF1C67D74300ECF1C4 /* shaders.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = shaders.metal; sourceTree = "<group>"; };
50+ 24ED21CC1C6C2397009C40FC /* MetaballDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetaballDataSource.swift; sourceTree = "<group>"; };
51+ 24FC38331C7F931E0085C04A /* Metaball.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Metaball.swift; sourceTree = "<group>"; };
52+ 24FC38351C7F9ABE0085C04A /* Dispatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dispatch.swift; sourceTree = "<group>"; };
53+ 24FFA8521C6BA038005ACD60 /* Numeric.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Numeric.swift; path = SwiftSugar/Numeric.swift; sourceTree = "<group>"; };
54+ 24FFA8531C6BA038005ACD60 /* PowerOperator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PowerOperator.swift; path = SwiftSugar/PowerOperator.swift; sourceTree = "<group>"; };
55+ 6659D12A5984961F335BDF69 /* Pods-PixelImage.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PixelImage.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PixelImage/Pods-PixelImage.debug.xcconfig"; sourceTree = "<group>"; };
56+ 93D10DBF530542A19EEF699A /* Pods_PixelImage.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PixelImage.framework; sourceTree = BUILT_PRODUCTS_DIR; };
57+/* End PBXFileReference section */
58+
59+/* Begin PBXFrameworksBuildPhase section */
60+ 242686811C63B72A00D62456 /* Frameworks */ = {
61+ isa = PBXFrameworksBuildPhase;
62+ buildActionMask = 2147483647;
63+ files = (
64+ 24E1E7E41C67D39300ECF1C4 /* MetalKit.framework in Frameworks */,
65+ 24E1E7E21C67D37F00ECF1C4 /* Metal.framework in Frameworks */,
66+ 400CECBA73C835B415805169 /* Pods_PixelImage.framework in Frameworks */,
67+ );
68+ runOnlyForDeploymentPostprocessing = 0;
69+ };
70+/* End PBXFrameworksBuildPhase section */
71+
72+/* Begin PBXGroup section */
73+ 0C205AE1A1A55E89EFAEE593 /* Pods */ = {
74+ isa = PBXGroup;
75+ children = (
76+ 6659D12A5984961F335BDF69 /* Pods-PixelImage.debug.xcconfig */,
77+ 0C9CE70F7D2C2896912B7B14 /* Pods-PixelImage.release.xcconfig */,
78+ );
79+ name = Pods;
80+ sourceTree = "<group>";
81+ };
82+ 2426867B1C63B72A00D62456 = {
83+ isa = PBXGroup;
84+ children = (
85+ 24E1E7E31C67D39300ECF1C4 /* MetalKit.framework */,
86+ 24E1E7E11C67D37F00ECF1C4 /* Metal.framework */,
87+ 242686861C63B72A00D62456 /* PixelImage */,
88+ 242686851C63B72A00D62456 /* Products */,
89+ 0C205AE1A1A55E89EFAEE593 /* Pods */,
90+ 8C02F866C2C0C9A954BB9375 /* Frameworks */,
91+ );
92+ sourceTree = "<group>";
93+ };
94+ 242686851C63B72A00D62456 /* Products */ = {
95+ isa = PBXGroup;
96+ children = (
97+ 242686841C63B72A00D62456 /* PixelImage.app */,
98+ );
99+ name = Products;
100+ sourceTree = "<group>";
101+ };
102+ 242686861C63B72A00D62456 /* PixelImage */ = {
103+ isa = PBXGroup;
104+ children = (
105+ 2461651A1C6DA4F400A913C8 /* Interpolation.swift */,
106+ 24FFA8521C6BA038005ACD60 /* Numeric.swift */,
107+ 24FFA8531C6BA038005ACD60 /* PowerOperator.swift */,
108+ 242686871C63B72A00D62456 /* AppDelegate.swift */,
109+ 24FC38351C7F9ABE0085C04A /* Dispatch.swift */,
110+ 24E1E7D61C67C8B200ECF1C4 /* UIKit.swift */,
111+ 246B0F821C70635D00A71D05 /* UIColor.swift */,
112+ 24FC38331C7F931E0085C04A /* Metaball.swift */,
113+ 24E1E7EE1C67D74300ECF1C4 /* Metal.swift */,
114+ 249CF84B1C6D113C008B0C78 /* Matrix.swift */,
115+ 24ED21CC1C6C2397009C40FC /* MetaballDataSource.swift */,
116+ 24E1E7EA1C67D70300ECF1C4 /* MetalMetaballRenderer.swift */,
117+ 24E1E7EF1C67D74300ECF1C4 /* shaders.metal */,
118+ 242686891C63B72A00D62456 /* ViewController.swift */,
119+ 2426868B1C63B72A00D62456 /* Main.storyboard */,
120+ 2426868E1C63B72A00D62456 /* Assets.xcassets */,
121+ 242686901C63B72A00D62456 /* LaunchScreen.storyboard */,
122+ 242686931C63B72A00D62456 /* Info.plist */,
123+ );
124+ path = PixelImage;
125+ sourceTree = "<group>";
126+ };
127+ 8C02F866C2C0C9A954BB9375 /* Frameworks */ = {
128+ isa = PBXGroup;
129+ children = (
130+ 93D10DBF530542A19EEF699A /* Pods_PixelImage.framework */,
131+ );
132+ name = Frameworks;
133+ sourceTree = "<group>";
134+ };
135+/* End PBXGroup section */
136+
137+/* Begin PBXNativeTarget section */
138+ 242686831C63B72A00D62456 /* PixelImage */ = {
139+ isa = PBXNativeTarget;
140+ buildConfigurationList = 242686961C63B72A00D62456 /* Build configuration list for PBXNativeTarget "PixelImage" */;
141+ buildPhases = (
142+ 041D2472535A642E23154A31 /* [CP] Check Pods Manifest.lock */,
143+ 242686801C63B72A00D62456 /* Sources */,
144+ 242686811C63B72A00D62456 /* Frameworks */,
145+ 242686821C63B72A00D62456 /* Resources */,
146+ 3CA888DC160972BAEB74EC52 /* [CP] Embed Pods Frameworks */,
147+ );
148+ buildRules = (
149+ );
150+ dependencies = (
151+ );
152+ name = PixelImage;
153+ productName = PixelImage;
154+ productReference = 242686841C63B72A00D62456 /* PixelImage.app */;
155+ productType = "com.apple.product-type.application";
156+ };
157+/* End PBXNativeTarget section */
158+
159+/* Begin PBXProject section */
160+ 2426867C1C63B72A00D62456 /* Project object */ = {
161+ isa = PBXProject;
162+ attributes = {
163+ LastSwiftUpdateCheck = 0720;
164+ LastUpgradeCheck = 1020;
165+ ORGANIZATIONNAME = "Vinicius Vendramini";
166+ TargetAttributes = {
167+ 242686831C63B72A00D62456 = {
168+ CreatedOnToolsVersion = 7.2;
169+ DevelopmentTeam = 8J63JZYYHZ;
170+ LastSwiftMigration = 1020;
171+ };
172+ };
173+ };
174+ buildConfigurationList = 2426867F1C63B72A00D62456 /* Build configuration list for PBXProject "PixelImage" */;
175+ compatibilityVersion = "Xcode 3.2";
176+ developmentRegion = English;
177+ hasScannedForEncodings = 0;
178+ knownRegions = (
179+ English,
180+ en,
181+ Base,
182+ );
183+ mainGroup = 2426867B1C63B72A00D62456;
184+ productRefGroup = 242686851C63B72A00D62456 /* Products */;
185+ projectDirPath = "";
186+ projectRoot = "";
187+ targets = (
188+ 242686831C63B72A00D62456 /* PixelImage */,
189+ );
190+ };
191+/* End PBXProject section */
192+
193+/* Begin PBXResourcesBuildPhase section */
194+ 242686821C63B72A00D62456 /* Resources */ = {
195+ isa = PBXResourcesBuildPhase;
196+ buildActionMask = 2147483647;
197+ files = (
198+ 242686921C63B72A00D62456 /* LaunchScreen.storyboard in Resources */,
199+ 2426868F1C63B72A00D62456 /* Assets.xcassets in Resources */,
200+ 2426868D1C63B72A00D62456 /* Main.storyboard in Resources */,
201+ );
202+ runOnlyForDeploymentPostprocessing = 0;
203+ };
204+/* End PBXResourcesBuildPhase section */
205+
206+/* Begin PBXShellScriptBuildPhase section */
207+ 041D2472535A642E23154A31 /* [CP] Check Pods Manifest.lock */ = {
208+ isa = PBXShellScriptBuildPhase;
209+ buildActionMask = 2147483647;
210+ files = (
211+ );
212+ inputPaths = (
213+ "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
214+ "${PODS_ROOT}/Manifest.lock",
215+ );
216+ name = "[CP] Check Pods Manifest.lock";
217+ outputPaths = (
218+ "$(DERIVED_FILE_DIR)/Pods-PixelImage-checkManifestLockResult.txt",
219+ );
220+ runOnlyForDeploymentPostprocessing = 0;
221+ shellPath = /bin/sh;
222+ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
223+ showEnvVarsInLog = 0;
224+ };
225+ 3CA888DC160972BAEB74EC52 /* [CP] Embed Pods Frameworks */ = {
226+ isa = PBXShellScriptBuildPhase;
227+ buildActionMask = 2147483647;
228+ files = (
229+ );
230+ inputPaths = (
231+ "${SRCROOT}/Pods/Target Support Files/Pods-PixelImage/Pods-PixelImage-frameworks.sh",
232+ "${BUILT_PRODUCTS_DIR}/ChameleonFramework/ChameleonFramework.framework",
233+ );
234+ name = "[CP] Embed Pods Frameworks";
235+ outputPaths = (
236+ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ChameleonFramework.framework",
237+ );
238+ runOnlyForDeploymentPostprocessing = 0;
239+ shellPath = /bin/sh;
240+ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PixelImage/Pods-PixelImage-frameworks.sh\"\n";
241+ showEnvVarsInLog = 0;
242+ };
243+/* End PBXShellScriptBuildPhase section */
244+
245+/* Begin PBXSourcesBuildPhase section */
246+ 242686801C63B72A00D62456 /* Sources */ = {
247+ isa = PBXSourcesBuildPhase;
248+ buildActionMask = 2147483647;
249+ files = (
250+ 2426868A1C63B72A00D62456 /* ViewController.swift in Sources */,
251+ 2461651B1C6DA4F400A913C8 /* Interpolation.swift in Sources */,
252+ 24FC38341C7F931E0085C04A /* Metaball.swift in Sources */,
253+ 24ED21CD1C6C2397009C40FC /* MetaballDataSource.swift in Sources */,
254+ 24FC38361C7F9ABE0085C04A /* Dispatch.swift in Sources */,
255+ 24FFA8711C6BA038005ACD60 /* PowerOperator.swift in Sources */,
256+ 24E1E7D71C67C8B200ECF1C4 /* UIKit.swift in Sources */,
257+ 249CF84C1C6D113C008B0C78 /* Matrix.swift in Sources */,
258+ 24E1E7F11C67D74300ECF1C4 /* shaders.metal in Sources */,
259+ 24E1E7EB1C67D70300ECF1C4 /* MetalMetaballRenderer.swift in Sources */,
260+ 246B0F831C70635D00A71D05 /* UIColor.swift in Sources */,
261+ 242686881C63B72A00D62456 /* AppDelegate.swift in Sources */,
262+ 24E1E7F01C67D74300ECF1C4 /* Metal.swift in Sources */,
263+ 24FFA8701C6BA038005ACD60 /* Numeric.swift in Sources */,
264+ );
265+ runOnlyForDeploymentPostprocessing = 0;
266+ };
267+/* End PBXSourcesBuildPhase section */
268+
269+/* Begin PBXVariantGroup section */
270+ 2426868B1C63B72A00D62456 /* Main.storyboard */ = {
271+ isa = PBXVariantGroup;
272+ children = (
273+ 2426868C1C63B72A00D62456 /* Base */,
274+ );
275+ name = Main.storyboard;
276+ sourceTree = "<group>";
277+ };
278+ 242686901C63B72A00D62456 /* LaunchScreen.storyboard */ = {
279+ isa = PBXVariantGroup;
280+ children = (
281+ 242686911C63B72A00D62456 /* Base */,
282+ );
283+ name = LaunchScreen.storyboard;
284+ sourceTree = "<group>";
285+ };
286+/* End PBXVariantGroup section */
287+
288+/* Begin XCBuildConfiguration section */
289+ 242686941C63B72A00D62456 /* Debug */ = {
290+ isa = XCBuildConfiguration;
291+ buildSettings = {
292+ ALWAYS_SEARCH_USER_PATHS = NO;
293+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
294+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
295+ CLANG_CXX_LIBRARY = "libc++";
296+ CLANG_ENABLE_MODULES = YES;
297+ CLANG_ENABLE_OBJC_ARC = YES;
298+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
299+ CLANG_WARN_BOOL_CONVERSION = YES;
300+ CLANG_WARN_COMMA = YES;
301+ CLANG_WARN_CONSTANT_CONVERSION = YES;
302+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
303+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
304+ CLANG_WARN_EMPTY_BODY = YES;
305+ CLANG_WARN_ENUM_CONVERSION = YES;
306+ CLANG_WARN_INFINITE_RECURSION = YES;
307+ CLANG_WARN_INT_CONVERSION = YES;
308+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
309+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
310+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
311+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
312+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
313+ CLANG_WARN_STRICT_PROTOTYPES = YES;
314+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
315+ CLANG_WARN_UNREACHABLE_CODE = YES;
316+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
317+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
318+ COPY_PHASE_STRIP = NO;
319+ DEBUG_INFORMATION_FORMAT = dwarf;
320+ ENABLE_STRICT_OBJC_MSGSEND = YES;
321+ ENABLE_TESTABILITY = YES;
322+ GCC_C_LANGUAGE_STANDARD = gnu99;
323+ GCC_DYNAMIC_NO_PIC = NO;
324+ GCC_NO_COMMON_BLOCKS = YES;
325+ GCC_OPTIMIZATION_LEVEL = 0;
326+ GCC_PREPROCESSOR_DEFINITIONS = (
327+ "DEBUG=1",
328+ "$(inherited)",
329+ );
330+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
331+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
332+ GCC_WARN_UNDECLARED_SELECTOR = YES;
333+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
334+ GCC_WARN_UNUSED_FUNCTION = YES;
335+ GCC_WARN_UNUSED_VARIABLE = YES;
336+ IPHONEOS_DEPLOYMENT_TARGET = 9.2;
337+ MTL_ENABLE_DEBUG_INFO = YES;
338+ ONLY_ACTIVE_ARCH = YES;
339+ SDKROOT = iphoneos;
340+ SWIFT_OPTIMIZATION_LEVEL = "-Onone";
341+ SWIFT_SWIFT3_OBJC_INFERENCE = Off;
342+ TARGETED_DEVICE_FAMILY = "1,2";
343+ };
344+ name = Debug;
345+ };
346+ 242686951C63B72A00D62456 /* Release */ = {
347+ isa = XCBuildConfiguration;
348+ buildSettings = {
349+ ALWAYS_SEARCH_USER_PATHS = NO;
350+ CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
351+ CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
352+ CLANG_CXX_LIBRARY = "libc++";
353+ CLANG_ENABLE_MODULES = YES;
354+ CLANG_ENABLE_OBJC_ARC = YES;
355+ CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
356+ CLANG_WARN_BOOL_CONVERSION = YES;
357+ CLANG_WARN_COMMA = YES;
358+ CLANG_WARN_CONSTANT_CONVERSION = YES;
359+ CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
360+ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
361+ CLANG_WARN_EMPTY_BODY = YES;
362+ CLANG_WARN_ENUM_CONVERSION = YES;
363+ CLANG_WARN_INFINITE_RECURSION = YES;
364+ CLANG_WARN_INT_CONVERSION = YES;
365+ CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
366+ CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
367+ CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
368+ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
369+ CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
370+ CLANG_WARN_STRICT_PROTOTYPES = YES;
371+ CLANG_WARN_SUSPICIOUS_MOVE = YES;
372+ CLANG_WARN_UNREACHABLE_CODE = YES;
373+ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
374+ "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
375+ COPY_PHASE_STRIP = NO;
376+ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
377+ ENABLE_NS_ASSERTIONS = NO;
378+ ENABLE_STRICT_OBJC_MSGSEND = YES;
379+ GCC_C_LANGUAGE_STANDARD = gnu99;
380+ GCC_NO_COMMON_BLOCKS = YES;
381+ GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
382+ GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
383+ GCC_WARN_UNDECLARED_SELECTOR = YES;
384+ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
385+ GCC_WARN_UNUSED_FUNCTION = YES;
386+ GCC_WARN_UNUSED_VARIABLE = YES;
387+ IPHONEOS_DEPLOYMENT_TARGET = 9.2;
388+ MTL_ENABLE_DEBUG_INFO = NO;
389+ SDKROOT = iphoneos;
390+ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
391+ SWIFT_SWIFT3_OBJC_INFERENCE = Off;
392+ TARGETED_DEVICE_FAMILY = "1,2";
393+ VALIDATE_PRODUCT = YES;
394+ };
395+ name = Release;
396+ };
397+ 242686971C63B72A00D62456 /* Debug */ = {
398+ isa = XCBuildConfiguration;
399+ baseConfigurationReference = 6659D12A5984961F335BDF69 /* Pods-PixelImage.debug.xcconfig */;
400+ buildSettings = {
401+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
402+ DEVELOPMENT_TEAM = 8J63JZYYHZ;
403+ INFOPLIST_FILE = PixelImage/Info.plist;
404+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
405+ PRODUCT_BUNDLE_IDENTIFIER = vinivendra.PixelImage;
406+ PRODUCT_NAME = "$(TARGET_NAME)";
407+ SWIFT_VERSION = 5.0;
408+ };
409+ name = Debug;
410+ };
411+ 242686981C63B72A00D62456 /* Release */ = {
412+ isa = XCBuildConfiguration;
413+ baseConfigurationReference = 0C9CE70F7D2C2896912B7B14 /* Pods-PixelImage.release.xcconfig */;
414+ buildSettings = {
415+ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
416+ DEVELOPMENT_TEAM = 8J63JZYYHZ;
417+ INFOPLIST_FILE = PixelImage/Info.plist;
418+ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
419+ PRODUCT_BUNDLE_IDENTIFIER = vinivendra.PixelImage;
420+ PRODUCT_NAME = "$(TARGET_NAME)";
421+ SWIFT_VERSION = 5.0;
422+ };
423+ name = Release;
424+ };
425+/* End XCBuildConfiguration section */
426+
427+/* Begin XCConfigurationList section */
428+ 2426867F1C63B72A00D62456 /* Build configuration list for PBXProject "PixelImage" */ = {
429+ isa = XCConfigurationList;
430+ buildConfigurations = (
431+ 242686941C63B72A00D62456 /* Debug */,
432+ 242686951C63B72A00D62456 /* Release */,
433+ );
434+ defaultConfigurationIsVisible = 0;
435+ defaultConfigurationName = Release;
436+ };
437+ 242686961C63B72A00D62456 /* Build configuration list for PBXNativeTarget "PixelImage" */ = {
438+ isa = XCConfigurationList;
439+ buildConfigurations = (
440+ 242686971C63B72A00D62456 /* Debug */,
441+ 242686981C63B72A00D62456 /* Release */,
442+ );
443+ defaultConfigurationIsVisible = 0;
444+ defaultConfigurationName = Release;
445+ };
446+/* End XCConfigurationList section */
447+ };
448+ rootObject = 2426867C1C63B72A00D62456 /* Project object */;
449+}
PixelImage.xcodeproj/project.xcworkspace/contents.xcworkspacedatanew file+7
@@ -0,0 +1,7 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<Workspace
3+ version = "1.0">
4+ <FileRef
5+ location = "self:PixelImage.xcodeproj">
6+ </FileRef>
7+</Workspace>
PixelImage.xcodeproj/xcuserdata/vini.xcuserdatad/xcschemes/PixelImage.xcschemenew file+91
@@ -0,0 +1,91 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<Scheme
3+ LastUpgradeVersion = "0830"
4+ version = "1.3">
5+ <BuildAction
6+ parallelizeBuildables = "YES"
7+ buildImplicitDependencies = "YES">
8+ <BuildActionEntries>
9+ <BuildActionEntry
10+ buildForTesting = "YES"
11+ buildForRunning = "YES"
12+ buildForProfiling = "YES"
13+ buildForArchiving = "YES"
14+ buildForAnalyzing = "YES">
15+ <BuildableReference
16+ BuildableIdentifier = "primary"
17+ BlueprintIdentifier = "242686831C63B72A00D62456"
18+ BuildableName = "PixelImage.app"
19+ BlueprintName = "PixelImage"
20+ ReferencedContainer = "container:PixelImage.xcodeproj">
21+ </BuildableReference>
22+ </BuildActionEntry>
23+ </BuildActionEntries>
24+ </BuildAction>
25+ <TestAction
26+ buildConfiguration = "Debug"
27+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
28+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29+ shouldUseLaunchSchemeArgsEnv = "YES">
30+ <Testables>
31+ </Testables>
32+ <MacroExpansion>
33+ <BuildableReference
34+ BuildableIdentifier = "primary"
35+ BlueprintIdentifier = "242686831C63B72A00D62456"
36+ BuildableName = "PixelImage.app"
37+ BlueprintName = "PixelImage"
38+ ReferencedContainer = "container:PixelImage.xcodeproj">
39+ </BuildableReference>
40+ </MacroExpansion>
41+ <AdditionalOptions>
42+ </AdditionalOptions>
43+ </TestAction>
44+ <LaunchAction
45+ buildConfiguration = "Debug"
46+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
47+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
48+ launchStyle = "0"
49+ useCustomWorkingDirectory = "NO"
50+ ignoresPersistentStateOnLaunch = "NO"
51+ debugDocumentVersioning = "YES"
52+ debugServiceExtension = "internal"
53+ allowLocationSimulation = "YES">
54+ <BuildableProductRunnable
55+ runnableDebuggingMode = "0">
56+ <BuildableReference
57+ BuildableIdentifier = "primary"
58+ BlueprintIdentifier = "242686831C63B72A00D62456"
59+ BuildableName = "PixelImage.app"
60+ BlueprintName = "PixelImage"
61+ ReferencedContainer = "container:PixelImage.xcodeproj">
62+ </BuildableReference>
63+ </BuildableProductRunnable>
64+ <AdditionalOptions>
65+ </AdditionalOptions>
66+ </LaunchAction>
67+ <ProfileAction
68+ buildConfiguration = "Release"
69+ shouldUseLaunchSchemeArgsEnv = "YES"
70+ savedToolIdentifier = ""
71+ useCustomWorkingDirectory = "NO"
72+ debugDocumentVersioning = "YES">
73+ <BuildableProductRunnable
74+ runnableDebuggingMode = "0">
75+ <BuildableReference
76+ BuildableIdentifier = "primary"
77+ BlueprintIdentifier = "242686831C63B72A00D62456"
78+ BuildableName = "PixelImage.app"
79+ BlueprintName = "PixelImage"
80+ ReferencedContainer = "container:PixelImage.xcodeproj">
81+ </BuildableReference>
82+ </BuildableProductRunnable>
83+ </ProfileAction>
84+ <AnalyzeAction
85+ buildConfiguration = "Debug">
86+ </AnalyzeAction>
87+ <ArchiveAction
88+ buildConfiguration = "Release"
89+ revealArchiveInOrganizer = "YES">
90+ </ArchiveAction>
91+</Scheme>
PixelImage.xcodeproj/xcuserdata/vini.xcuserdatad/xcschemes/xcschememanagement.plistnew file+22
@@ -0,0 +1,22 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+<plist version="1.0">
4+<dict>
5+ <key>SchemeUserState</key>
6+ <dict>
7+ <key>PixelImage.xcscheme</key>
8+ <dict>
9+ <key>orderHint</key>
10+ <integer>0</integer>
11+ </dict>
12+ </dict>
13+ <key>SuppressBuildableAutocreation</key>
14+ <dict>
15+ <key>242686831C63B72A00D62456</key>
16+ <dict>
17+ <key>primary</key>
18+ <true/>
19+ </dict>
20+ </dict>
21+</dict>
22+</plist>
PixelImage.xcworkspace/contents.xcworkspacedatanew file+10
@@ -0,0 +1,10 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<Workspace
3+ version = "1.0">
4+ <FileRef
5+ location = "group:PixelImage.xcodeproj">
6+ </FileRef>
7+ <FileRef
8+ location = "group:Pods/Pods.xcodeproj">
9+ </FileRef>
10+</Workspace>
PixelImage.xcworkspace/xcuserdata/vini.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlistnew file+5
@@ -0,0 +1,5 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<Bucket
3+ type = "0"
4+ version = "2.0">
5+</Bucket>
PixelImage/AppDelegate.swiftnew file+42
@@ -0,0 +1,42 @@
1+//
2+// AppDelegate.swift
3+// PixelImage
4+//
5+// Created by Vinicius Vendramini on 2/4/16.
6+// Copyright © 2016 Vinicius Vendramini. All rights reserved.
7+//
8+
9+import UIKit
10+
11+@UIApplicationMain
12+class AppDelegate: UIResponder, UIApplicationDelegate {
13+
14+ var window: UIWindow?
15+
16+ func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17+ // Override point for customization after application launch.
18+ return true
19+ }
20+
21+ func applicationWillResignActive(_ application: UIApplication) {
22+ // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
23+ // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
24+ }
25+
26+ func applicationDidEnterBackground(_ application: UIApplication) {
27+ // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
28+ // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
29+ }
30+
31+ func applicationWillEnterForeground(_ application: UIApplication) {
32+ // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
33+ }
34+
35+ func applicationDidBecomeActive(_ application: UIApplication) {
36+ // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
37+ }
38+
39+ func applicationWillTerminate(_ application: UIApplication) {
40+ // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
41+ }
42+}
PixelImage/Assets.xcassets/AppIcon.appiconset/Contents.jsonnew file+68
@@ -0,0 +1,68 @@
1+{
2+ "images" : [
3+ {
4+ "idiom" : "iphone",
5+ "size" : "29x29",
6+ "scale" : "2x"
7+ },
8+ {
9+ "idiom" : "iphone",
10+ "size" : "29x29",
11+ "scale" : "3x"
12+ },
13+ {
14+ "idiom" : "iphone",
15+ "size" : "40x40",
16+ "scale" : "2x"
17+ },
18+ {
19+ "idiom" : "iphone",
20+ "size" : "40x40",
21+ "scale" : "3x"
22+ },
23+ {
24+ "idiom" : "iphone",
25+ "size" : "60x60",
26+ "scale" : "2x"
27+ },
28+ {
29+ "idiom" : "iphone",
30+ "size" : "60x60",
31+ "scale" : "3x"
32+ },
33+ {
34+ "idiom" : "ipad",
35+ "size" : "29x29",
36+ "scale" : "1x"
37+ },
38+ {
39+ "idiom" : "ipad",
40+ "size" : "29x29",
41+ "scale" : "2x"
42+ },
43+ {
44+ "idiom" : "ipad",
45+ "size" : "40x40",
46+ "scale" : "1x"
47+ },
48+ {
49+ "idiom" : "ipad",
50+ "size" : "40x40",
51+ "scale" : "2x"
52+ },
53+ {
54+ "idiom" : "ipad",
55+ "size" : "76x76",
56+ "scale" : "1x"
57+ },
58+ {
59+ "idiom" : "ipad",
60+ "size" : "76x76",
61+ "scale" : "2x"
62+ }
63+ ],
64+ "info" : {
65+ "version" : 1,
66+ "author" : "xcode"
67+ }
68+}
PixelImage/Base.lproj/LaunchScreen.storyboardnew file+27
@@ -0,0 +1,27 @@
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8150" systemVersion="15A204g" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
3+ <dependencies>
4+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8122"/>
5+ </dependencies>
6+ <scenes>
7+ <!--View Controller-->
8+ <scene sceneID="EHf-IW-A2E">
9+ <objects>
10+ <viewController id="01J-lp-oVM" sceneMemberID="viewController">
11+ <layoutGuides>
12+ <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
13+ <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
14+ </layoutGuides>
15+ <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
16+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
17+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
18+ <animations/>
19+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
20+ </view>
21+ </viewController>
22+ <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
23+ </objects>
24+ <point key="canvasLocation" x="53" y="375"/>
25+ </scene>
26+ </scenes>
27+</document>
PixelImage/Base.lproj/Main.storyboardnew file+26
@@ -0,0 +1,26 @@
1+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
3+ <dependencies>
4+ <deployment identifier="iOS"/>
5+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9530"/>
6+ </dependencies>
7+ <scenes>
8+ <!--View Controller-->
9+ <scene sceneID="tne-QT-ifu">
10+ <objects>
11+ <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="PixelImage" customModuleProvider="target" sceneMemberID="viewController">
12+ <layoutGuides>
13+ <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
14+ <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
15+ </layoutGuides>
16+ <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
17+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
18+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
19+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
20+ </view>
21+ </viewController>
22+ <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
23+ </objects>
24+ </scene>
25+ </scenes>
26+</document>
PixelImage/Dispatch.swiftnew file+10
@@ -0,0 +1,10 @@
1+
2+import Dispatch
3+
4+func delay(_ duration: Float = 1, block: @escaping () -> ()) {
5+ DispatchQueue.main.asyncAfter(
6+ deadline: DispatchTime.now() +
7+ Double(duration * Float(NSEC_PER_SEC)) /
8+ Double(NSEC_PER_SEC),
9+ execute: block)
10+}
PixelImage/Info.plistnew file+47
@@ -0,0 +1,47 @@
1+<?xml version="1.0" encoding="UTF-8"?>
2+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+<plist version="1.0">
4+<dict>
5+ <key>CFBundleDevelopmentRegion</key>
6+ <string>en</string>
7+ <key>CFBundleExecutable</key>
8+ <string>$(EXECUTABLE_NAME)</string>
9+ <key>CFBundleIdentifier</key>
10+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
11+ <key>CFBundleInfoDictionaryVersion</key>
12+ <string>6.0</string>
13+ <key>CFBundleName</key>
14+ <string>$(PRODUCT_NAME)</string>
15+ <key>CFBundlePackageType</key>
16+ <string>APPL</string>
17+ <key>CFBundleShortVersionString</key>
18+ <string>1.0</string>
19+ <key>CFBundleSignature</key>
20+ <string>????</string>
21+ <key>CFBundleVersion</key>
22+ <string>1</string>
23+ <key>LSRequiresIPhoneOS</key>
24+ <true/>
25+ <key>UILaunchStoryboardName</key>
26+ <string>LaunchScreen</string>
27+ <key>UIMainStoryboardFile</key>
28+ <string>Main</string>
29+ <key>UIRequiredDeviceCapabilities</key>
30+ <array>
31+ <string>armv7</string>
32+ </array>
33+ <key>UISupportedInterfaceOrientations</key>
34+ <array>
35+ <string>UIInterfaceOrientationPortrait</string>
36+ <string>UIInterfaceOrientationLandscapeLeft</string>
37+ <string>UIInterfaceOrientationLandscapeRight</string>
38+ </array>
39+ <key>UISupportedInterfaceOrientations~ipad</key>
40+ <array>
41+ <string>UIInterfaceOrientationPortrait</string>
42+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
43+ <string>UIInterfaceOrientationLandscapeLeft</string>
44+ <string>UIInterfaceOrientationLandscapeRight</string>
45+ </array>
46+</dict>
47+</plist>
PixelImage/Interpolation.swiftnew file+67
@@ -0,0 +1,67 @@
1+
2+import Darwin
3+
4+func interpolateCubeEaseIn<T: Numeric>(_ linear: T) -> T {
5+ return linear ^ 3
6+}
7+
8+func interpolateSquareEaseIn<T: Numeric>(_ linear: T) -> T {
9+ return linear ^ 2
10+}
11+
12+func interpolateSquareEaseOut<T: Numeric>(_ linear: T) -> T {
13+ return 1 - (1 - linear) * (1 - linear)
14+}
15+
16+func interpolateSmooth<T: Numeric>(_ linear: T) -> T {
17+ let cube = (linear) * (linear) * (linear)
18+ return (cube * ((linear) * ((linear) * 6 - 15) + 10))
19+}
20+
21+func createStringKeyframes(_ bounces: Int = 2, elasticity: Double = 1.1, bounceSpeed: Double) -> [(time: Double, position: Double)] {
22+ // Setup keyframes
23+ var keyFrames: [(time: Double, position: Double)] = [(1, 1)]
24+
25+ let overreach = elasticity - 1
26+
27+ var currentBounceTime: Double = overreach / (bounceSpeed ^ Double(bounces))
28+ var bounceDirection = Double((bounces % 2) * 2 - 1)
29+ var bounceReach = overreach / (2 ^ Double(bounces - 1))
30+ var currentBouncePosition: Double = 1.0 + bounceDirection * bounceReach
31+
32+ keyFrames.append((1.0 - currentBounceTime, currentBouncePosition))
33+
34+ for i in (1..<bounces).reversed() {
35+ currentBounceTime += overreach / (bounceSpeed ^ Double(i + 1))
36+ currentBounceTime += overreach / (bounceSpeed ^ Double(i))
37+ bounceDirection = Double((i % 2) * 2 - 1)
38+ bounceReach = overreach / (2 ^ Double(i - 1))
39+ currentBouncePosition = 1.0 + bounceDirection * bounceReach
40+
41+ keyFrames.append((1.0 - currentBounceTime, currentBouncePosition))
42+ }
43+
44+ keyFrames.append((0, 0))
45+
46+ var reversed = [(time: Double, position: Double)]()
47+ for keyFrame in keyFrames.reversed() {
48+ reversed.append(keyFrame)
49+ }
50+
51+ return reversed
52+}
53+
54+func interpolateSpring<T: Numeric>(_ linear: T, bounces: Int = 2, elasticity: Double = 1.1, bounceSpeed: Double = 1.0) -> T {
55+ let keyFrames: [(time: Double, position: Double)]
56+ keyFrames = createStringKeyframes(bounces, elasticity: elasticity, bounceSpeed: bounceSpeed)
57+
58+ let x = linear.toDouble
59+
60+ let startFrame = keyFrames.filter { $0.time < x }.last ?? (0, 0)
61+ let endFrame = keyFrames.filter { $0.time > x }.first ?? (1, 1)
62+ let adjustedX = (x - startFrame.time) / (endFrame.time - startFrame.time)
63+ let interpolatedX = interpolateSmooth(adjustedX)
64+ let position = startFrame.position + interpolatedX * (endFrame.position - startFrame.position)
65+
66+ return T.fromDouble(position)
67+}
PixelImage/Matrix.swiftnew file+72
@@ -0,0 +1,72 @@
1+import UIKit
2+
3+class Graph<T> {
4+ var adjacencyMatrix: Matrix
5+ var vertices: [T]
6+
7+ var size: Int {
8+ get {
9+ return vertices.count
10+ }
11+ }
12+
13+ init(vertices: [T]) {
14+ let size = vertices.count
15+
16+ self.vertices = vertices
17+ self.adjacencyMatrix = Matrix(size: size)
18+ }
19+
20+ func addEdge(_ i: Int, _ j: Int) {
21+ adjacencyMatrix.set(i, j)
22+ adjacencyMatrix.set(j, i)
23+ }
24+
25+ func removeEdge(_ i: Int, _ j: Int) {
26+ adjacencyMatrix.reset(i, j)
27+ adjacencyMatrix.reset(j, i)
28+ }
29+
30+}
31+
32+struct Matrix: CustomStringConvertible {
33+ var buffer: [Float]
34+
35+ let size: Int
36+
37+ init(size: Int) {
38+ buffer = [Float](repeating: 0.0, count: size * size)
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+ fileprivate 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.enumerated() {
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+}
PixelImage/Metaball.swiftnew file+67
@@ -0,0 +1,67 @@
1+
2+import UIKit
3+
4+class Metaball: UIView {
5+
6+ var color: UIColor
7+
8+ init(position: CGPoint,
9+ color: UIColor = UIColor(randomFlatColorOf: .light)) {
10+ self.color = color
11+ super.init(frame: CGRect.zero)
12+
13+ size = CGSize(50)
14+ middle = position
15+ }
16+
17+ required init?(coder aDecoder: NSCoder) {
18+ self.color = UIColor(randomFlatColorOf: .light)
19+ super.init(coder: aDecoder)
20+ }
21+}
22+
23+class EdgeAnimationParameters {
24+ let startDate: Date
25+ let duration: Float
26+ let fadeIn: Bool
27+ let i: Int
28+ let j: Int
29+
30+ func unpack() -> (startDate: Date, duration: Float, fadeIn: Bool,
31+ i: Int, j: Int)
32+ {
33+ return (startDate, duration, fadeIn, i, j)
34+ }
35+
36+ init(startDate: Date, duration: Float, fadeIn: Bool, i: Int, j: Int) {
37+ self.startDate = startDate
38+ self.duration = duration
39+ self.fadeIn = fadeIn
40+ self.i = i
41+ self.j = j
42+ }
43+}
44+
45+class VertexAnimationParameters {
46+ let startDate: Date
47+ let duration: Float
48+ let origin: CGPoint
49+ let destination: CGPoint
50+ let metaball: Metaball
51+
52+ func unpack() -> (startDate: Date, duration: Float, origin: CGPoint,
53+ destination: CGPoint, metaball: Metaball)
54+ {
55+ return (startDate, duration, origin, destination, metaball)
56+ }
57+
58+ init(startDate: Date, duration: Float, origin: CGPoint,
59+ destination: CGPoint, metaball: Metaball)
60+ {
61+ self.startDate = startDate
62+ self.duration = duration
63+ self.origin = origin
64+ self.destination = destination
65+ self.metaball = metaball
66+ }
67+}
PixelImage/MetaballDataSource.swiftnew file+14
@@ -0,0 +1,14 @@
1+import Metal
2+
3+protocol MetaballDataSource {
4+ var metaballGraph: Graph<Metaball>! { get }
5+}
6+
7+extension MetaballDataSource {
8+ var metaballs: [Metaball]! {
9+ get {
10+ return metaballGraph.vertices
11+ }
12+ }
13+
14+}
PixelImage/Metal.swiftnew file+43
@@ -0,0 +1,43 @@
1+import Metal
2+import UIKit
3+
4+class MTLContext {
5+ let device: MTLDevice
6+ let library: MTLLibrary
7+ let commandQueue: MTLCommandQueue
8+
9+ init(device: MTLDevice = MTLCreateSystemDefaultDevice()!) {
10+ self.device = device
11+ self.library = device.makeDefaultLibrary()!
12+ self.commandQueue = device.makeCommandQueue()!
13+ }
14+}
15+
16+class MTLComputeContext {
17+ var texture: MTLTexture
18+ var imageBuffer: UnsafeMutableRawPointer
19+
20+ init(size: CGSize, texture: MTLTexture) {
21+ let width = Int(size.width)
22+ let height = Int(size.height)
23+ let imageByteCount = width * height * 4
24+ imageBuffer = malloc(imageByteCount)
25+
26+ self.texture = texture
27+ }
28+
29+ func updateToSize(_ size: CGSize, texture: MTLTexture) {
30+ free(imageBuffer)
31+
32+ let width = Int(size.width)
33+ let height = Int(size.height)
34+ let imageByteCount = width * height * 4
35+ imageBuffer = malloc(imageByteCount)
36+
37+ self.texture = texture
38+ }
39+
40+ deinit {
41+ free(imageBuffer)
42+ }
43+}
PixelImage/MetalMetaballRenderer.swiftnew file+232
@@ -0,0 +1,232 @@
1+import Metal
2+import UIKit
3+import ChameleonFramework
4+
5+@objc class MetalMetaballRenderer: NSObject {
6+
7+ typealias TargetView = UIImageView
8+
9+ let targetView: TargetView
10+
11+ let context = MTLContext()
12+ var activeComputeContext: MTLComputeContext
13+ var idleComputeContext: MTLComputeContext
14+
15+ let dataSource: MetaballDataSource
16+
17+ let semaphore = DispatchSemaphore(value: 2)
18+
19+ enum RendererState {
20+ case idle
21+ case running
22+ case ending
23+ }
24+
25+ var state: RendererState = .idle {
26+ willSet {
27+ if state == .idle {
28+ if newValue == .running {
29+ updateTargetView()
30+ }
31+ }
32+ }
33+ }
34+
35+ required init(dataSource: MetaballDataSource, frame: CGRect) {
36+ self.dataSource = dataSource
37+
38+ targetView = TargetView(frame: frame)
39+
40+ let width = Int(frame.width)
41+ let height = Int(frame.height)
42+ let textureDescriptor =
43+ MTLTextureDescriptor.texture2DDescriptor(
44+ pixelFormat: .bgra8Unorm,
45+ width: width,
46+ height: height,
47+ mipmapped: false)
48+ textureDescriptor.usage = .shaderWrite
49+
50+ let texture1 =
51+ context.device.makeTexture(descriptor: textureDescriptor)
52+ activeComputeContext = MTLComputeContext(size: targetView.size,
53+ texture: texture1!)
54+ let texture2 =
55+ context.device.makeTexture(descriptor: textureDescriptor)
56+ idleComputeContext = MTLComputeContext(size: targetView.size,
57+ texture: texture2!)
58+ }
59+
60+ internal func updateTargetView() {
61+ let timeout = DispatchTime.now() + Double(1000000000) / Double(NSEC_PER_SEC)
62+ _ = semaphore.wait(timeout: timeout)
63+
64+ state = .ending
65+
66+ DispatchQueue.main.async { () -> Void in
67+ let computeContext = self.activeComputeContext
68+ swap(&self.activeComputeContext, &self.idleComputeContext)
69+
70+ // Render graphics to metal texture
71+ self.renderToContext(computeContext)
72+
73+ if self.state != .running {
74+ self.state = .idle
75+ } else {
76+ self.updateTargetView()
77+ }
78+
79+ // Transform metal texture into image
80+ let uiimage = self.createImageFromContext(computeContext)
81+
82+ self.semaphore.signal()
83+
84+ DispatchQueue.main.async { () -> Void in
85+ // Send image to view
86+ self.targetView.image = uiimage
87+ }
88+ }
89+ }
90+
91+ internal func renderToContext(_ computeContext: MTLComputeContext) {
92+ let width = Int(targetView.width)
93+ let height = Int(targetView.height)
94+
95+ let kernelFunction: MTLFunction?
96+ let pipeline: MTLComputePipelineState
97+ do {
98+ // Get long-lived objects
99+ kernelFunction =
100+ context.library.makeFunction(name: "drawMetaballs")
101+ pipeline = try context.device.makeComputePipelineState(
102+ function: kernelFunction!)
103+
104+ // Configure info for computing
105+ let threadGroupCounts = MTLSizeMake(8, 8, 1)
106+ let threadGroups = MTLSizeMake(width / threadGroupCounts.width,
107+ height / threadGroupCounts.height,
108+ 1)
109+
110+ // Send commands to metal and render
111+ let commandBuffer = context.commandQueue.makeCommandBuffer()
112+
113+ let commandEncoder = commandBuffer!.makeComputeCommandEncoder()
114+ commandEncoder!.setComputePipelineState(pipeline)
115+ commandEncoder!.setTexture(computeContext.texture, index: 0)
116+ let metaballInfoBuffer = metaballBuffer()
117+ commandEncoder!.setBuffer(metaballInfoBuffer, offset: 0, index: 0)
118+ let edgesBuffer = metaballEdgesBuffer()
119+ commandEncoder!.setBuffer(edgesBuffer, offset: 0, index: 1)
120+ commandEncoder!.dispatchThreadgroups(threadGroups,
121+ threadsPerThreadgroup: threadGroupCounts)
122+ commandEncoder!.endEncoding()
123+
124+ commandBuffer!.commit()
125+ commandBuffer!.waitUntilCompleted()
126+ } catch _ {
127+ assertionFailure()
128+ }
129+ }
130+
131+ internal func createImageFromContext(_ computeContext: MTLComputeContext)
132+ -> UIImage
133+ {
134+ let texture = computeContext.texture
135+
136+ // Get image info
137+ let imageSize = CGSize(width: CGFloat(texture.width),
138+ height: CGFloat(texture.height))
139+ let width = Int(imageSize.width)
140+ let height = Int(imageSize.height)
141+ let imageByteCount = width * height * 4
142+ let bytesPerRow = width * 4
143+ let region = MTLRegionMake2D(0, 0, width, height)
144+
145+ // Transfer texture info into image buffer
146+ texture.getBytes(computeContext.imageBuffer,
147+ bytesPerRow: bytesPerRow,
148+ from: region,
149+ mipmapLevel: 0)
150+
151+ // Get info for UIImage
152+ let provider = CGDataProvider(dataInfo: nil,
153+ data: computeContext.imageBuffer,
154+ size: imageByteCount) {
155+ (_, _, _) in }
156+ let bitsPerComponent = 8
157+ let bitsperPixel = 32
158+ let colorSpaceRef = CGColorSpaceCreateDeviceRGB()
159+ let bitmapInfoRaw = CGImageAlphaInfo.premultipliedLast.rawValue |
160+ CGBitmapInfo.byteOrder32Big.rawValue
161+ let bitmapInfo = CGBitmapInfo(rawValue: bitmapInfoRaw)
162+ let renderingIntent = CGColorRenderingIntent.defaultIntent
163+
164+ // Create UIImage from image buffer
165+ let imageRef = CGImage(width: width, height: height,
166+ bitsPerComponent: bitsPerComponent,
167+ bitsPerPixel: bitsperPixel,
168+ bytesPerRow: bytesPerRow,
169+ space: colorSpaceRef,
170+ bitmapInfo: bitmapInfo,
171+ provider: provider!,
172+ decode: nil, shouldInterpolate: false,
173+ intent: renderingIntent)
174+ let image = UIImage(cgImage: imageRef!, scale: 0.0, orientation: .up)
175+
176+ return image
177+ }
178+
179+ internal func metaballEdgesBuffer() -> MTLBuffer {
180+ let floats = dataSource.metaballGraph.adjacencyMatrix.buffer
181+
182+ let bufferLength = floats.count * MemoryLayout<Float>.size
183+ let device = context.device
184+ let buffer = device.makeBuffer(bytes: floats,
185+ length: bufferLength,
186+ options: MTLResourceOptions())
187+
188+ return buffer!
189+ }
190+
191+ internal func metaballBuffer() -> MTLBuffer {
192+ // Create Float array for buffer
193+ // Exclude metaballs far from the view's bounds
194+ let border: CGFloat = 100
195+ let metaballs = self.dataSource.metaballs!
196+
197+ var floats = [Float](repeating: 0, count: 5)
198+ floats = metaballs.reduce(floats) {
199+ (array, metaball) -> [Float] in
200+ var array = array
201+
202+ let x = CGFloat(metaball.midX)
203+ let y = CGFloat(metaball.midY)
204+ if -border < x,
205+ x < targetView.width + border,
206+ -border < y,
207+ y < targetView.height + border {
208+ array.append(Float(x))
209+ array.append(Float(y))
210+
211+ let color = metaball.color
212+ let components = color.components
213+ array.append(Float(components.red))
214+ array.append(Float(components.green))
215+ array.append(Float(components.blue))
216+ }
217+ return array
218+ }
219+
220+ // Add array size to start of array so metal knows how far to go
221+ floats[0] = Float(floats.count) / 5
222+
223+ // Create buffer
224+ let bufferLength = floats.count * MemoryLayout<Float>.size
225+ let device = context.device
226+ let buffer = device.makeBuffer(bytes: floats,
227+ length: bufferLength,
228+ options: MTLResourceOptions())
229+
230+ return buffer!
231+ }
232+}
PixelImage/SwiftSugar/Numeric.swiftnew file+55
@@ -0,0 +1,55 @@
1+protocol DoubleValuable {
2+ var toDouble: Double { get }
3+
4+ static func fromDouble(_ double: Double) -> Self
5+}
6+
7+protocol Numeric: DoubleValuable, Comparable,
8+ ExpressibleByIntegerLiteral {
9+
10+ static func / (lhs: Self, rhs: Self) -> Self
11+ static func - (lhs: Self, rhs: Self) -> Self
12+ static func + (lhs: Self, rhs: Self) -> Self
13+ static func * (lhs: Self, rhs: Self) -> Self
14+}
15+
16+protocol FastNumeric: Numeric {}
17+
18+extension Double: FastNumeric {
19+ var toDouble: Double {
20+ get {
21+ return self
22+ }
23+ }
24+
25+ static func fromDouble(_ double: Double) -> Double {
26+ return double
27+ }
28+
29+}
30+
31+extension Float: FastNumeric {
32+ var toDouble: Double {
33+ get {
34+ return Double(self)
35+ }
36+ }
37+
38+ static func fromDouble(_ double: Double) -> Float {
39+ return Float(double)
40+ }
41+
42+}
43+
44+extension Int: FastNumeric {
45+ var toDouble: Double {
46+ get {
47+ return Double(self)
48+ }
49+ }
50+
51+ static func fromDouble(_ double: Double) -> Int {
52+ return Int(double)
53+ }
54+
55+}
PixelImage/SwiftSugar/PowerOperator.swiftnew file+21
@@ -0,0 +1,21 @@
1+import Darwin
2+
3+precedencegroup PowerPrecedence {
4+ higherThan: MultiplicationPrecedence
5+ associativity: right
6+}
7+
8+//
9+infix operator ^: PowerPrecedence
10+
11+func ^ <T: DoubleValuable>(left: T, right: T) -> T {
12+ return T.fromDouble(pow(left.toDouble, right.toDouble))
13+}
14+
15+func ^ (left: Double, right: Double) -> Double {
16+ return pow(left, right)
17+}
18+
19+func ^ (left: Float, right: Float) -> Float {
20+ return powf(left, right)
21+}
PixelImage/UIColor.swiftnew file+15
@@ -0,0 +1,15 @@
1+import UIKit
2+
3+extension UIColor {
4+ var components: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
5+ var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
6+ getRed(&r, green: &g, blue: &b, alpha: &a)
7+ return (r, g, b, a)
8+ }
9+}
10+
11+extension UIColor {
12+ convenience init(red255 red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
13+ self.init(red: red / 255, green: green / 255, blue: blue / 255, alpha: alpha / 255)
14+ }
15+}
PixelImage/UIKit.swiftnew file+219
@@ -0,0 +1,219 @@
1+// TODO: Unite UIView and CALayer extensions under a common protocol
2+
3+import UIKit
4+
5+func - (left: CGPoint, right: CGPoint) -> CGVector {
6+ return CGVector(dx: left.x - right.x, dy: left.y - right.y)
7+}
8+
9+func * (left: CGVector, right: CGVector) -> CGFloat {
10+ return (left.dx * right.dx) + (left.dy * right.dy)
11+}
12+
13+func / (vector: CGVector, scalar: CGFloat) -> CGVector {
14+ return CGVector(dx: vector.dx / scalar, dy: vector.dy / scalar)
15+}
16+
17+extension CGVector {
18+ var norm: CGFloat {
19+ get {
20+ let dot = self * self
21+ return sqrt(dot)
22+ }
23+ }
24+
25+ var normalized: CGVector {
26+ get {
27+ return self / norm
28+ }
29+ }
30+
31+ mutating func normalize() {
32+ let norm = self.norm
33+ dx = dx / norm
34+ dy = dy / norm
35+ }
36+
37+}
38+
39+extension CGFloat: DoubleValuable {
40+ var toDouble: Double { get {
41+ return Double(self)
42+ }
43+ }
44+
45+ static func fromDouble(_ double: Double) -> CGFloat {
46+ return CGFloat(double)
47+ }
48+
49+}
50+
51+extension CGFloat: Numeric {}
52+
53+extension CGRect {
54+ var x: CGFloat {
55+ get {
56+ return origin.x
57+ }
58+ set {
59+ origin.x = newValue
60+ }
61+ }
62+
63+ var y: CGFloat {
64+ get {
65+ return origin.y
66+ }
67+ set {
68+ origin.y = newValue
69+ }
70+ }
71+}
72+
73+extension CGSize {
74+ init(_ squareSize: CGFloat) {
75+ self.init(width: squareSize, height: squareSize)
76+ }
77+}
78+
79+extension UIView {
80+ var origin: CGPoint {
81+ get {
82+ return frame.origin
83+ }
84+ set {
85+ frame.origin = newValue
86+ }
87+ }
88+
89+ var size: CGSize {
90+ get {
91+ return frame.size
92+ }
93+ set {
94+ frame.size = newValue
95+ }
96+ }
97+
98+ var width: CGFloat {
99+ get {
100+ return frame.width
101+ }
102+ set {
103+ frame.size = CGSize(width: newValue, height: height)
104+ }
105+ }
106+
107+ var height: CGFloat {
108+ get {
109+ return frame.height
110+ }
111+ set {
112+ frame.size = CGSize(width: width, height: newValue)
113+ }
114+ }
115+
116+ var middle: CGPoint {
117+ get {
118+ return CGPoint(x: origin.x + width / 2, y: origin.y + height / 2)
119+ }
120+ set {
121+ origin = CGPoint(x: newValue.x - width / 2, y: newValue.y - height / 2)
122+ }
123+ }
124+
125+ var midX: CGFloat {
126+ get {
127+ return middle.x
128+ }
129+ set {
130+ middle = CGPoint(x: newValue, y: midY)
131+ }
132+ }
133+
134+ var midY: CGFloat {
135+ get {
136+ return middle.y
137+ }
138+ set {
139+ middle = CGPoint(x: midX, y: newValue)
140+ }
141+ }
142+}
143+
144+extension CALayer {
145+ var origin: CGPoint {
146+ get {
147+ return frame.origin
148+ }
149+ set {
150+ frame.origin = newValue
151+ }
152+ }
153+
154+ var size: CGSize {
155+ get {
156+ return frame.size
157+ }
158+ set {
159+ frame.size = newValue
160+ }
161+ }
162+
163+ var width: CGFloat {
164+ get {
165+ return frame.width
166+ }
167+ set {
168+ frame.size = CGSize(width: newValue, height: height)
169+ }
170+ }
171+
172+ var height: CGFloat {
173+ get {
174+ return frame.height
175+ }
176+ set {
177+ frame.size = CGSize(width: width, height: newValue)
178+ }
179+ }
180+
181+ var middle: CGPoint {
182+ get {
183+ return CGPoint(x: origin.x + width / 2, y: origin.y + height / 2)
184+ }
185+ set {
186+ origin = CGPoint(x: newValue.x - width / 2, y: newValue.y - height / 2)
187+ }
188+ }
189+
190+ var midX: CGFloat {
191+ get {
192+ return middle.x
193+ }
194+ set {
195+ middle = CGPoint(x: newValue, y: midY)
196+ }
197+ }
198+
199+ var midY: CGFloat {
200+ get {
201+ return middle.y
202+ }
203+ set {
204+ middle = CGPoint(x: midX, y: newValue)
205+ }
206+ }
207+}
208+
209+let screenRect = UIScreen.main.bounds
210+let screenWidth = screenRect.size.width
211+let screenHeight = screenRect.size.height
212+
213+func * <T: Numeric>(vector: CGVector, scalar: T) -> CGVector {
214+ return CGVector(dx: vector.dx * CGFloat(scalar.toDouble), dy: vector.dy * CGFloat(scalar.toDouble))
215+}
216+
217+func + (point: CGPoint, vector: CGVector) -> CGPoint {
218+ return CGPoint(x: point.x + vector.dx, y: point.y + vector.dy)
219+}
PixelImage/ViewController.swiftnew file+226
@@ -0,0 +1,226 @@
1+import UIKit
2+
3+class ViewController: UIViewController, MetaballDataSource {
4+
5+ var metaballView: UIImageView!
6+ var renderer: MetalMetaballRenderer!
7+
8+ let width = 350
9+ let height = 600
10+
11+ var metaballGraph: Graph<Metaball>!
12+ var previousLocation: CGPoint!
13+
14+ var selectedMetaball: Metaball?
15+
16+ let edgeAnimationDuration: Float = 1
17+
18+ override func viewDidLoad() {
19+ super.viewDidLoad()
20+
21+ view.backgroundColor = UIColor.white
22+
23+ let recognizer = UIPanGestureRecognizer(
24+ target: self,
25+ action: #selector(ViewController.handlePan(_:)))
26+ view.addGestureRecognizer(recognizer)
27+
28+ var positions = [CGPoint]()
29+ positions.append(CGPoint(
30+ x: Double(screenWidth) / 2,
31+ y: Double(screenHeight) / 2))
32+ for i in 1...5 {
33+ let sine = sin(Double(i) * 2 * .pi / 5)
34+ let cosine = cos(Double(i) * 2 * .pi / 5)
35+ let radius: Double = 150
36+ let x = Double(screenWidth) / 2 + sine * radius
37+ let y = Double(screenHeight) / 2 + cosine * radius
38+ positions.append(CGPoint(x: x, y: y))
39+ }
40+
41+ let colors =
42+ [UIColor(red255: 110, green: 220, blue: 220, alpha: 1.0),
43+ UIColor(red255: 250, green: 235, blue: 50, alpha: 1.0),
44+ UIColor(red255: 110, green: 220, blue: 220, alpha: 1.0),
45+ UIColor(red255: 90, green: 170, blue: 170, alpha: 1.0),
46+ UIColor(red255: 90, green: 170, blue: 170, alpha: 1.0),
47+ UIColor(red255: 110, green: 220, blue: 220, alpha: 1.0)]
48+ let metaballs = zip(positions, colors).map {
49+ Metaball(position: $0.0, color: $0.1)
50+ }
51+
52+ metaballGraph = Graph(vertices: metaballs)
53+
54+ let metaballViewFrame = screenRect
55+ renderer = MetalMetaballRenderer(dataSource: self,
56+ frame: metaballViewFrame)
57+ metaballView = renderer.targetView
58+ view.addSubview(metaballView)
59+
60+ for metaball in metaballs {
61+ metaballView.addSubview(metaball)
62+ }
63+
64+ delay(1.0) {
65+ self.addEdge(0, 1)
66+ self.addEdge(0, 2)
67+ self.addEdge(0, 3)
68+ self.addEdge(0, 4)
69+ self.addEdge(0, 5)
70+ }
71+
72+ delay(2.3) {
73+ self.removeEdge(0, 1)
74+ self.removeEdge(0, 2)
75+ self.removeEdge(0, 3)
76+ self.removeEdge(0, 4)
77+ self.removeEdge(0, 5)
78+ }
79+
80+ delay(4) {
81+ for i in 1...5 {
82+ UIView.animate(withDuration: 1.0, delay: 0,
83+ options: UIView.AnimationOptions(),
84+ animations: { () -> Void in
85+ let sine = sin(Double(i) * 2 * .pi / 5)
86+ let cosine = cos(Double(i) * 2 * .pi / 5)
87+ let radius: Double = 85
88+ let x = Double(screenWidth) / 2 + sine * radius
89+ let y = Double(screenHeight) / 2 + cosine * radius
90+ let metaball = metaballs[i]
91+ self.animateMetaball(metaball, toPoint: CGPoint(x: x, y: y))
92+ }, completion: nil)
93+ }
94+ }
95+
96+ delay(6) { () -> () in
97+ for i in 1...5 {
98+ UIView.animate(withDuration: 1.0, delay: 0,
99+ usingSpringWithDamping: 0.5,
100+ initialSpringVelocity: 0,
101+ options: UIView.AnimationOptions(),
102+ animations: { () -> Void in
103+ let sine = sin(Double(i) * 2 * .pi / 5)
104+ let cosine = cos(Double(i) * 2 * .pi / 5)
105+ let radius: Double = 150
106+ let x = Double(screenWidth) / 2 + sine * radius
107+ let y = Double(screenHeight) / 2 + cosine * radius
108+ let metaball = metaballs[i]
109+ self.animateMetaball(metaball, toPoint: CGPoint(x: x, y: y))
110+ }, completion: nil)
111+ }
112+ }
113+
114+ renderer.state = .running
115+ }
116+
117+ func addEdge(_ i: Int, _ j: Int) {
118+ animateEdge(i, j, fadeIn: true)
119+ }
120+
121+ func removeEdge(_ i: Int, _ j: Int) {
122+ animateEdge(i, j, fadeIn: false)
123+ }
124+
125+ func animateMetaball(_ metaball: Metaball, toPoint destination: CGPoint) {
126+ let parameters = VertexAnimationParameters(startDate: Date(),
127+ duration: 1.0,
128+ origin: metaball.center,
129+ destination: destination,
130+ metaball: metaball)
131+ Timer.scheduledTimer(
132+ timeInterval: 1.0 / 60.0,
133+ target: self,
134+ selector: #selector(ViewController.animateMetaball(withTimer:)),
135+ userInfo: parameters,
136+ repeats: true)
137+ }
138+
139+ @objc func animateMetaball(withTimer timer: Timer) {
140+ guard let userInfo = timer.userInfo as? VertexAnimationParameters else {
141+ preconditionFailure()
142+ }
143+ let (animationStart, duration, origin, destination, metaball) =
144+ (userInfo).unpack()
145+
146+ let now = Date()
147+ var timeElapsed = Float(now.timeIntervalSince(animationStart))
148+
149+ if timeElapsed > duration {
150+ timer.invalidate()
151+ timeElapsed = duration
152+ }
153+
154+ let linearValue = timeElapsed / duration
155+ let interpolatedValue = interpolateSmooth(linearValue)
156+
157+ let position = origin + ((destination - origin) * interpolatedValue)
158+ metaball.center = position
159+
160+ renderer.state = .running
161+ }
162+
163+ func animateEdge(_ i: Int, _ j: Int, fadeIn: Bool) {
164+ let parameters = EdgeAnimationParameters(startDate: Date(),
165+ duration: edgeAnimationDuration,
166+ fadeIn: fadeIn,
167+ i: i,
168+ j: j)
169+ Timer.scheduledTimer(
170+ timeInterval: 1.0 / 60.0,
171+ target: self,
172+ selector: #selector(ViewController.animateEdge(withTimer:)),
173+ userInfo: parameters,
174+ repeats: true)
175+ }
176+
177+ @objc func animateEdge(withTimer timer: Timer) {
178+ guard let userInfo = timer.userInfo as? EdgeAnimationParameters else {
179+ preconditionFailure()
180+ }
181+ let (animationStart, duration, fadeIn, i, j) = (userInfo).unpack()
182+
183+ let now = Date()
184+ var timeElapsed = Float(now.timeIntervalSince(animationStart))
185+
186+ if timeElapsed > duration {
187+ timer.invalidate()
188+ timeElapsed = duration
189+ }
190+
191+ let linearValue = timeElapsed / duration
192+ let interpolatedValue = fadeIn ?
193+ interpolateSquareEaseOut(linearValue) :
194+ (1 - interpolateSquareEaseIn(linearValue))
195+
196+ metaballGraph.adjacencyMatrix.set(i, j, value: interpolatedValue)
197+ metaballGraph.adjacencyMatrix.set(j, i, value: interpolatedValue)
198+
199+ renderer.state = .running
200+ }
201+
202+ @objc func handlePan(_ recognizer: UIPanGestureRecognizer) {
203+ let location = recognizer.location(in: metaballView)
204+
205+ if selectedMetaball == nil {
206+ for metaball in metaballs where
207+ abs(metaball.midX - location.x) < 50 &&
208+ abs(metaball.midY - location.y) < 50
209+ {
210+ selectedMetaball = metaball
211+ break
212+ }
213+ }
214+
215+ guard selectedMetaball != nil else { return }
216+
217+ selectedMetaball?.middle = location
218+
219+ renderer.state = .running
220+
221+ if recognizer.state == .ended {
222+ selectedMetaball = nil
223+ }
224+ }
225+
226+}
PixelImage/shaders.metalnew file+120
@@ -0,0 +1,120 @@
1+
2+#include <metal_stdlib>
3+
4+using namespace metal;
5+
6+struct metaballData {
7+ float x;
8+ float y;
9+ float r;
10+ float g;
11+ float b;
12+};
13+
14+kernel void
15+ drawMetaballs(texture2d<float, access::write> outTexture[[texture(0)]],
16+ constant float *edgesBuffer[[buffer(1)]],
17+ constant metaballData *metaballBuffer[[buffer(0)]],
18+ uint2 gid[[thread_position_in_grid]]) {
19+
20+ char numberOfMetaballs = metaballBuffer[0].x - 1;
21+
22+ float sum = 0;
23+ float3 colorSum = float3(0, 0, 0);
24+ float colorAccumulation = 0;
25+ float3 colorSumLink = float3(0, 0, 0);
26+
27+ char x, y;
28+
29+ float metaballDistances[10];
30+ float2 metaballDirections[10];
31+ float3 metaballColors[10];
32+
33+ for (x = 1; x <= numberOfMetaballs; x += 1) {
34+ metaballData metaball = metaballBuffer[x];
35+ float2 metaballPosition = float2(metaball.x, metaball.y);
36+ float2 vector = float2(metaballPosition.x - gid.x,
37+ metaballPosition.y - gid.y);
38+ float dotProduct = dot(vector, vector);
39+ float squaredDistance = dotProduct > 0 ? dotProduct : 1;
40+ float realDistance = sqrt(squaredDistance);
41+ metaballDistances[x - 1] = squaredDistance;
42+ metaballDirections[x - 1] = vector / realDistance;
43+ metaballColors[x - 1] = float3(metaball.r, metaball.g, metaball.b);
44+ }
45+
46+ float bendClose = 0.0;
47+ float bendCloseCount = 0.0;
48+ float ball = 0.0;
49+
50+ for (x = 0; x < numberOfMetaballs; x += 1) {
51+ float distance1 = metaballDistances[x];
52+ float value1 = 1500 / distance1;
53+ float2 direction1 = metaballDirections[x];
54+
55+ float colorContribution = 1 / distance1;
56+ colorAccumulation += colorContribution;
57+ colorSum += metaballColors[x] * colorContribution;
58+
59+ for (y = x + 1; y < numberOfMetaballs; y += 1) {
60+ float distance2 = metaballDistances[y];
61+ float value2 = 1500 / distance2;
62+ float2 direction2 = metaballDirections[y];
63+
64+ float v = value1 + value2;
65+ float weightedValue = 0.5 * v;
66+
67+ char edgeIndex = y * numberOfMetaballs + x;
68+ float edgeWeight = edgesBuffer[edgeIndex];
69+ float cosine = dot(direction1, direction2);
70+ float link = pow(((1 - cosine) * 0.5), 100);
71+ float weightedLink = 0.6 * link * edgeWeight;
72+
73+ float result = weightedValue + weightedLink;
74+
75+ ball += step(0.4, weightedValue);
76+
77+ float metaballValue = step(0.5, weightedValue + weightedLink);
78+
79+ if (metaballValue > 0.0) {
80+ colorSumLink = float3(0.0, 0.0, 0.0);
81+ colorSumLink += metaballColors[x] / distance1;
82+ colorSumLink += metaballColors[y] / distance2;
83+ colorSumLink /= (1 / distance1) + (1 / distance2);
84+ }
85+
86+ if (result > 0.4) {
87+ bendClose += result;
88+ bendCloseCount += 1.0;
89+ }
90+
91+ sum += metaballValue;
92+ }
93+ }
94+
95+ // bendClose = bendClose / bendCloseCount;
96+
97+ if (bendClose != 0.0) {
98+ bendClose = (bendClose - 0.4) * (1 / (0.6 - 0.4));
99+ } else {
100+ bendClose = 0.0;
101+ }
102+
103+
104+ float result = step(0.4, sum);
105+ result = clamp(result, 0.0, 1.0);
106+
107+ colorSum = colorSum / colorAccumulation;
108+
109+ if (result > 0.0 && ball < 1.0) {
110+ colorSum = colorSumLink;
111+ }
112+
113+ colorSum *= result;
114+
115+ if (colorSum.r == 0 && colorSum.g == 0 && colorSum.b == 0) {
116+ colorSum = float3(1, 1, 1);
117+ }
118+
119+ outTexture.write(float4(colorSum.bgr, 1), gid);
120+}
Podfilenew file+6
@@ -0,0 +1,6 @@
1+platform :ios, '9.2'
2+use_frameworks!
3+
4+target 'PixelImage' do
5+ pod 'ChameleonFramework/Swift', :git => 'https://github.com/ViccAlexander/Chameleon.git'
6+end
Podfile.locknew file+23
@@ -0,0 +1,23 @@
1+PODS:
2+ - ChameleonFramework/Default (2.1.0)
3+ - ChameleonFramework/Swift (2.1.0):
4+ - ChameleonFramework/Default
5+
6+DEPENDENCIES:
7+ - ChameleonFramework/Swift (from `https://github.com/ViccAlexander/Chameleon.git`)
8+
9+EXTERNAL SOURCES:
10+ ChameleonFramework:
11+ :git: https://github.com/ViccAlexander/Chameleon.git
12+
13+CHECKOUT OPTIONS:
14+ ChameleonFramework:
15+ :commit: 6dd284bde21ea2e7f9fd89bc36f40df16e16369d
16+ :git: https://github.com/ViccAlexander/Chameleon.git
17+
18+SPEC CHECKSUMS:
19+ ChameleonFramework: d21a3cc247abfe5e37609a283a8238b03575cf64
20+
21+PODFILE CHECKSUM: 01ad8f20bdbed983c319dde08426f855f26bc5a0
22+
23+COCOAPODS: 1.5.2
README.md+19−2
@@ -1,2 +1,19 @@
1−# metal-metaballs
2−A 'playground' project for rendering metaball-like graphics in metal.
1+![Example Animation](https://raw.githubusercontent.com/vinivendra/metal-metaballs/master/Examples/Animation.gif)
2+
3+# Metal Metaballs
4+
5+Metal Metaballs is a framework for rendering graphs in an aesthetically pleasing way. It uses an adapted version of [metaball](https://en.wikipedia.org/wiki/Metaballs) math to create fluid graphics, allow vertices to merge with each other and allow edges to flow and animate smoothly.
6+
7+Vertices and edges may be added and removed at will and can interact with gestures. Rendering a fullscreen view continuously uses only about 19% of the CPU at 60 fps, though normally the view will only be updated when needed. All rendering is done in a background thread, so the main thread remains almost completely free. These measurements were done using an iPhone 6S Plus (which has more pixels to calculate but more power to do so), so YMMV.
8+
9+This framework is being developed to make me learn several different design patters, APIs and features used in iOS and in graphics applications. So far, it uses:
10+
11+- [**Metaball**](https://en.wikipedia.org/wiki/Metaballs) math, in an adapted version, to render the graphics.
12+- [**Metal**](https://developer.apple.com/metal/) compute shaders to draw the metaballs dynamically.
13+- [**Grand Central Dispatch**](https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/) for doing CPU and GPU calculations simultaneously.
14+- [**Double buffering**](https://en.wikipedia.org/wiki/Multiple_buffering), to speed up rendering times.
15+- Different [**interpolations**](http://flexmonkey.blogspot.ca/2016/01/playing-with-interpolation-functions-in.html) for animations.
16+
17+## Installation
18+
19+Just download the app and run it on your metal-enabled device of choice :)