mirror of
https://github.com/ilyakooo0/metal-metaballs.git
synced 2024-09-11 10:35:56 +03:00
Merged release-presentation into master
This commit is contained in:
commit
feaaefcb71
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,3 +1,8 @@
|
||||
.DS*
|
||||
PixelImage.xcodeproj/xcuserdata/*
|
||||
PixelImage.xcodeproj/project.xcworkspace/xcuserdata/*
|
||||
PixelImage.xcodeproj/project.xcworkspace/xcuserdata/*
|
||||
PixelImage.xcworkspace/xcuserdata/*
|
||||
|
||||
Pods/*
|
||||
|
||||
|
||||
|
14
.tailor.yml
Normal file
14
.tailor.yml
Normal file
@ -0,0 +1,14 @@
|
||||
|
||||
|
||||
# Tailor will check for violations to all rules except for the following ones
|
||||
except:
|
||||
- constant-k-prefix
|
||||
- function-whitespace
|
||||
- leading-whitespace
|
||||
|
||||
# include:
|
||||
# - Source # Inspect all Swift files under "Source/"
|
||||
exclude:
|
||||
- Pods # Ignore Swift files under "Pods/"
|
||||
- PixelImage/SwiftSugar/SwiftSequences
|
||||
|
BIN
Examples/Animation.gif
Normal file
BIN
Examples/Animation.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 440 KiB |
@ -13,6 +13,7 @@
|
||||
2426868F1C63B72A00D62456 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 2426868E1C63B72A00D62456 /* Assets.xcassets */; };
|
||||
242686921C63B72A00D62456 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 242686901C63B72A00D62456 /* LaunchScreen.storyboard */; };
|
||||
2461651B1C6DA4F400A913C8 /* Interpolation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2461651A1C6DA4F400A913C8 /* Interpolation.swift */; };
|
||||
246B0F831C70635D00A71D05 /* UIColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 246B0F821C70635D00A71D05 /* UIColor.swift */; };
|
||||
249CF84C1C6D113C008B0C78 /* Matrix.swift in Sources */ = {isa = PBXBuildFile; fileRef = 249CF84B1C6D113C008B0C78 /* Matrix.swift */; };
|
||||
24E1E7D71C67C8B200ECF1C4 /* UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E1E7D61C67C8B200ECF1C4 /* UIKit.swift */; };
|
||||
24E1E7E21C67D37F00ECF1C4 /* Metal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 24E1E7E11C67D37F00ECF1C4 /* Metal.framework */; };
|
||||
@ -21,6 +22,8 @@
|
||||
24E1E7F01C67D74300ECF1C4 /* Metal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24E1E7EE1C67D74300ECF1C4 /* Metal.swift */; };
|
||||
24E1E7F11C67D74300ECF1C4 /* shaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = 24E1E7EF1C67D74300ECF1C4 /* shaders.metal */; };
|
||||
24ED21CD1C6C2397009C40FC /* MetaballDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24ED21CC1C6C2397009C40FC /* MetaballDataSource.swift */; };
|
||||
24FC38341C7F931E0085C04A /* Metaball.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FC38331C7F931E0085C04A /* Metaball.swift */; };
|
||||
24FC38361C7F9ABE0085C04A /* Dispatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FC38351C7F9ABE0085C04A /* Dispatch.swift */; };
|
||||
24FFA86B1C6BA038005ACD60 /* ApproximationOperator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FFA84C1C6BA038005ACD60 /* ApproximationOperator.swift */; };
|
||||
24FFA86C1C6BA038005ACD60 /* Collections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FFA84D1C6BA038005ACD60 /* Collections.swift */; };
|
||||
24FFA86D1C6BA038005ACD60 /* ComparatorChains.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FFA84E1C6BA038005ACD60 /* ComparatorChains.swift */; };
|
||||
@ -49,6 +52,7 @@
|
||||
24FFA8841C6BA038005ACD60 /* TakeDrop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FFA8681C6BA038005ACD60 /* TakeDrop.swift */; };
|
||||
24FFA8851C6BA038005ACD60 /* Zip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FFA8691C6BA038005ACD60 /* Zip.swift */; };
|
||||
24FFA8861C6BA038005ACD60 /* TestCommons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 24FFA86A1C6BA038005ACD60 /* TestCommons.swift */; };
|
||||
2BF7957B6518CA68241BB2CE /* Pods_PixelImage.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C55F9558B3B267413C3A07A6 /* Pods_PixelImage.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@ -60,6 +64,7 @@
|
||||
242686911C63B72A00D62456 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
242686931C63B72A00D62456 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
2461651A1C6DA4F400A913C8 /* Interpolation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Interpolation.swift; path = PixelImage/Interpolation.swift; sourceTree = SOURCE_ROOT; };
|
||||
246B0F821C70635D00A71D05 /* UIColor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIColor.swift; sourceTree = "<group>"; };
|
||||
249CF84B1C6D113C008B0C78 /* Matrix.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Matrix.swift; sourceTree = "<group>"; };
|
||||
24E1E7D61C67C8B200ECF1C4 /* UIKit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKit.swift; sourceTree = "<group>"; };
|
||||
24E1E7E11C67D37F00ECF1C4 /* Metal.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Metal.framework; path = System/Library/Frameworks/Metal.framework; sourceTree = SDKROOT; };
|
||||
@ -68,6 +73,8 @@
|
||||
24E1E7EE1C67D74300ECF1C4 /* Metal.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Metal.swift; sourceTree = "<group>"; };
|
||||
24E1E7EF1C67D74300ECF1C4 /* shaders.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = shaders.metal; sourceTree = "<group>"; };
|
||||
24ED21CC1C6C2397009C40FC /* MetaballDataSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MetaballDataSource.swift; sourceTree = "<group>"; };
|
||||
24FC38331C7F931E0085C04A /* Metaball.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Metaball.swift; sourceTree = "<group>"; };
|
||||
24FC38351C7F9ABE0085C04A /* Dispatch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dispatch.swift; sourceTree = "<group>"; };
|
||||
24FFA84C1C6BA038005ACD60 /* ApproximationOperator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApproximationOperator.swift; sourceTree = "<group>"; };
|
||||
24FFA84D1C6BA038005ACD60 /* Collections.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Collections.swift; sourceTree = "<group>"; };
|
||||
24FFA84E1C6BA038005ACD60 /* ComparatorChains.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ComparatorChains.swift; sourceTree = "<group>"; };
|
||||
@ -96,6 +103,9 @@
|
||||
24FFA8681C6BA038005ACD60 /* TakeDrop.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TakeDrop.swift; sourceTree = "<group>"; };
|
||||
24FFA8691C6BA038005ACD60 /* Zip.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Zip.swift; sourceTree = "<group>"; };
|
||||
24FFA86A1C6BA038005ACD60 /* TestCommons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestCommons.swift; sourceTree = "<group>"; };
|
||||
B1A755EFC03DA2A172C2B82F /* 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>"; };
|
||||
C419D37C90A6BE8B5139806A /* 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>"; };
|
||||
C55F9558B3B267413C3A07A6 /* Pods_PixelImage.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PixelImage.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -105,6 +115,7 @@
|
||||
files = (
|
||||
24E1E7E41C67D39300ECF1C4 /* MetalKit.framework in Frameworks */,
|
||||
24E1E7E21C67D37F00ECF1C4 /* Metal.framework in Frameworks */,
|
||||
2BF7957B6518CA68241BB2CE /* Pods_PixelImage.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -118,6 +129,8 @@
|
||||
24E1E7E11C67D37F00ECF1C4 /* Metal.framework */,
|
||||
242686861C63B72A00D62456 /* PixelImage */,
|
||||
242686851C63B72A00D62456 /* Products */,
|
||||
F1933B48ED44BA9FCD7A4A1F /* Pods */,
|
||||
588576572D7A188031904848 /* Frameworks */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
@ -134,12 +147,15 @@
|
||||
children = (
|
||||
24FFA84B1C6BA038005ACD60 /* SwiftSugar */,
|
||||
242686871C63B72A00D62456 /* AppDelegate.swift */,
|
||||
242686891C63B72A00D62456 /* ViewController.swift */,
|
||||
24FC38351C7F9ABE0085C04A /* Dispatch.swift */,
|
||||
24E1E7D61C67C8B200ECF1C4 /* UIKit.swift */,
|
||||
24ED21CC1C6C2397009C40FC /* MetaballDataSource.swift */,
|
||||
24E1E7EA1C67D70300ECF1C4 /* MetalMetaballRenderer.swift */,
|
||||
246B0F821C70635D00A71D05 /* UIColor.swift */,
|
||||
24FC38331C7F931E0085C04A /* Metaball.swift */,
|
||||
24E1E7EE1C67D74300ECF1C4 /* Metal.swift */,
|
||||
249CF84B1C6D113C008B0C78 /* Matrix.swift */,
|
||||
242686891C63B72A00D62456 /* ViewController.swift */,
|
||||
24ED21CC1C6C2397009C40FC /* MetaballDataSource.swift */,
|
||||
24E1E7EA1C67D70300ECF1C4 /* MetalMetaballRenderer.swift */,
|
||||
24E1E7EF1C67D74300ECF1C4 /* shaders.metal */,
|
||||
2426868B1C63B72A00D62456 /* Main.storyboard */,
|
||||
2426868E1C63B72A00D62456 /* Assets.xcassets */,
|
||||
@ -209,6 +225,23 @@
|
||||
path = SwiftSequences;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
588576572D7A188031904848 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
C55F9558B3B267413C3A07A6 /* Pods_PixelImage.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F1933B48ED44BA9FCD7A4A1F /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B1A755EFC03DA2A172C2B82F /* Pods-PixelImage.debug.xcconfig */,
|
||||
C419D37C90A6BE8B5139806A /* Pods-PixelImage.release.xcconfig */,
|
||||
);
|
||||
name = Pods;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@ -216,9 +249,13 @@
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 242686961C63B72A00D62456 /* Build configuration list for PBXNativeTarget "PixelImage" */;
|
||||
buildPhases = (
|
||||
EF55C7246917A184756D93D8 /* Check Pods Manifest.lock */,
|
||||
242686801C63B72A00D62456 /* Sources */,
|
||||
242686811C63B72A00D62456 /* Frameworks */,
|
||||
242686821C63B72A00D62456 /* Resources */,
|
||||
C7A2DF67265A9D17DEFBD193 /* Embed Pods Frameworks */,
|
||||
FADC01F75BFAC01F0260D1C9 /* Copy Pods Resources */,
|
||||
DD0396CE594E3EAC8D18CC6C /* Tailor */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -276,6 +313,68 @@
|
||||
};
|
||||
/* End PBXResourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXShellScriptBuildPhase section */
|
||||
C7A2DF67265A9D17DEFBD193 /* Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PixelImage/Pods-PixelImage-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
DD0396CE594E3EAC8D18CC6C /* Tailor */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = Tailor;
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "if hash tailor 2>/dev/null; then\n tailor --except=function-whitespace,leading-whitespace,constant-k-prefix --max-severity error\nelse\n echo \"warning: Please install Tailor from https://tailor.sh\"\nfi";
|
||||
};
|
||||
EF55C7246917A184756D93D8 /* Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Check Pods Manifest.lock";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [[ $? != 0 ]] ; then\n cat << EOM\nerror: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\nEOM\n exit 1\nfi\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
FADC01F75BFAC01F0260D1C9 /* Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
);
|
||||
name = "Copy Pods Resources";
|
||||
outputPaths = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-PixelImage/Pods-PixelImage-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
242686801C63B72A00D62456 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
@ -290,6 +389,7 @@
|
||||
24FFA8771C6BA038005ACD60 /* ChunkWindowSplit.swift in Sources */,
|
||||
24FFA86F1C6BA038005ACD60 /* NSNumber.swift in Sources */,
|
||||
2461651B1C6DA4F400A913C8 /* Interpolation.swift in Sources */,
|
||||
24FC38341C7F931E0085C04A /* Metaball.swift in Sources */,
|
||||
24FFA8861C6BA038005ACD60 /* TestCommons.swift in Sources */,
|
||||
24FFA8741C6BA038005ACD60 /* SignOperator.swift in Sources */,
|
||||
24FFA8821C6BA038005ACD60 /* ScanReduce.swift in Sources */,
|
||||
@ -297,6 +397,7 @@
|
||||
24FFA8831C6BA038005ACD60 /* Slicing.swift in Sources */,
|
||||
24FFA8721C6BA038005ACD60 /* Sequence.swift in Sources */,
|
||||
24FFA87F1C6BA038005ACD60 /* Iterate.swift in Sources */,
|
||||
24FC38361C7F9ABE0085C04A /* Dispatch.swift in Sources */,
|
||||
24FFA8761C6BA038005ACD60 /* Categorise.swift in Sources */,
|
||||
24FFA86D1C6BA038005ACD60 /* ComparatorChains.swift in Sources */,
|
||||
24FFA8711C6BA038005ACD60 /* PowerOperator.swift in Sources */,
|
||||
@ -308,6 +409,7 @@
|
||||
24FFA86E1C6BA038005ACD60 /* Dictionary.swift in Sources */,
|
||||
24FFA87D1C6BA038005ACD60 /* Hopping.swift in Sources */,
|
||||
24E1E7EB1C67D70300ECF1C4 /* MetalMetaballRenderer.swift in Sources */,
|
||||
246B0F831C70635D00A71D05 /* UIColor.swift in Sources */,
|
||||
24FFA87A1C6BA038005ACD60 /* Enumerate.swift in Sources */,
|
||||
242686881C63B72A00D62456 /* AppDelegate.swift in Sources */,
|
||||
24E1E7F01C67D74300ECF1C4 /* Metal.swift in Sources */,
|
||||
@ -428,6 +530,7 @@
|
||||
};
|
||||
242686971C63B72A00D62456 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = B1A755EFC03DA2A172C2B82F /* Pods-PixelImage.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
INFOPLIST_FILE = PixelImage/Info.plist;
|
||||
@ -439,6 +542,7 @@
|
||||
};
|
||||
242686981C63B72A00D62456 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = C419D37C90A6BE8B5139806A /* Pods-PixelImage.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
INFOPLIST_FILE = PixelImage/Info.plist;
|
||||
|
10
PixelImage.xcworkspace/contents.xcworkspacedata
generated
Normal file
10
PixelImage.xcworkspace/contents.xcworkspacedata
generated
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:PixelImage.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Pods/Pods.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Bucket
|
||||
type = "0"
|
||||
version = "2.0">
|
||||
</Bucket>
|
@ -13,7 +13,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
|
||||
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
|
||||
// Override point for customization after application launch.
|
||||
return true
|
||||
@ -40,7 +39,4 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
func applicationWillTerminate(application: UIApplication) {
|
||||
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
6
PixelImage/Dispatch.swift
Normal file
6
PixelImage/Dispatch.swift
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
import Dispatch
|
||||
|
||||
func delay(duration: Float = 1, block: () -> ()) {
|
||||
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(duration * Float(NSEC_PER_SEC))), dispatch_get_main_queue(), block)
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
|
||||
import Darwin
|
||||
|
||||
func interpolateCubeEaseIn<T: Numeric>(linear: T) -> T {
|
||||
return linear ^ 3
|
||||
}
|
||||
@ -15,3 +17,60 @@ func interpolateSmooth<T: Numeric>(linear: T) -> T {
|
||||
return ((linear) * (linear) * (linear) * ((linear) * ((linear) * 6 - 15) + 10))
|
||||
}
|
||||
|
||||
func createStringKeyframes(bounces: Int = 2, elasticity: Double = 1.1, bounceSpeed: Double) -> [(time: Double, position: Double)] {
|
||||
// Setup keyframes
|
||||
var keyFrames: [(time: Double, position: Double)] = [(1, 1)]
|
||||
|
||||
let overreach = elasticity - 1
|
||||
|
||||
var currentBounceTime: Double = overreach / (bounceSpeed ^ Double(bounces))
|
||||
var bounceDirection = Double((bounces % 2) * 2 - 1)
|
||||
var bounceReach = overreach / (2 ^ Double(bounces - 1))
|
||||
var currentBouncePosition: Double = 1.0 + bounceDirection * bounceReach
|
||||
|
||||
print(currentBounceTime)
|
||||
print(bounceDirection)
|
||||
print(bounceReach)
|
||||
print(currentBouncePosition)
|
||||
|
||||
keyFrames.append((1.0 - currentBounceTime, currentBouncePosition))
|
||||
|
||||
for i in (1..<bounces).reverse() {
|
||||
currentBounceTime += overreach / (bounceSpeed ^ Double(i + 1))
|
||||
currentBounceTime += overreach / (bounceSpeed ^ Double(i))
|
||||
bounceDirection = Double((i % 2) * 2 - 1)
|
||||
bounceReach = overreach / (2 ^ Double(i - 1))
|
||||
currentBouncePosition = 1.0 + bounceDirection * bounceReach
|
||||
|
||||
keyFrames.append((1.0 - currentBounceTime, currentBouncePosition))
|
||||
|
||||
print(currentBounceTime)
|
||||
print(bounceDirection)
|
||||
print(bounceReach)
|
||||
print(currentBouncePosition)
|
||||
}
|
||||
|
||||
keyFrames.append((0, 0))
|
||||
|
||||
var reversed = [(time: Double, position: Double)]()
|
||||
for keyFrame in keyFrames.reverse() {
|
||||
reversed.append(keyFrame)
|
||||
}
|
||||
|
||||
return reversed
|
||||
}
|
||||
|
||||
func interpolateSpring<T: Numeric>(linear: T, bounces: Int = 2, elasticity: Double = 1.1, bounceSpeed: Double = 1.0) -> T {
|
||||
let keyFrames: [(time: Double, position: Double)]
|
||||
keyFrames = createStringKeyframes(bounces, elasticity: elasticity, bounceSpeed: bounceSpeed)
|
||||
|
||||
let x = linear.toDouble
|
||||
|
||||
let startFrame = keyFrames.filter { $0.time < x }.last ?? (0, 0)
|
||||
let endFrame = keyFrames.filter { $0.time > x }.first ?? (1, 1)
|
||||
let adjustedX = (x - startFrame.time) / (endFrame.time - startFrame.time)
|
||||
let interpolatedX = interpolateSmooth(adjustedX)
|
||||
let position = startFrame.position + interpolatedX * (endFrame.position - startFrame.position)
|
||||
|
||||
return T.fromDouble(position)
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class Graph<T> {
|
||||
@ -27,6 +26,7 @@ class Graph<T> {
|
||||
adjacencyMatrix.reset(i, j)
|
||||
adjacencyMatrix.reset(j, i)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct Matrix: CustomStringConvertible {
|
||||
@ -70,23 +70,3 @@ struct Matrix: CustomStringConvertible {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EdgeAnimationParameters {
|
||||
let startDate: NSDate
|
||||
let duration: Float
|
||||
let fadeIn: Bool
|
||||
let i: Int
|
||||
let j: Int
|
||||
|
||||
func unpack() -> (startDate: NSDate, duration: Float, fadeIn: Bool, i: Int, j: Int) {
|
||||
return (startDate, duration, fadeIn, i, j)
|
||||
}
|
||||
|
||||
init(startDate: NSDate, duration: Float, fadeIn: Bool, i: Int, j: Int) {
|
||||
self.startDate = startDate
|
||||
self.duration = duration
|
||||
self.fadeIn = fadeIn
|
||||
self.i = i
|
||||
self.j = j
|
||||
}
|
||||
}
|
||||
|
60
PixelImage/Metaball.swift
Normal file
60
PixelImage/Metaball.swift
Normal file
@ -0,0 +1,60 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class Metaball: UIView {
|
||||
|
||||
var color: UIColor
|
||||
|
||||
init(position: CGPoint, color: UIColor = UIColor(randomFlatColorOfShadeStyle: .Light)) {
|
||||
self.color = color
|
||||
super.init(frame: CGRect.zero)
|
||||
|
||||
size = CGSize(50)
|
||||
middle = position
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
self.color = UIColor(randomFlatColorOfShadeStyle: .Light)
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
}
|
||||
|
||||
class EdgeAnimationParameters {
|
||||
let startDate: NSDate
|
||||
let duration: Float
|
||||
let fadeIn: Bool
|
||||
let i: Int
|
||||
let j: Int
|
||||
|
||||
func unpack() -> (startDate: NSDate, duration: Float, fadeIn: Bool, i: Int, j: Int) {
|
||||
return (startDate, duration, fadeIn, i, j)
|
||||
}
|
||||
|
||||
init(startDate: NSDate, duration: Float, fadeIn: Bool, i: Int, j: Int) {
|
||||
self.startDate = startDate
|
||||
self.duration = duration
|
||||
self.fadeIn = fadeIn
|
||||
self.i = i
|
||||
self.j = j
|
||||
}
|
||||
}
|
||||
|
||||
class VertexAnimationParameters {
|
||||
let startDate: NSDate
|
||||
let duration: Float
|
||||
let origin: CGPoint
|
||||
let destination: CGPoint
|
||||
let metaball: Metaball
|
||||
|
||||
func unpack() -> (startDate: NSDate, duration: Float, origin: CGPoint, destination: CGPoint, metaball: Metaball) {
|
||||
return (startDate, duration, origin, destination, metaball)
|
||||
}
|
||||
|
||||
init(startDate: NSDate, duration: Float, origin: CGPoint, destination: CGPoint, metaball: Metaball) {
|
||||
self.startDate = startDate
|
||||
self.duration = duration
|
||||
self.origin = origin
|
||||
self.destination = destination
|
||||
self.metaball = metaball
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
|
||||
import Metal
|
||||
|
||||
protocol MetaballDataSource {
|
||||
@ -11,4 +10,5 @@ extension MetaballDataSource {
|
||||
return metaballGraph.vertices
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -40,4 +40,4 @@ class MTLComputeContext {
|
||||
deinit {
|
||||
free(imageBuffer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import Metal
|
||||
import UIKit
|
||||
import ChameleonFramework
|
||||
|
||||
class MetalMetaballRenderer {
|
||||
@objc class MetalMetaballRenderer: NSObject {
|
||||
|
||||
typealias TargetView = UIImageView
|
||||
|
||||
@ -38,8 +39,7 @@ class MetalMetaballRenderer {
|
||||
|
||||
let width = Int(frame.width)
|
||||
let height = Int(frame.height)
|
||||
let textureDescriptor =
|
||||
MTLTextureDescriptor.texture2DDescriptorWithPixelFormat(.BGRA8Unorm, width: width, height: height, mipmapped: false)
|
||||
let textureDescriptor = MTLTextureDescriptor.texture2DDescriptorWithPixelFormat(.BGRA8Unorm, width: width, height: height, mipmapped: false)
|
||||
|
||||
let texture1 = context.device.newTextureWithDescriptor(textureDescriptor)
|
||||
activeComputeContext = MTLComputeContext(size: targetView.size, texture: texture1)
|
||||
@ -47,14 +47,15 @@ class MetalMetaballRenderer {
|
||||
idleComputeContext = MTLComputeContext(size: targetView.size, texture: texture2)
|
||||
}
|
||||
|
||||
func updateTargetView() {
|
||||
internal func updateTargetView() {
|
||||
|
||||
let timeout = dispatch_time(DISPATCH_TIME_NOW, 1000000000)
|
||||
dispatch_semaphore_wait(semaphore, timeout)
|
||||
|
||||
state = .Ending
|
||||
|
||||
dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.rawValue), 0)) { () -> Void in
|
||||
let userInteractiveQueue = dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.rawValue), 0)
|
||||
dispatch_async(userInteractiveQueue) { () -> Void in
|
||||
|
||||
let computeContext = self.activeComputeContext
|
||||
swap(&self.activeComputeContext, &self.idleComputeContext)
|
||||
@ -73,14 +74,14 @@ class MetalMetaballRenderer {
|
||||
|
||||
dispatch_semaphore_signal(self.semaphore)
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), { () -> Void in
|
||||
dispatch_async(dispatch_get_main_queue()) { () -> Void in
|
||||
// Send image to view
|
||||
self.targetView.image = uiimage
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func renderToContext(computeContext: MTLComputeContext) {
|
||||
internal func renderToContext(computeContext: MTLComputeContext) {
|
||||
let width = Int(targetView.width)
|
||||
let height = Int(targetView.height)
|
||||
|
||||
@ -93,8 +94,7 @@ class MetalMetaballRenderer {
|
||||
|
||||
// Configure info for computing
|
||||
let threadGroupCounts = MTLSizeMake(8, 8, 1)
|
||||
let threadGroups = MTLSizeMake(width / threadGroupCounts.width,
|
||||
height / threadGroupCounts.height, 1)
|
||||
let threadGroups = MTLSizeMake(width / threadGroupCounts.width, height / threadGroupCounts.height, 1)
|
||||
|
||||
// Send commands to metal and render
|
||||
let commandBuffer = context.commandQueue.commandBuffer()
|
||||
@ -112,18 +112,16 @@ class MetalMetaballRenderer {
|
||||
|
||||
commandBuffer.commit()
|
||||
commandBuffer.waitUntilCompleted()
|
||||
|
||||
} catch _ {
|
||||
print("Error!")
|
||||
assertionFailure()
|
||||
}
|
||||
}
|
||||
|
||||
func createImageFromContext(computeContext: MTLComputeContext) -> UIImage {
|
||||
internal func createImageFromContext(computeContext: MTLComputeContext) -> UIImage {
|
||||
let texture = computeContext.texture
|
||||
|
||||
// Get image info
|
||||
let imageSize = CGSizeMake(CGFloat(texture.width),
|
||||
CGFloat(texture.height))
|
||||
let imageSize = CGSizeMake(CGFloat(texture.width), CGFloat(texture.height))
|
||||
let width = Int(imageSize.width)
|
||||
let height = Int(imageSize.height)
|
||||
let imageByteCount = width * height * 4
|
||||
@ -136,60 +134,68 @@ class MetalMetaballRenderer {
|
||||
}
|
||||
|
||||
// Transfer texture info into image buffer
|
||||
texture.getBytes(computeContext.imageBuffer, bytesPerRow: bytesPerRow,
|
||||
fromRegion: region, mipmapLevel: 0)
|
||||
texture.getBytes(computeContext.imageBuffer, bytesPerRow: bytesPerRow, fromRegion: region, mipmapLevel: 0)
|
||||
|
||||
// Get info for UIImage
|
||||
let provider = CGDataProviderCreateWithData(nil, computeContext.imageBuffer,
|
||||
imageByteCount, nil)
|
||||
let provider = CGDataProviderCreateWithData(nil, computeContext.imageBuffer, imageByteCount, nil)
|
||||
let bitsPerComponent = 8
|
||||
let bitsperPixel = 32
|
||||
let colorSpaceRef = CGColorSpaceCreateDeviceRGB()
|
||||
let bitmapInfo = CGBitmapInfo(rawValue:
|
||||
CGImageAlphaInfo.PremultipliedLast.rawValue |
|
||||
CGBitmapInfo.ByteOrder32Big.rawValue)
|
||||
let bitmapInfoRaw = CGImageAlphaInfo.PremultipliedLast.rawValue | CGBitmapInfo.ByteOrder32Big.rawValue
|
||||
let bitmapInfo = CGBitmapInfo(rawValue: bitmapInfoRaw)
|
||||
let renderingIntent = CGColorRenderingIntent.RenderingIntentDefault
|
||||
|
||||
// Create UIImage from image buffer
|
||||
let imageRef = CGImageCreate(width, height,
|
||||
bitsPerComponent, bitsperPixel, bytesPerRow,
|
||||
colorSpaceRef, bitmapInfo, provider,
|
||||
nil, false, renderingIntent)
|
||||
let image = UIImage(CGImage: imageRef!, scale: 0.0,
|
||||
orientation: UIImageOrientation.Up)
|
||||
let imageRef = CGImageCreate(width, height, bitsPerComponent, bitsperPixel, bytesPerRow, colorSpaceRef, bitmapInfo, provider, nil, false, renderingIntent)
|
||||
let image = UIImage(CGImage: imageRef!, scale: 0.0, orientation: .Up)
|
||||
|
||||
return image
|
||||
}
|
||||
|
||||
func metaballEdgesBuffer() -> MTLBuffer {
|
||||
internal func metaballEdgesBuffer() -> MTLBuffer {
|
||||
let floats = dataSource.metaballGraph.adjacencyMatrix.buffer
|
||||
|
||||
let buffer = context.device.newBufferWithBytes(floats, length: floats.count * sizeof(Float), options: .StorageModeShared)
|
||||
let bufferLength = floats.count * sizeof(Float)
|
||||
let device = context.device
|
||||
let buffer = device.newBufferWithBytes(floats, length: bufferLength, options: .StorageModeShared)
|
||||
|
||||
return buffer
|
||||
}
|
||||
|
||||
func metaballBuffer() -> MTLBuffer {
|
||||
internal func metaballBuffer() -> MTLBuffer {
|
||||
// Create Float array for buffer
|
||||
// Exclude metaballs far from the view's bounds
|
||||
let border: CGFloat = 100
|
||||
let metaballs = self.dataSource.metaballs
|
||||
var floats: [Float] = metaballs.reduce([Float(0)]) { (var array, metaball) -> [Float] in
|
||||
|
||||
var floats = [Float](count: 5, repeatedValue: 0)
|
||||
floats = metaballs.reduce(floats) {
|
||||
(var array, metaball) -> [Float] in
|
||||
|
||||
let x = CGFloat(metaball.midX)
|
||||
let y = CGFloat(metaball.midY)
|
||||
if -border < x ≤ targetView.width + border &&
|
||||
-border < y ≤ targetView.height + border {
|
||||
array.append(Float(x))
|
||||
array.append(Float(y))
|
||||
|
||||
let color = metaball.color
|
||||
let components = color.components
|
||||
array.append(Float(components.red))
|
||||
array.append(Float(components.green))
|
||||
array.append(Float(components.blue))
|
||||
}
|
||||
return array
|
||||
}
|
||||
|
||||
// Add array size to start of array so metal knows how far to go
|
||||
floats[0] = Float(floats.count) / 2
|
||||
floats[0] = Float(floats.count) / 5
|
||||
|
||||
// Create buffer
|
||||
let buffer = context.device.newBufferWithBytes(floats, length: floats.count * sizeof(Float), options: .StorageModeShared)
|
||||
|
||||
let bufferLength = floats.count * sizeof(Float)
|
||||
let device = context.device
|
||||
let buffer = device.newBufferWithBytes(floats, length: bufferLength, options: .StorageModeShared)
|
||||
|
||||
return buffer
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
extension Dictionary {
|
||||
subscript(keys: [Key]) -> [Value] {
|
||||
get {
|
||||
return keys.unwrappedMap({ self[$0] })
|
||||
return keys.unwrappedMap { self[$0] }
|
||||
}
|
||||
set (values) {
|
||||
for (key, value) in zip(keys, values) {
|
||||
|
@ -2,10 +2,13 @@ import Foundation
|
||||
|
||||
extension NSNumber: DoubleValuable {
|
||||
var toDouble: Double {
|
||||
get { return self.doubleValue }
|
||||
get {
|
||||
return self.doubleValue
|
||||
}
|
||||
}
|
||||
|
||||
static func fromDouble(double: Double) -> Self {
|
||||
return self.init(double: double)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,43 +20,54 @@ protocol Numeric: DoubleValuable, Incrementable, Comparable,
|
||||
postfix func ++ (inout _: Self) -> Self
|
||||
}
|
||||
|
||||
protocol FastNumeric: Numeric { }
|
||||
protocol FastNumeric: Numeric {}
|
||||
|
||||
extension Double: FastNumeric {
|
||||
var toDouble: Double {
|
||||
get { return self }
|
||||
get {
|
||||
return self
|
||||
}
|
||||
}
|
||||
|
||||
static func fromDouble(double: Double) -> Double {
|
||||
return double
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Float: FastNumeric {
|
||||
var toDouble: Double {
|
||||
get { return Double(self) }
|
||||
get {
|
||||
return Double(self)
|
||||
}
|
||||
}
|
||||
|
||||
static func fromDouble(double: Double) -> Float {
|
||||
return Float(double)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Int: FastNumeric {
|
||||
var toDouble: Double {
|
||||
get { return Double(self) }
|
||||
get {
|
||||
return Double(self)
|
||||
}
|
||||
}
|
||||
|
||||
static func fromDouble(double: Double) -> Int {
|
||||
return Int(double)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//
|
||||
extension Int {
|
||||
|
||||
func times(@noescape block: () throws -> ()) rethrows {
|
||||
for _ in 1...self {
|
||||
try block()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import Darwin
|
||||
|
||||
infix operator ^ { associativity right precedence 131 }
|
||||
|
||||
func ^<T: DoubleValuable>(left: T, right: T) -> T {
|
||||
func ^ <T: DoubleValuable>(left: T, right: T) -> T {
|
||||
return T.fromDouble(pow(left.toDouble, right.toDouble))
|
||||
}
|
||||
|
||||
|
@ -7,62 +7,66 @@ protocol MemoizedSequence: LazySequenceType {
|
||||
memoizedFunction: (IndexType -> ReturnType))
|
||||
}
|
||||
|
||||
struct HashMemoizedSequence<IndexType: Hashable, ReturnType>:
|
||||
MemoizedSequence {
|
||||
|
||||
struct HashMemoizedSequence <IndexType: Hashable, ReturnType>:
|
||||
MemoizedSequence {
|
||||
let memoizedFunction: (IndexType -> ReturnType)
|
||||
let increment: ((inout IndexType) -> Void)
|
||||
let firstIndex: IndexType
|
||||
|
||||
let memoizedFunction: (IndexType -> ReturnType)
|
||||
let increment: ((inout IndexType) -> Void)
|
||||
let firstIndex: IndexType
|
||||
var index: IndexType
|
||||
|
||||
var index: IndexType
|
||||
init(first: IndexType,
|
||||
incrementer: ((inout IndexType) -> Void),
|
||||
memoizedFunction: (IndexType -> ReturnType)) {
|
||||
|
||||
init(first: IndexType,
|
||||
incrementer: ((inout IndexType) -> Void),
|
||||
memoizedFunction: (IndexType -> ReturnType)) {
|
||||
self.firstIndex = first
|
||||
|
||||
self.firstIndex = first
|
||||
self.index = first
|
||||
self.increment = incrementer
|
||||
self.memoizedFunction = memoizedFunction
|
||||
}
|
||||
|
||||
self.index = first
|
||||
self.increment = incrementer
|
||||
self.memoizedFunction = memoizedFunction
|
||||
}
|
||||
init(first: IndexType,
|
||||
incrementer: ((inout IndexType) -> Void),
|
||||
closure: ((IndexType -> ReturnType, IndexType) -> ReturnType)) {
|
||||
let memoizedFunction = memoize(closure)
|
||||
|
||||
init(first: IndexType,
|
||||
incrementer: ((inout IndexType) -> Void),
|
||||
closure: ((IndexType -> ReturnType, IndexType) -> ReturnType)) {
|
||||
let memoizedFunction = memoize(closure)
|
||||
self.init(first: first, incrementer: incrementer,
|
||||
memoizedFunction: memoizedFunction)
|
||||
}
|
||||
|
||||
self.init(first: first, incrementer: incrementer,
|
||||
memoizedFunction: memoizedFunction)
|
||||
}
|
||||
init(first: IndexType,
|
||||
incrementer: ((inout IndexType) -> Void),
|
||||
function: (IndexType -> ReturnType)) {
|
||||
|
||||
init(first: IndexType,
|
||||
incrementer: ((inout IndexType) -> Void),
|
||||
function: (IndexType -> ReturnType)) {
|
||||
let memoizedFunction = memoize(function)
|
||||
|
||||
let memoizedFunction = memoize(function)
|
||||
|
||||
self.init(first: first, incrementer: incrementer,
|
||||
memoizedFunction: memoizedFunction)
|
||||
}
|
||||
self.init(first: first, incrementer: incrementer,
|
||||
memoizedFunction: memoizedFunction)
|
||||
}
|
||||
}
|
||||
|
||||
extension HashMemoizedSequence: GeneratorType {
|
||||
|
||||
mutating func next() -> ReturnType? {
|
||||
let result = memoizedFunction(index)
|
||||
increment(&index)
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension HashMemoizedSequence: LazySequenceType {
|
||||
|
||||
func generate() -> HashMemoizedSequence {
|
||||
return self
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension HashMemoizedSequence where ReturnType: Comparable {
|
||||
|
||||
@warn_unused_result
|
||||
func contains(element: ReturnType) -> Bool {
|
||||
var index = firstIndex
|
||||
@ -79,6 +83,7 @@ extension HashMemoizedSequence where ReturnType: Comparable {
|
||||
increment(&index)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension HashMemoizedSequence where IndexType: Incrementable {
|
||||
@ -104,29 +109,29 @@ extension HashMemoizedSequence where IndexType: Incrementable {
|
||||
}
|
||||
|
||||
extension HashMemoizedSequence where IndexType: Incrementable,
|
||||
IndexType: IntegerLiteralConvertible {
|
||||
IndexType: IntegerLiteralConvertible {
|
||||
init(_ closure: ((IndexType -> ReturnType, IndexType) -> ReturnType)) {
|
||||
self.firstIndex = 1
|
||||
self.index = 1
|
||||
self.increment = { (inout index: IndexType) in
|
||||
index++
|
||||
}
|
||||
self.memoizedFunction = memoize(closure)
|
||||
self.firstIndex = 1
|
||||
self.index = 1
|
||||
self.increment = { (inout index: IndexType) in
|
||||
index++
|
||||
}
|
||||
self.memoizedFunction = memoize(closure)
|
||||
}
|
||||
|
||||
init(_ function: (IndexType -> ReturnType)) {
|
||||
self.firstIndex = 1
|
||||
self.index = 1
|
||||
self.increment = { (inout index: IndexType) in
|
||||
index++
|
||||
}
|
||||
self.memoizedFunction = memoize(function)
|
||||
self.firstIndex = 1
|
||||
self.index = 1
|
||||
self.increment = { (inout index: IndexType) in
|
||||
index++
|
||||
}
|
||||
self.memoizedFunction = memoize(function)
|
||||
}
|
||||
}
|
||||
|
||||
func memoize <H: Hashable, R> (closure: ((H -> R, H) -> R) ) -> (H -> R) {
|
||||
func memoize<H: Hashable, R>(closure: ((H -> R, H) -> R)) -> (H -> R) {
|
||||
|
||||
var table = [H : R]()
|
||||
var table = [H: R]()
|
||||
|
||||
var auxiliaryFunction: (H -> R)!
|
||||
auxiliaryFunction = { index in
|
||||
@ -143,9 +148,9 @@ func memoize <H: Hashable, R> (closure: ((H -> R, H) -> R) ) -> (H -> R) {
|
||||
return auxiliaryFunction
|
||||
}
|
||||
|
||||
func memoize <H: Hashable, R> (function: (H -> R) ) -> (H -> R) {
|
||||
func memoize<H: Hashable, R>(function: (H -> R)) -> (H -> R) {
|
||||
|
||||
var table = [H : R]()
|
||||
var table = [H: R]()
|
||||
|
||||
let result: (H -> R) = { index in
|
||||
if let result = table[index] {
|
||||
|
@ -1,13 +1,12 @@
|
||||
|
||||
prefix operator ± { }
|
||||
|
||||
prefix func ± <T: Numeric> (number: T) -> T {
|
||||
prefix func ± <T: Numeric>(number: T) -> T {
|
||||
|
||||
if number > 0 {
|
||||
return 1
|
||||
} else if number == 0 {
|
||||
return 0
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
if number > 0 {
|
||||
return 1
|
||||
} else if number == 0 {
|
||||
return 0
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
extension String: DoubleValuable {
|
||||
var toDouble: Double {
|
||||
get { return Double(self) ?? Double(0) }
|
||||
get {
|
||||
return Double(self) ?? Double(0)
|
||||
}
|
||||
}
|
||||
|
||||
static func fromDouble(double: Double) -> String {
|
||||
return "\(double)"
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import Darwin
|
||||
|
||||
class Random <T: Numeric> {
|
||||
class Random<T: Numeric> {
|
||||
|
||||
let max: Double = Double(INT32_MAX)
|
||||
let exponent: Double = 7
|
||||
@ -92,4 +92,5 @@ class Random <T: Numeric> {
|
||||
let result = Double((2 * rawRandom) - 1)
|
||||
return T.fromDouble(result)
|
||||
}
|
||||
|
||||
}
|
||||
|
15
PixelImage/UIColor.swift
Normal file
15
PixelImage/UIColor.swift
Normal file
@ -0,0 +1,15 @@
|
||||
import UIKit
|
||||
|
||||
extension UIColor {
|
||||
var components: (red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
|
||||
var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0, a: CGFloat = 0
|
||||
getRed(&r, green: &g, blue: &b, alpha: &a)
|
||||
return (r, g, b, a)
|
||||
}
|
||||
}
|
||||
|
||||
extension UIColor {
|
||||
convenience init(red255 red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) {
|
||||
self.init(red: red / 255, green: green / 255, blue: blue / 255, alpha: alpha / 255)
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
// TODO: Unite UIView and CALayer extensions under a common protocol
|
||||
|
||||
import UIKit
|
||||
|
||||
func - (left: CGPoint, right: CGPoint) -> CGVector {
|
||||
@ -31,6 +33,7 @@ extension CGVector {
|
||||
dx = dx / norm
|
||||
dy = dy / norm
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension CGFloat: DoubleValuable {
|
||||
@ -42,19 +45,28 @@ extension CGFloat: DoubleValuable {
|
||||
static func fromDouble(double: Double) -> CGFloat {
|
||||
return CGFloat(double)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension CGFloat: Numeric {}
|
||||
|
||||
extension CGRect {
|
||||
var x: CGFloat {
|
||||
get { return origin.x }
|
||||
set { origin.x = newValue }
|
||||
get {
|
||||
return origin.x
|
||||
}
|
||||
set {
|
||||
origin.x = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var y: CGFloat {
|
||||
get { return origin.y }
|
||||
set { origin.y = newValue }
|
||||
get {
|
||||
return origin.y
|
||||
}
|
||||
set {
|
||||
origin.y = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,41 +78,142 @@ extension CGSize {
|
||||
|
||||
extension UIView {
|
||||
var origin: CGPoint {
|
||||
get { return frame.origin }
|
||||
set { frame.origin = newValue }
|
||||
get {
|
||||
return frame.origin
|
||||
}
|
||||
set {
|
||||
frame.origin = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var size: CGSize {
|
||||
get { return frame.size }
|
||||
set { frame.size = newValue }
|
||||
get {
|
||||
return frame.size
|
||||
}
|
||||
set {
|
||||
frame.size = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var width: CGFloat {
|
||||
get { return frame.width }
|
||||
set { frame.size = CGSize(width: newValue, height: height) }
|
||||
get {
|
||||
return frame.width
|
||||
}
|
||||
set {
|
||||
frame.size = CGSize(width: newValue, height: height)
|
||||
}
|
||||
}
|
||||
|
||||
var height: CGFloat {
|
||||
get { return frame.height }
|
||||
set { frame.size = CGSize(width: width, height: newValue) }
|
||||
get {
|
||||
return frame.height
|
||||
}
|
||||
set {
|
||||
frame.size = CGSize(width: width, height: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
var middle: CGPoint {
|
||||
get {
|
||||
return CGPoint(x: origin.x + width/2, y: origin.y + height/2)
|
||||
return CGPoint(x: origin.x + width / 2, y: origin.y + height / 2)
|
||||
}
|
||||
set {
|
||||
origin = CGPoint(x: newValue.x - width/2, y: newValue.y - height/2)
|
||||
origin = CGPoint(x: newValue.x - width / 2, y: newValue.y - height / 2)
|
||||
}
|
||||
}
|
||||
|
||||
var midX: CGFloat {
|
||||
get { return middle.x }
|
||||
set { middle = CGPoint(x: newValue, y: midY) }
|
||||
get {
|
||||
return middle.x
|
||||
}
|
||||
set {
|
||||
middle = CGPoint(x: newValue, y: midY)
|
||||
}
|
||||
}
|
||||
|
||||
var midY: CGFloat {
|
||||
get { return middle.y }
|
||||
set { middle = CGPoint(x: midX, y: newValue) }
|
||||
get {
|
||||
return middle.y
|
||||
}
|
||||
set {
|
||||
middle = CGPoint(x: midX, y: newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CALayer {
|
||||
var origin: CGPoint {
|
||||
get {
|
||||
return frame.origin
|
||||
}
|
||||
set {
|
||||
frame.origin = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var size: CGSize {
|
||||
get {
|
||||
return frame.size
|
||||
}
|
||||
set {
|
||||
frame.size = newValue
|
||||
}
|
||||
}
|
||||
|
||||
var width: CGFloat {
|
||||
get {
|
||||
return frame.width
|
||||
}
|
||||
set {
|
||||
frame.size = CGSize(width: newValue, height: height)
|
||||
}
|
||||
}
|
||||
|
||||
var height: CGFloat {
|
||||
get {
|
||||
return frame.height
|
||||
}
|
||||
set {
|
||||
frame.size = CGSize(width: width, height: newValue)
|
||||
}
|
||||
}
|
||||
|
||||
var middle: CGPoint {
|
||||
get {
|
||||
return CGPoint(x: origin.x + width / 2, y: origin.y + height / 2)
|
||||
}
|
||||
set {
|
||||
origin = CGPoint(x: newValue.x - width / 2, y: newValue.y - height / 2)
|
||||
}
|
||||
}
|
||||
|
||||
var midX: CGFloat {
|
||||
get {
|
||||
return middle.x
|
||||
}
|
||||
set {
|
||||
middle = CGPoint(x: newValue, y: midY)
|
||||
}
|
||||
}
|
||||
|
||||
var midY: CGFloat {
|
||||
get {
|
||||
return middle.y
|
||||
}
|
||||
set {
|
||||
middle = CGPoint(x: midX, y: newValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let screenRect = UIScreen.mainScreen().bounds
|
||||
let screenWidth = screenRect.size.width
|
||||
let screenHeight = screenRect.size.height
|
||||
|
||||
func * <T: Numeric>(vector: CGVector, scalar: T) -> CGVector {
|
||||
return CGVector(dx: vector.dx * CGFloat(scalar.toDouble), dy: vector.dy * CGFloat(scalar.toDouble))
|
||||
}
|
||||
|
||||
func + (point: CGPoint, vector: CGVector) -> CGPoint {
|
||||
return CGPoint(x: point.x + vector.dx, y: point.y + vector.dy)
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
class ViewController: UIViewController, MetaballDataSource {
|
||||
@ -19,33 +18,85 @@ class ViewController: UIViewController, MetaballDataSource {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
view.backgroundColor = UIColor.whiteColor()
|
||||
|
||||
let recognizer = UIPanGestureRecognizer(target: self, action: "handlePan:")
|
||||
view.addGestureRecognizer(recognizer)
|
||||
|
||||
let metaballs = [CGPoint(x: 70, y: 70), CGPoint(x: 270, y: 70), CGPoint(x: 270, y: 470), CGPoint(x: 70, y: 470)].map {
|
||||
Metaball(position: $0)
|
||||
var positions = [CGPoint]()
|
||||
positions.append(CGPoint(x: Double(screenWidth) / 2, y: Double(screenHeight) / 2))
|
||||
for i in 1...5 {
|
||||
let sine = sin(Double(i) * 2 * M_PI / 5)
|
||||
let cosine = cos(Double(i) * 2 * M_PI / 5)
|
||||
let radius: Double = 150
|
||||
let x = Double(screenWidth) / 2 + sine * radius
|
||||
let y = Double(screenHeight) / 2 + cosine * radius
|
||||
positions.append(CGPoint(x: x, y: y))
|
||||
}
|
||||
metaballGraph = Graph(vertices: metaballs)
|
||||
metaballGraph.addEdge(0, 1)
|
||||
metaballGraph.addEdge(0, 3)
|
||||
metaballGraph.addEdge(1, 2)
|
||||
metaballGraph.addEdge(2, 3)
|
||||
|
||||
let border = 20
|
||||
let metaballViewFrame = CGRect(x: border/2, y: border/2, width: width, height: height)
|
||||
let colors =
|
||||
[UIColor(red255: 110, green: 220, blue: 220, alpha: 1.0),
|
||||
UIColor(red255: 250, green: 235, blue: 50, alpha: 1.0),
|
||||
UIColor(red255: 110, green: 220, blue: 220, alpha: 1.0),
|
||||
UIColor(red255: 90, green: 170, blue: 170, alpha: 1.0),
|
||||
UIColor(red255: 90, green: 170, blue: 170, alpha: 1.0),
|
||||
UIColor(red255: 110, green: 220, blue: 220, alpha: 1.0)]
|
||||
let metaballs = zip(positions, colors).map { Metaball(position: $0.0, color: $0.1) }
|
||||
|
||||
metaballGraph = Graph(vertices: metaballs)
|
||||
|
||||
let metaballViewFrame = screenRect
|
||||
renderer = MetalMetaballRenderer(dataSource: self, frame: metaballViewFrame)
|
||||
metaballView = renderer.targetView
|
||||
|
||||
let bigView = UIView(frame: CGRect(x: 10, y: 70, width: width + border, height: height + border))
|
||||
bigView.backgroundColor = UIColor.redColor()
|
||||
bigView.addSubview(metaballView)
|
||||
view.addSubview(bigView)
|
||||
view.addSubview(metaballView)
|
||||
|
||||
for metaball in metaballs {
|
||||
metaballView.addSubview(metaball)
|
||||
}
|
||||
|
||||
addEdge(0, 2)
|
||||
delay(1.0) {
|
||||
self.addEdge(0, 1)
|
||||
self.addEdge(0, 2)
|
||||
self.addEdge(0, 3)
|
||||
self.addEdge(0, 4)
|
||||
self.addEdge(0, 5)
|
||||
}
|
||||
|
||||
delay(2.3) {
|
||||
self.removeEdge(0, 1)
|
||||
self.removeEdge(0, 2)
|
||||
self.removeEdge(0, 3)
|
||||
self.removeEdge(0, 4)
|
||||
self.removeEdge(0, 5)
|
||||
}
|
||||
|
||||
delay(4) {
|
||||
for i in 1...5 {
|
||||
UIView.animateWithDuration(1.0, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
|
||||
let sine = sin(Double(i) * 2 * M_PI / 5)
|
||||
let cosine = cos(Double(i) * 2 * M_PI / 5)
|
||||
let radius: Double = 85
|
||||
let x = Double(screenWidth) / 2 + sine * radius
|
||||
let y = Double(screenHeight) / 2 + cosine * radius
|
||||
let metaball = metaballs[i]
|
||||
self.animateMetaball(metaball, toPoint: CGPoint(x: x, y: y))
|
||||
}, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
delay(6) { () -> () in
|
||||
for i in 1...5 {
|
||||
UIView.animateWithDuration(1.0, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in
|
||||
let sine = sin(Double(i) * 2 * M_PI / 5)
|
||||
let cosine = cos(Double(i) * 2 * M_PI / 5)
|
||||
let radius: Double = 150
|
||||
let x = Double(screenWidth) / 2 + sine * radius
|
||||
let y = Double(screenHeight) / 2 + cosine * radius
|
||||
let metaball = metaballs[i]
|
||||
self.animateMetaball(metaball, toPoint: CGPoint(x: x, y: y))
|
||||
}, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
renderer.state = .Running
|
||||
}
|
||||
@ -58,13 +109,41 @@ class ViewController: UIViewController, MetaballDataSource {
|
||||
animateEdge(i, j, fadeIn: false)
|
||||
}
|
||||
|
||||
func animateMetaball(metaball: Metaball, toPoint destination: CGPoint) {
|
||||
let parameters = VertexAnimationParameters(startDate: NSDate(), duration: 1.0, origin: metaball.center, destination: destination, metaball: metaball)
|
||||
NSTimer.scheduledTimerWithTimeInterval(1.0 / 60.0, target: self, selector: "animateMetaballWithTimer:", userInfo: parameters, repeats: true)
|
||||
}
|
||||
|
||||
func animateMetaball(withTimer timer: NSTimer) {
|
||||
guard let userInfo = timer.userInfo as? VertexAnimationParameters else { preconditionFailure() }
|
||||
let (animationStart, duration, origin, destination, metaball) = (userInfo).unpack()
|
||||
|
||||
let now = NSDate()
|
||||
var timeElapsed = Float(now.timeIntervalSinceDate(animationStart))
|
||||
|
||||
if timeElapsed > duration {
|
||||
timer.invalidate()
|
||||
timeElapsed = duration
|
||||
}
|
||||
|
||||
let linearValue = timeElapsed / duration
|
||||
let interpolatedValue = interpolateSmooth(linearValue)
|
||||
print(interpolatedValue)
|
||||
|
||||
let position = origin + ((destination - origin) * interpolatedValue)
|
||||
metaball.center = position
|
||||
|
||||
renderer.state = .Running
|
||||
}
|
||||
|
||||
func animateEdge(i: Int, _ j: Int, fadeIn: Bool) {
|
||||
let parameters = EdgeAnimationParameters(startDate: NSDate(), duration: edgeAnimationDuration, fadeIn: fadeIn, i: i, j: j)
|
||||
NSTimer.scheduledTimerWithTimeInterval(1.0/60.0, target: self, selector: "animateEdgeWithTimer:", userInfo: parameters, repeats: true)
|
||||
NSTimer.scheduledTimerWithTimeInterval(1.0 / 60.0, target: self, selector: "animateEdgeWithTimer:", userInfo: parameters, repeats: true)
|
||||
}
|
||||
|
||||
func animateEdge(withTimer timer: NSTimer) {
|
||||
let (animationStart, duration, fadeIn, i, j) = (timer.userInfo as! EdgeAnimationParameters).unpack()
|
||||
guard let userInfo = timer.userInfo as? EdgeAnimationParameters else { preconditionFailure() }
|
||||
let (animationStart, duration, fadeIn, i, j) = (userInfo).unpack()
|
||||
|
||||
let now = NSDate()
|
||||
var timeElapsed = Float(now.timeIntervalSinceDate(animationStart))
|
||||
@ -88,9 +167,9 @@ class ViewController: UIViewController, MetaballDataSource {
|
||||
|
||||
if selectedMetaball == nil {
|
||||
for metaball in metaballs where
|
||||
abs(metaball.midX - location.x) < 50 && abs(metaball.midY - location.y) < 50 {
|
||||
selectedMetaball = metaball
|
||||
break
|
||||
abs(metaball.midX - location.x) < 50 && abs(metaball.midY - location.y) < 50 {
|
||||
selectedMetaball = metaball
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -104,19 +183,5 @@ class ViewController: UIViewController, MetaballDataSource {
|
||||
selectedMetaball = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Metaball: UIView {
|
||||
|
||||
init(position: CGPoint) {
|
||||
super.init(frame: CGRect.zero)
|
||||
|
||||
size = CGSize(50)
|
||||
middle = position
|
||||
backgroundColor = UIColor(red: 0.1306, green: 0.7522, blue: 0.0307, alpha: 0.3)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -3,54 +3,117 @@
|
||||
|
||||
using namespace metal;
|
||||
|
||||
struct metaballData {
|
||||
float x;
|
||||
float y;
|
||||
float r;
|
||||
float g;
|
||||
float b;
|
||||
};
|
||||
|
||||
kernel void
|
||||
drawMetaballs(texture2d<float, access::write> outTexture[[texture(0)]],
|
||||
constant float *edgesBuffer[[buffer(1)]],
|
||||
constant float *metaballBuffer[[buffer(0)]],
|
||||
constant metaballData *metaballBuffer[[buffer(0)]],
|
||||
uint2 gid[[thread_position_in_grid]]) {
|
||||
|
||||
uint numberOfMetaballs = metaballBuffer[0];
|
||||
uint metaballArraySize = numberOfMetaballs * 2 + 1;
|
||||
char numberOfMetaballs = metaballBuffer[0].x - 1;
|
||||
|
||||
float sum = 0;
|
||||
float3 colorSum = float3(0, 0, 0);
|
||||
float colorAccumulation = 0;
|
||||
float3 colorSumLink = float3(0, 0, 0);
|
||||
|
||||
uint i, j, x, y;
|
||||
char x, y;
|
||||
|
||||
for (i = 1, x = 0; i < metaballArraySize; i += 2, x += 1) {
|
||||
for (j = i + 2, y = x + 1; j < metaballArraySize; j += 2, y += 1) {
|
||||
float2 metaball1 = float2(metaballBuffer[i], metaballBuffer[i + 1]);
|
||||
float metaballDistances[10];
|
||||
float2 metaballDirections[10];
|
||||
float3 metaballColors[10];
|
||||
|
||||
float2 metaball2 = float2(metaballBuffer[j], metaballBuffer[j + 1]);
|
||||
for (x = 1; x <= numberOfMetaballs; x += 1) {
|
||||
metaballData metaball = metaballBuffer[x];
|
||||
float2 metaballPosition = float2(metaball.x, metaball.y);
|
||||
float2 vector = float2(metaballPosition.x - gid.x, metaballPosition.y - gid.y);
|
||||
float dotProduct = dot(vector, vector);
|
||||
float squaredDistance = dotProduct > 0 ? dotProduct : 1;
|
||||
float realDistance = sqrt(squaredDistance);
|
||||
metaballDistances[x - 1] = squaredDistance;
|
||||
metaballDirections[x - 1] = vector / realDistance;
|
||||
metaballColors[x - 1] = float3(metaball.r, metaball.g, metaball.b);
|
||||
}
|
||||
|
||||
float2 metaball1Vector
|
||||
= float2(metaball1.x - gid.x, metaball1.y - gid.y);
|
||||
float2 metaball2Vector
|
||||
= float2(metaball2.x - gid.x, metaball2.y - gid.y);
|
||||
float bendClose = 0.0;
|
||||
float bendCloseCount = 0.0;
|
||||
float ball = 0.0;
|
||||
|
||||
float value1 = 2048 / (dot(metaball1Vector, metaball1Vector) + 1);
|
||||
float value2 = 2048 / (dot(metaball2Vector, metaball2Vector) + 1);
|
||||
for (x = 0; x < numberOfMetaballs; x += 1) {
|
||||
float distance1 = metaballDistances[x];
|
||||
float value1 = 1500 / distance1;
|
||||
float2 direction1 = metaballDirections[x];
|
||||
|
||||
float colorContribution = 1 / distance1;
|
||||
colorAccumulation += colorContribution;
|
||||
colorSum += metaballColors[x] * colorContribution;
|
||||
|
||||
for (y = x + 1; y < numberOfMetaballs; y += 1) {
|
||||
float distance2 = metaballDistances[y];
|
||||
float value2 = 1500 / distance2;
|
||||
float2 direction2 = metaballDirections[y];
|
||||
|
||||
float v = value1 + value2;
|
||||
|
||||
float weightedValue = 0.5 * v;
|
||||
|
||||
char edgeIndex = y * numberOfMetaballs + x;
|
||||
float edgeWeight = edgesBuffer[edgeIndex];
|
||||
float2 direction1 = normalize(metaball1Vector);
|
||||
float2 direction2 = normalize(metaball2Vector);
|
||||
|
||||
float cosine = dot(direction1, direction2);
|
||||
|
||||
float link = pow(((1 - cosine) * 0.5), 100);
|
||||
|
||||
float weightedLink = 0.6 * link * edgeWeight;
|
||||
|
||||
float result = weightedValue + weightedLink;
|
||||
|
||||
sum += mix(0.0, 1.0, step(0.5, weightedValue + weightedLink));
|
||||
ball += step(0.4, weightedValue);
|
||||
|
||||
float metaballValue = step(0.5, weightedValue + weightedLink);
|
||||
|
||||
if (metaballValue > 0.0) {
|
||||
colorSumLink = float3(0.0, 0.0, 0.0);
|
||||
colorSumLink += metaballColors[x] / distance1;
|
||||
colorSumLink += metaballColors[y] / distance2;
|
||||
colorSumLink /= (1 / distance1) + (1 / distance2);
|
||||
}
|
||||
|
||||
if (result > 0.4) {
|
||||
bendClose += result;
|
||||
bendCloseCount += 1.0;
|
||||
}
|
||||
|
||||
sum += metaballValue;
|
||||
}
|
||||
}
|
||||
|
||||
float result = mix(0.0, 1.0, step(0.4, sum));
|
||||
// bendClose = bendClose / bendCloseCount;
|
||||
|
||||
outTexture.write(float4(result, result / 2, 0, 1), gid);
|
||||
if (bendClose != 0.0) {
|
||||
bendClose = (bendClose - 0.4) * (1 / (0.6 - 0.4));
|
||||
} else {
|
||||
bendClose = 0.0;
|
||||
}
|
||||
|
||||
|
||||
float result = step(0.4, sum);
|
||||
result = clamp(result, 0.0, 1.0);
|
||||
|
||||
colorSum = colorSum / colorAccumulation;
|
||||
|
||||
if (result > 0.0 && ball < 1.0) {
|
||||
colorSum = colorSumLink;
|
||||
}
|
||||
|
||||
colorSum *= result;
|
||||
|
||||
if (colorSum.r == 0 && colorSum.g == 0 && colorSum.b == 0) {
|
||||
colorSum = float3(1, 1, 1);
|
||||
}
|
||||
|
||||
outTexture.write(float4(colorSum.bgr, 1), gid);
|
||||
}
|
||||
|
6
Podfile
Normal file
6
Podfile
Normal file
@ -0,0 +1,6 @@
|
||||
platform :ios, '9.2'
|
||||
use_frameworks!
|
||||
|
||||
target 'PixelImage' do
|
||||
pod 'ChameleonFramework/Swift'
|
||||
end
|
12
Podfile.lock
Normal file
12
Podfile.lock
Normal file
@ -0,0 +1,12 @@
|
||||
PODS:
|
||||
- ChameleonFramework/Default (2.1.0)
|
||||
- ChameleonFramework/Swift (2.1.0):
|
||||
- ChameleonFramework/Default
|
||||
|
||||
DEPENDENCIES:
|
||||
- ChameleonFramework/Swift
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
ChameleonFramework: d21a3cc247abfe5e37609a283a8238b03575cf64
|
||||
|
||||
COCOAPODS: 0.39.0
|
21
README.md
21
README.md
@ -1,2 +1,19 @@
|
||||
# metal-metaballs
|
||||
A 'playground' project for rendering metaball-like graphics in metal.
|
||||
![Example Animation](https://raw.githubusercontent.com/vinivendra/metal-metaballs/master/Examples/Animation.gif)
|
||||
|
||||
# Metal Metaballs
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
- [**Metaball**](https://en.wikipedia.org/wiki/Metaballs) math, in an adapted version, to render the graphics.
|
||||
- [**Metal**](https://developer.apple.com/metal/) compute shaders to draw the metaballs dynamically.
|
||||
- [**Grand Central Dispatch**](https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/) for doing CPU and GPU calculations simultaneously.
|
||||
- [**Double buffering**](https://en.wikipedia.org/wiki/Multiple_buffering), to speed up rendering times.
|
||||
- Different [**interpolations**](http://flexmonkey.blogspot.ca/2016/01/playing-with-interpolation-functions-in.html) for animations.
|
||||
|
||||
## Installation
|
||||
|
||||
Just download the app and run it on your metal-enabled device of choice :)
|
||||
|
Loading…
Reference in New Issue
Block a user