diff --git a/Metaballs.xcodeproj/project.pbxproj b/Metaballs.xcodeproj/project.pbxproj index 33468f8..594ae06 100644 --- a/Metaballs.xcodeproj/project.pbxproj +++ b/Metaballs.xcodeproj/project.pbxproj @@ -12,6 +12,10 @@ C0BBE36F1F2E816500E68524 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C0BBE36E1F2E816500E68524 /* Assets.xcassets */; }; C0BBE3721F2E816500E68524 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C0BBE3701F2E816500E68524 /* Main.storyboard */; }; C0BBE37D1F2E816500E68524 /* MetaballsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0BBE37C1F2E816500E68524 /* MetaballsTests.swift */; }; + C0BBE3951F2E81B600E68524 /* MetaballsKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C0BBE38C1F2E81B600E68524 /* MetaballsKit.framework */; }; + C0BBE39A1F2E81B600E68524 /* MetaballsKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0BBE3991F2E81B600E68524 /* MetaballsKitTests.swift */; }; + C0BBE39C1F2E81B600E68524 /* MetaballsKit.h in Headers */ = {isa = PBXBuildFile; fileRef = C0BBE38E1F2E81B600E68524 /* MetaballsKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; + C0BBE3A41F2E81C700E68524 /* Metaballs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0BBE3A31F2E81C700E68524 /* Metaballs.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -22,6 +26,13 @@ remoteGlobalIDString = C0BBE3661F2E816500E68524; remoteInfo = Metaballs; }; + C0BBE3961F2E81B600E68524 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = C0BBE35F1F2E816500E68524 /* Project object */; + proxyType = 1; + remoteGlobalIDString = C0BBE38B1F2E81B600E68524; + remoteInfo = MetaballsKit; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -34,6 +45,13 @@ C0BBE3781F2E816500E68524 /* MetaballsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MetaballsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; C0BBE37C1F2E816500E68524 /* MetaballsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaballsTests.swift; sourceTree = ""; }; C0BBE37E1F2E816500E68524 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C0BBE38C1F2E81B600E68524 /* MetaballsKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MetaballsKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + C0BBE38E1F2E81B600E68524 /* MetaballsKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MetaballsKit.h; sourceTree = ""; }; + C0BBE38F1F2E81B600E68524 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C0BBE3941F2E81B600E68524 /* MetaballsKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MetaballsKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + C0BBE3991F2E81B600E68524 /* MetaballsKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MetaballsKitTests.swift; sourceTree = ""; }; + C0BBE39B1F2E81B600E68524 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + C0BBE3A31F2E81C700E68524 /* Metaballs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Metaballs.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -51,6 +69,21 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C0BBE3881F2E81B600E68524 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C0BBE3911F2E81B600E68524 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + C0BBE3951F2E81B600E68524 /* MetaballsKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -59,6 +92,8 @@ children = ( C0BBE3691F2E816500E68524 /* Metaballs */, C0BBE37B1F2E816500E68524 /* MetaballsTests */, + C0BBE38D1F2E81B600E68524 /* MetaballsKit */, + C0BBE3981F2E81B600E68524 /* MetaballsKitTests */, C0BBE3681F2E816500E68524 /* Products */, ); sourceTree = ""; @@ -68,6 +103,8 @@ children = ( C0BBE3671F2E816500E68524 /* Metaballs.app */, C0BBE3781F2E816500E68524 /* MetaballsTests.xctest */, + C0BBE38C1F2E81B600E68524 /* MetaballsKit.framework */, + C0BBE3941F2E81B600E68524 /* MetaballsKitTests.xctest */, ); name = Products; sourceTree = ""; @@ -93,8 +130,38 @@ path = MetaballsTests; sourceTree = ""; }; + C0BBE38D1F2E81B600E68524 /* MetaballsKit */ = { + isa = PBXGroup; + children = ( + C0BBE38E1F2E81B600E68524 /* MetaballsKit.h */, + C0BBE38F1F2E81B600E68524 /* Info.plist */, + C0BBE3A31F2E81C700E68524 /* Metaballs.swift */, + ); + path = MetaballsKit; + sourceTree = ""; + }; + C0BBE3981F2E81B600E68524 /* MetaballsKitTests */ = { + isa = PBXGroup; + children = ( + C0BBE3991F2E81B600E68524 /* MetaballsKitTests.swift */, + C0BBE39B1F2E81B600E68524 /* Info.plist */, + ); + path = MetaballsKitTests; + sourceTree = ""; + }; /* End PBXGroup section */ +/* Begin PBXHeadersBuildPhase section */ + C0BBE3891F2E81B600E68524 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + C0BBE39C1F2E81B600E68524 /* MetaballsKit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + /* Begin PBXNativeTarget section */ C0BBE3661F2E816500E68524 /* Metaballs */ = { isa = PBXNativeTarget; @@ -131,6 +198,42 @@ productReference = C0BBE3781F2E816500E68524 /* MetaballsTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + C0BBE38B1F2E81B600E68524 /* MetaballsKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = C0BBE39D1F2E81B600E68524 /* Build configuration list for PBXNativeTarget "MetaballsKit" */; + buildPhases = ( + C0BBE3871F2E81B600E68524 /* Sources */, + C0BBE3881F2E81B600E68524 /* Frameworks */, + C0BBE3891F2E81B600E68524 /* Headers */, + C0BBE38A1F2E81B600E68524 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = MetaballsKit; + productName = MetaballsKit; + productReference = C0BBE38C1F2E81B600E68524 /* MetaballsKit.framework */; + productType = "com.apple.product-type.framework"; + }; + C0BBE3931F2E81B600E68524 /* MetaballsKitTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = C0BBE3A01F2E81B600E68524 /* Build configuration list for PBXNativeTarget "MetaballsKitTests" */; + buildPhases = ( + C0BBE3901F2E81B600E68524 /* Sources */, + C0BBE3911F2E81B600E68524 /* Frameworks */, + C0BBE3921F2E81B600E68524 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + C0BBE3971F2E81B600E68524 /* PBXTargetDependency */, + ); + name = MetaballsKitTests; + productName = MetaballsKitTests; + productReference = C0BBE3941F2E81B600E68524 /* MetaballsKitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -152,6 +255,17 @@ ProvisioningStyle = Automatic; TestTargetID = C0BBE3661F2E816500E68524; }; + C0BBE38B1F2E81B600E68524 = { + CreatedOnToolsVersion = 8.3.3; + DevelopmentTeam = 78372RE6B4; + LastSwiftMigration = 0830; + ProvisioningStyle = Automatic; + }; + C0BBE3931F2E81B600E68524 = { + CreatedOnToolsVersion = 8.3.3; + DevelopmentTeam = 78372RE6B4; + ProvisioningStyle = Automatic; + }; }; }; buildConfigurationList = C0BBE3621F2E816500E68524 /* Build configuration list for PBXProject "Metaballs" */; @@ -169,6 +283,8 @@ targets = ( C0BBE3661F2E816500E68524 /* Metaballs */, C0BBE3771F2E816500E68524 /* MetaballsTests */, + C0BBE38B1F2E81B600E68524 /* MetaballsKit */, + C0BBE3931F2E81B600E68524 /* MetaballsKitTests */, ); }; /* End PBXProject section */ @@ -190,6 +306,20 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C0BBE38A1F2E81B600E68524 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C0BBE3921F2E81B600E68524 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -210,6 +340,22 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + C0BBE3871F2E81B600E68524 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C0BBE3A41F2E81C700E68524 /* Metaballs.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + C0BBE3901F2E81B600E68524 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + C0BBE39A1F2E81B600E68524 /* MetaballsKitTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -218,6 +364,11 @@ target = C0BBE3661F2E816500E68524 /* Metaballs */; targetProxy = C0BBE3791F2E816500E68524 /* PBXContainerItemProxy */; }; + C0BBE3971F2E81B600E68524 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = C0BBE38B1F2E81B600E68524 /* MetaballsKit */; + targetProxy = C0BBE3961F2E81B600E68524 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -384,6 +535,85 @@ }; name = Release; }; + C0BBE39E1F2E81B600E68524 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 78372RE6B4; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = MetaballsKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = me.erynwells.MetaballsKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + C0BBE39F1F2E81B600E68524 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_IDENTITY = ""; + COMBINE_HIDPI_IMAGES = YES; + CURRENT_PROJECT_VERSION = 1; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 78372RE6B4; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = MetaballsKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = me.erynwells.MetaballsKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + C0BBE3A11F2E81B600E68524 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 78372RE6B4; + INFOPLIST_FILE = MetaballsKitTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = me.erynwells.MetaballsKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + C0BBE3A21F2E81B600E68524 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 78372RE6B4; + INFOPLIST_FILE = MetaballsKitTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = me.erynwells.MetaballsKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -412,6 +642,22 @@ ); defaultConfigurationIsVisible = 0; }; + C0BBE39D1F2E81B600E68524 /* Build configuration list for PBXNativeTarget "MetaballsKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C0BBE39E1F2E81B600E68524 /* Debug */, + C0BBE39F1F2E81B600E68524 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; + C0BBE3A01F2E81B600E68524 /* Build configuration list for PBXNativeTarget "MetaballsKitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C0BBE3A11F2E81B600E68524 /* Debug */, + C0BBE3A21F2E81B600E68524 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; /* End XCConfigurationList section */ }; rootObject = C0BBE35F1F2E816500E68524 /* Project object */; diff --git a/MetaballsKit/Info.plist b/MetaballsKit/Info.plist new file mode 100644 index 0000000..6368852 --- /dev/null +++ b/MetaballsKit/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2017 Eryn Wells. All rights reserved. + NSPrincipalClass + + + diff --git a/MetaballsKit/Metaballs.swift b/MetaballsKit/Metaballs.swift new file mode 100644 index 0000000..2f81f52 --- /dev/null +++ b/MetaballsKit/Metaballs.swift @@ -0,0 +1,78 @@ +// +// Metaballs.swift +// Metaballs +// +// Created by Eryn Wells on 7/30/17. +// Copyright © 2017 Eryn Wells. All rights reserved. +// + +import Foundation + +public struct Ball { + let radius: CGFloat + var position = CGPoint() + var velocity = CGVector() + + internal var bounds: CGRect { + let diameter = radius * 2 + return CGRect(x: position.x - radius, y: position.y - radius, width: diameter, height: diameter) + } + + init(radius r: CGFloat) { + radius = r + } + + internal mutating func update() { + position.x += velocity.dx + position.y += velocity.dy + } +} + +public struct Field { + var size: CGSize + private(set) var balls = [Ball]() + + internal var bounds: CGRect { + return CGRect(origin: CGPoint(), size: size) + } + + init(size s: CGSize) { + size = s + } + + public func update() { + let selfBounds = bounds + for var ball in balls { + // Update position of ball. + ball.update() + + if !selfBounds.contains(ball.position) { + // Degenerate case. If the ball finds itself outside the bounds of the field, plop it back in the center. + ball.position = CGPoint(x: selfBounds.midX, y: selfBounds.midY) + } else { + // Do collision detection with walls. + let ballBounds = ball.bounds + if !selfBounds.contains(ballBounds) { + if ballBounds.minX < selfBounds.minX || ballBounds.maxX > selfBounds.maxX { + ball.velocity.dx *= -1 + } + if ballBounds.minY < selfBounds.minY || ballBounds.maxY > selfBounds.maxY { + ball.velocity.dy *= -1 + } + } + } + } + } + + public func sample(at point: CGPoint) throws -> CGFloat { + return 0.0 + } + + public mutating func add(ball: Ball) throws { + guard bounds.contains(ball.bounds) else { + /// TODO: Throw an error. + return + } + balls.append(ball) + } +} diff --git a/MetaballsKit/MetaballsKit.h b/MetaballsKit/MetaballsKit.h new file mode 100644 index 0000000..955d4cf --- /dev/null +++ b/MetaballsKit/MetaballsKit.h @@ -0,0 +1,19 @@ +// +// MetaballsKit.h +// MetaballsKit +// +// Created by Eryn Wells on 7/30/17. +// Copyright © 2017 Eryn Wells. All rights reserved. +// + +#import + +//! Project version number for MetaballsKit. +FOUNDATION_EXPORT double MetaballsKitVersionNumber; + +//! Project version string for MetaballsKit. +FOUNDATION_EXPORT const unsigned char MetaballsKitVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/MetaballsKitTests/Info.plist b/MetaballsKitTests/Info.plist new file mode 100644 index 0000000..6c6c23c --- /dev/null +++ b/MetaballsKitTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/MetaballsKitTests/MetaballsKitTests.swift b/MetaballsKitTests/MetaballsKitTests.swift new file mode 100644 index 0000000..d350bdb --- /dev/null +++ b/MetaballsKitTests/MetaballsKitTests.swift @@ -0,0 +1,36 @@ +// +// MetaballsKitTests.swift +// MetaballsKitTests +// +// Created by Eryn Wells on 7/30/17. +// Copyright © 2017 Eryn Wells. All rights reserved. +// + +import XCTest +@testable import MetaballsKit + +class MetaballsKitTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +}