├── .github └── workflows │ └── swift.yml ├── .gitignore ├── .swiftpm └── xcode │ ├── package.xcworkspace │ └── contents.xcworkspacedata │ └── xcshareddata │ └── xcschemes │ └── RealityMorpher.xcscheme ├── Example ├── Cloth-simulation.gif ├── MorphTargetExample.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── MorphTargetExample.xcscheme ├── MorphTargetExample │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── ContentView.swift │ ├── MorphTargetExampleApp.swift │ ├── Presenter.swift │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── RealityView.swift │ ├── cloth0.usdz │ └── cloth1.usdz └── MorphTargetExampleTests │ ├── MorphTargetExample.xctestplan │ └── MorphTargetExampleTests.swift ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── RealityMorpher │ ├── Documentation.docc │ │ ├── Getting Started.md │ │ └── RealityMorpher.md │ ├── MorphAnimating.swift │ ├── MorphAnimation.swift │ ├── MorphComponent.swift │ ├── MorphEnvironment.swift │ ├── MorphWeights.swift │ └── SIMD+helpers.swift └── RealityMorpherKernels │ ├── MorphGeometryModifier.metal │ ├── Shared.m │ └── include │ └── Shared.h └── Tests ├── RealityMorpher.xctestplan └── RealityMorpherTests └── MorphComponentTests.swift /.github/workflows/swift.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Swift project 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-swift 3 | 4 | name: Swift 5 | 6 | on: 7 | push: 8 | branches: [ "main" ] 9 | pull_request: 10 | branches: [ "main" ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: macos-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - uses: swift-actions/setup-swift@v1 20 | with: 21 | swift-version: "5.9.0" 22 | - name: Build 23 | run: swift build -v 24 | - name: Run tests 25 | run: swift test -v 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/RealityMorpher.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 48 | 49 | 50 | 51 | 53 | 59 | 60 | 61 | 62 | 63 | 73 | 74 | 80 | 81 | 87 | 88 | 89 | 90 | 92 | 93 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /Example/Cloth-simulation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oliver-dew/RealityMorpher/8ee8c7a0d9229b27a163240f45fb5b5fb1fdb817/Example/Cloth-simulation.gif -------------------------------------------------------------------------------- /Example/MorphTargetExample.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 60; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 512D494A2A83C47100244033 /* Presenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 512D49492A83C47100244033 /* Presenter.swift */; }; 11 | 513DB0772A79AFD30098D767 /* MorphTargetExampleApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513DB0762A79AFD30098D767 /* MorphTargetExampleApp.swift */; }; 12 | 513DB0792A79AFD30098D767 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513DB0782A79AFD30098D767 /* ContentView.swift */; }; 13 | 513DB07B2A79AFD60098D767 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 513DB07A2A79AFD60098D767 /* Assets.xcassets */; }; 14 | 513DB07E2A79AFD60098D767 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 513DB07D2A79AFD60098D767 /* Preview Assets.xcassets */; }; 15 | 513DB0882A79AFD60098D767 /* MorphTargetExampleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513DB0872A79AFD60098D767 /* MorphTargetExampleTests.swift */; }; 16 | 513DB0A52A7AAF0D0098D767 /* RealityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 513DB0A42A7AAF0D0098D767 /* RealityView.swift */; }; 17 | 513DB0A82A7AB06F0098D767 /* cloth0.usdz in Resources */ = {isa = PBXBuildFile; fileRef = 513DB0A62A7AB06F0098D767 /* cloth0.usdz */; }; 18 | 513DB0A92A7AB06F0098D767 /* cloth1.usdz in Resources */ = {isa = PBXBuildFile; fileRef = 513DB0A72A7AB06F0098D767 /* cloth1.usdz */; }; 19 | 5170B3C32A8A7ACF006B590C /* RealityMorpher in Frameworks */ = {isa = PBXBuildFile; productRef = 5170B3C22A8A7ACF006B590C /* RealityMorpher */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | 513DB0842A79AFD60098D767 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = 513DB06B2A79AFD30098D767 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = 513DB0722A79AFD30098D767; 28 | remoteInfo = MorphTargetExample; 29 | }; 30 | 513DB08E2A79AFD60098D767 /* PBXContainerItemProxy */ = { 31 | isa = PBXContainerItemProxy; 32 | containerPortal = 513DB06B2A79AFD30098D767 /* Project object */; 33 | proxyType = 1; 34 | remoteGlobalIDString = 513DB0722A79AFD30098D767; 35 | remoteInfo = MorphTargetExample; 36 | }; 37 | /* End PBXContainerItemProxy section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | 512D49492A83C47100244033 /* Presenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Presenter.swift; sourceTree = ""; }; 41 | 513DB0732A79AFD30098D767 /* MorphTargetExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MorphTargetExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 513DB0762A79AFD30098D767 /* MorphTargetExampleApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MorphTargetExampleApp.swift; sourceTree = ""; }; 43 | 513DB0782A79AFD30098D767 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 44 | 513DB07A2A79AFD60098D767 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 45 | 513DB07D2A79AFD60098D767 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 46 | 513DB0832A79AFD60098D767 /* MorphTargetExampleTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MorphTargetExampleTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 513DB0872A79AFD60098D767 /* MorphTargetExampleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MorphTargetExampleTests.swift; sourceTree = ""; }; 48 | 513DB08D2A79AFD60098D767 /* MorphTargetExampleUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MorphTargetExampleUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 49 | 513DB0A42A7AAF0D0098D767 /* RealityView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RealityView.swift; sourceTree = ""; }; 50 | 513DB0A62A7AB06F0098D767 /* cloth0.usdz */ = {isa = PBXFileReference; lastKnownFileType = file.usdz; path = cloth0.usdz; sourceTree = ""; }; 51 | 513DB0A72A7AB06F0098D767 /* cloth1.usdz */ = {isa = PBXFileReference; lastKnownFileType = file.usdz; path = cloth1.usdz; sourceTree = ""; }; 52 | 515F1AD62A8A2D9F00611AF9 /* MorphTargetExample.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; path = MorphTargetExample.xctestplan; sourceTree = ""; }; 53 | /* End PBXFileReference section */ 54 | 55 | /* Begin PBXFrameworksBuildPhase section */ 56 | 513DB0702A79AFD30098D767 /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | 5170B3C32A8A7ACF006B590C /* RealityMorpher in Frameworks */, 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | 513DB0802A79AFD60098D767 /* Frameworks */ = { 65 | isa = PBXFrameworksBuildPhase; 66 | buildActionMask = 2147483647; 67 | files = ( 68 | ); 69 | runOnlyForDeploymentPostprocessing = 0; 70 | }; 71 | 513DB08A2A79AFD60098D767 /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | /* End PBXFrameworksBuildPhase section */ 79 | 80 | /* Begin PBXGroup section */ 81 | 513DB06A2A79AFD30098D767 = { 82 | isa = PBXGroup; 83 | children = ( 84 | 513DB0752A79AFD30098D767 /* MorphTargetExample */, 85 | 513DB0862A79AFD60098D767 /* MorphTargetExampleTests */, 86 | 513DB0742A79AFD30098D767 /* Products */, 87 | 513DB0AA2A7AB28D0098D767 /* Frameworks */, 88 | ); 89 | sourceTree = ""; 90 | }; 91 | 513DB0742A79AFD30098D767 /* Products */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 513DB0732A79AFD30098D767 /* MorphTargetExample.app */, 95 | 513DB0832A79AFD60098D767 /* MorphTargetExampleTests.xctest */, 96 | 513DB08D2A79AFD60098D767 /* MorphTargetExampleUITests.xctest */, 97 | ); 98 | name = Products; 99 | sourceTree = ""; 100 | }; 101 | 513DB0752A79AFD30098D767 /* MorphTargetExample */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 513DB0A62A7AB06F0098D767 /* cloth0.usdz */, 105 | 513DB0A72A7AB06F0098D767 /* cloth1.usdz */, 106 | 513DB0762A79AFD30098D767 /* MorphTargetExampleApp.swift */, 107 | 513DB0782A79AFD30098D767 /* ContentView.swift */, 108 | 512D49492A83C47100244033 /* Presenter.swift */, 109 | 513DB0A42A7AAF0D0098D767 /* RealityView.swift */, 110 | 513DB07A2A79AFD60098D767 /* Assets.xcassets */, 111 | 513DB07C2A79AFD60098D767 /* Preview Content */, 112 | ); 113 | path = MorphTargetExample; 114 | sourceTree = ""; 115 | }; 116 | 513DB07C2A79AFD60098D767 /* Preview Content */ = { 117 | isa = PBXGroup; 118 | children = ( 119 | 513DB07D2A79AFD60098D767 /* Preview Assets.xcassets */, 120 | ); 121 | path = "Preview Content"; 122 | sourceTree = ""; 123 | }; 124 | 513DB0862A79AFD60098D767 /* MorphTargetExampleTests */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 515F1AD62A8A2D9F00611AF9 /* MorphTargetExample.xctestplan */, 128 | 513DB0872A79AFD60098D767 /* MorphTargetExampleTests.swift */, 129 | ); 130 | path = MorphTargetExampleTests; 131 | sourceTree = ""; 132 | }; 133 | 513DB0AA2A7AB28D0098D767 /* Frameworks */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | ); 137 | name = Frameworks; 138 | sourceTree = ""; 139 | }; 140 | /* End PBXGroup section */ 141 | 142 | /* Begin PBXNativeTarget section */ 143 | 513DB0722A79AFD30098D767 /* MorphTargetExample */ = { 144 | isa = PBXNativeTarget; 145 | buildConfigurationList = 513DB0972A79AFD60098D767 /* Build configuration list for PBXNativeTarget "MorphTargetExample" */; 146 | buildPhases = ( 147 | 513DB06F2A79AFD30098D767 /* Sources */, 148 | 513DB0702A79AFD30098D767 /* Frameworks */, 149 | 513DB0712A79AFD30098D767 /* Resources */, 150 | ); 151 | buildRules = ( 152 | ); 153 | dependencies = ( 154 | ); 155 | name = MorphTargetExample; 156 | packageProductDependencies = ( 157 | 5170B3C22A8A7ACF006B590C /* RealityMorpher */, 158 | ); 159 | productName = MorphTargetExample; 160 | productReference = 513DB0732A79AFD30098D767 /* MorphTargetExample.app */; 161 | productType = "com.apple.product-type.application"; 162 | }; 163 | 513DB0822A79AFD60098D767 /* MorphTargetExampleTests */ = { 164 | isa = PBXNativeTarget; 165 | buildConfigurationList = 513DB09A2A79AFD60098D767 /* Build configuration list for PBXNativeTarget "MorphTargetExampleTests" */; 166 | buildPhases = ( 167 | 513DB07F2A79AFD60098D767 /* Sources */, 168 | 513DB0802A79AFD60098D767 /* Frameworks */, 169 | 513DB0812A79AFD60098D767 /* Resources */, 170 | ); 171 | buildRules = ( 172 | ); 173 | dependencies = ( 174 | 513DB0852A79AFD60098D767 /* PBXTargetDependency */, 175 | ); 176 | name = MorphTargetExampleTests; 177 | productName = MorphTargetExampleTests; 178 | productReference = 513DB0832A79AFD60098D767 /* MorphTargetExampleTests.xctest */; 179 | productType = "com.apple.product-type.bundle.unit-test"; 180 | }; 181 | 513DB08C2A79AFD60098D767 /* MorphTargetExampleUITests */ = { 182 | isa = PBXNativeTarget; 183 | buildConfigurationList = 513DB09D2A79AFD60098D767 /* Build configuration list for PBXNativeTarget "MorphTargetExampleUITests" */; 184 | buildPhases = ( 185 | 513DB0892A79AFD60098D767 /* Sources */, 186 | 513DB08A2A79AFD60098D767 /* Frameworks */, 187 | 513DB08B2A79AFD60098D767 /* Resources */, 188 | ); 189 | buildRules = ( 190 | ); 191 | dependencies = ( 192 | 513DB08F2A79AFD60098D767 /* PBXTargetDependency */, 193 | ); 194 | name = MorphTargetExampleUITests; 195 | productName = MorphTargetExampleUITests; 196 | productReference = 513DB08D2A79AFD60098D767 /* MorphTargetExampleUITests.xctest */; 197 | productType = "com.apple.product-type.bundle.ui-testing"; 198 | }; 199 | /* End PBXNativeTarget section */ 200 | 201 | /* Begin PBXProject section */ 202 | 513DB06B2A79AFD30098D767 /* Project object */ = { 203 | isa = PBXProject; 204 | attributes = { 205 | BuildIndependentTargetsInParallel = 1; 206 | LastSwiftUpdateCheck = 1500; 207 | LastUpgradeCheck = 1500; 208 | TargetAttributes = { 209 | 513DB0722A79AFD30098D767 = { 210 | CreatedOnToolsVersion = 15.0; 211 | LastSwiftMigration = 1500; 212 | }; 213 | 513DB0822A79AFD60098D767 = { 214 | CreatedOnToolsVersion = 15.0; 215 | TestTargetID = 513DB0722A79AFD30098D767; 216 | }; 217 | 513DB08C2A79AFD60098D767 = { 218 | CreatedOnToolsVersion = 15.0; 219 | TestTargetID = 513DB0722A79AFD30098D767; 220 | }; 221 | }; 222 | }; 223 | buildConfigurationList = 513DB06E2A79AFD30098D767 /* Build configuration list for PBXProject "MorphTargetExample" */; 224 | compatibilityVersion = "Xcode 14.0"; 225 | developmentRegion = en; 226 | hasScannedForEncodings = 0; 227 | knownRegions = ( 228 | en, 229 | Base, 230 | ); 231 | mainGroup = 513DB06A2A79AFD30098D767; 232 | packageReferences = ( 233 | 5170B3C12A8A7ACF006B590C /* XCLocalSwiftPackageReference ".." */, 234 | ); 235 | productRefGroup = 513DB0742A79AFD30098D767 /* Products */; 236 | projectDirPath = ""; 237 | projectRoot = ""; 238 | targets = ( 239 | 513DB0722A79AFD30098D767 /* MorphTargetExample */, 240 | 513DB0822A79AFD60098D767 /* MorphTargetExampleTests */, 241 | 513DB08C2A79AFD60098D767 /* MorphTargetExampleUITests */, 242 | ); 243 | }; 244 | /* End PBXProject section */ 245 | 246 | /* Begin PBXResourcesBuildPhase section */ 247 | 513DB0712A79AFD30098D767 /* Resources */ = { 248 | isa = PBXResourcesBuildPhase; 249 | buildActionMask = 2147483647; 250 | files = ( 251 | 513DB07E2A79AFD60098D767 /* Preview Assets.xcassets in Resources */, 252 | 513DB07B2A79AFD60098D767 /* Assets.xcassets in Resources */, 253 | 513DB0A92A7AB06F0098D767 /* cloth1.usdz in Resources */, 254 | 513DB0A82A7AB06F0098D767 /* cloth0.usdz in Resources */, 255 | ); 256 | runOnlyForDeploymentPostprocessing = 0; 257 | }; 258 | 513DB0812A79AFD60098D767 /* Resources */ = { 259 | isa = PBXResourcesBuildPhase; 260 | buildActionMask = 2147483647; 261 | files = ( 262 | ); 263 | runOnlyForDeploymentPostprocessing = 0; 264 | }; 265 | 513DB08B2A79AFD60098D767 /* Resources */ = { 266 | isa = PBXResourcesBuildPhase; 267 | buildActionMask = 2147483647; 268 | files = ( 269 | ); 270 | runOnlyForDeploymentPostprocessing = 0; 271 | }; 272 | /* End PBXResourcesBuildPhase section */ 273 | 274 | /* Begin PBXSourcesBuildPhase section */ 275 | 513DB06F2A79AFD30098D767 /* Sources */ = { 276 | isa = PBXSourcesBuildPhase; 277 | buildActionMask = 2147483647; 278 | files = ( 279 | 513DB0792A79AFD30098D767 /* ContentView.swift in Sources */, 280 | 513DB0A52A7AAF0D0098D767 /* RealityView.swift in Sources */, 281 | 512D494A2A83C47100244033 /* Presenter.swift in Sources */, 282 | 513DB0772A79AFD30098D767 /* MorphTargetExampleApp.swift in Sources */, 283 | ); 284 | runOnlyForDeploymentPostprocessing = 0; 285 | }; 286 | 513DB07F2A79AFD60098D767 /* Sources */ = { 287 | isa = PBXSourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | 513DB0882A79AFD60098D767 /* MorphTargetExampleTests.swift in Sources */, 291 | ); 292 | runOnlyForDeploymentPostprocessing = 0; 293 | }; 294 | 513DB0892A79AFD60098D767 /* Sources */ = { 295 | isa = PBXSourcesBuildPhase; 296 | buildActionMask = 2147483647; 297 | files = ( 298 | ); 299 | runOnlyForDeploymentPostprocessing = 0; 300 | }; 301 | /* End PBXSourcesBuildPhase section */ 302 | 303 | /* Begin PBXTargetDependency section */ 304 | 513DB0852A79AFD60098D767 /* PBXTargetDependency */ = { 305 | isa = PBXTargetDependency; 306 | target = 513DB0722A79AFD30098D767 /* MorphTargetExample */; 307 | targetProxy = 513DB0842A79AFD60098D767 /* PBXContainerItemProxy */; 308 | }; 309 | 513DB08F2A79AFD60098D767 /* PBXTargetDependency */ = { 310 | isa = PBXTargetDependency; 311 | target = 513DB0722A79AFD30098D767 /* MorphTargetExample */; 312 | targetProxy = 513DB08E2A79AFD60098D767 /* PBXContainerItemProxy */; 313 | }; 314 | /* End PBXTargetDependency section */ 315 | 316 | /* Begin XCBuildConfiguration section */ 317 | 513DB0952A79AFD60098D767 /* Debug */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | ALWAYS_SEARCH_USER_PATHS = NO; 321 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 322 | CLANG_ANALYZER_NONNULL = YES; 323 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 324 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 325 | CLANG_ENABLE_MODULES = YES; 326 | CLANG_ENABLE_OBJC_ARC = YES; 327 | CLANG_ENABLE_OBJC_WEAK = YES; 328 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 329 | CLANG_WARN_BOOL_CONVERSION = YES; 330 | CLANG_WARN_COMMA = YES; 331 | CLANG_WARN_CONSTANT_CONVERSION = YES; 332 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 333 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 334 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 335 | CLANG_WARN_EMPTY_BODY = YES; 336 | CLANG_WARN_ENUM_CONVERSION = YES; 337 | CLANG_WARN_INFINITE_RECURSION = YES; 338 | CLANG_WARN_INT_CONVERSION = YES; 339 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 340 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 341 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 342 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 343 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 344 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 345 | CLANG_WARN_STRICT_PROTOTYPES = YES; 346 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 347 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 348 | CLANG_WARN_UNREACHABLE_CODE = YES; 349 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 350 | COPY_PHASE_STRIP = NO; 351 | DEBUG_INFORMATION_FORMAT = dwarf; 352 | ENABLE_STRICT_OBJC_MSGSEND = YES; 353 | ENABLE_TESTABILITY = YES; 354 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 355 | GCC_C_LANGUAGE_STANDARD = gnu17; 356 | GCC_DYNAMIC_NO_PIC = NO; 357 | GCC_NO_COMMON_BLOCKS = YES; 358 | GCC_OPTIMIZATION_LEVEL = 0; 359 | GCC_PREPROCESSOR_DEFINITIONS = ( 360 | "DEBUG=1", 361 | "$(inherited)", 362 | ); 363 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 364 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 365 | GCC_WARN_UNDECLARED_SELECTOR = YES; 366 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 367 | GCC_WARN_UNUSED_FUNCTION = YES; 368 | GCC_WARN_UNUSED_VARIABLE = YES; 369 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 370 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 371 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 372 | MTL_FAST_MATH = YES; 373 | ONLY_ACTIVE_ARCH = YES; 374 | SDKROOT = iphoneos; 375 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 376 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 377 | }; 378 | name = Debug; 379 | }; 380 | 513DB0962A79AFD60098D767 /* Release */ = { 381 | isa = XCBuildConfiguration; 382 | buildSettings = { 383 | ALWAYS_SEARCH_USER_PATHS = NO; 384 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 385 | CLANG_ANALYZER_NONNULL = YES; 386 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 387 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 388 | CLANG_ENABLE_MODULES = YES; 389 | CLANG_ENABLE_OBJC_ARC = YES; 390 | CLANG_ENABLE_OBJC_WEAK = YES; 391 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 392 | CLANG_WARN_BOOL_CONVERSION = YES; 393 | CLANG_WARN_COMMA = YES; 394 | CLANG_WARN_CONSTANT_CONVERSION = YES; 395 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 396 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 397 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 398 | CLANG_WARN_EMPTY_BODY = YES; 399 | CLANG_WARN_ENUM_CONVERSION = YES; 400 | CLANG_WARN_INFINITE_RECURSION = YES; 401 | CLANG_WARN_INT_CONVERSION = YES; 402 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 403 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 404 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 405 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 406 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 407 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 408 | CLANG_WARN_STRICT_PROTOTYPES = YES; 409 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 410 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 411 | CLANG_WARN_UNREACHABLE_CODE = YES; 412 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 413 | COPY_PHASE_STRIP = NO; 414 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 415 | ENABLE_NS_ASSERTIONS = NO; 416 | ENABLE_STRICT_OBJC_MSGSEND = YES; 417 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 418 | GCC_C_LANGUAGE_STANDARD = gnu17; 419 | GCC_NO_COMMON_BLOCKS = YES; 420 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 421 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 422 | GCC_WARN_UNDECLARED_SELECTOR = YES; 423 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 424 | GCC_WARN_UNUSED_FUNCTION = YES; 425 | GCC_WARN_UNUSED_VARIABLE = YES; 426 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 427 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 428 | MTL_ENABLE_DEBUG_INFO = NO; 429 | MTL_FAST_MATH = YES; 430 | SDKROOT = iphoneos; 431 | SWIFT_COMPILATION_MODE = wholemodule; 432 | VALIDATE_PRODUCT = YES; 433 | }; 434 | name = Release; 435 | }; 436 | 513DB0982A79AFD60098D767 /* Debug */ = { 437 | isa = XCBuildConfiguration; 438 | buildSettings = { 439 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 440 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 441 | CLANG_ENABLE_MODULES = YES; 442 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 443 | CODE_SIGN_STYLE = Manual; 444 | CURRENT_PROJECT_VERSION = 1; 445 | DEVELOPMENT_ASSET_PATHS = "\"MorphTargetExample/Preview Content\""; 446 | DEVELOPMENT_TEAM = ""; 447 | "DEVELOPMENT_TEAM[sdk=iphoneos*]" = VG9R2N57GC; 448 | ENABLE_PREVIEWS = YES; 449 | GENERATE_INFOPLIST_FILE = YES; 450 | INFOPLIST_KEY_MetalCaptureEnabled = YES; 451 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 452 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 453 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 454 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 455 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 456 | LD_RUNPATH_SEARCH_PATHS = ( 457 | "$(inherited)", 458 | "@executable_path/Frameworks", 459 | ); 460 | MARKETING_VERSION = 1.0; 461 | PRODUCT_BUNDLE_IDENTIFIER = SaltPig.MorphTargetExample; 462 | PRODUCT_NAME = "$(TARGET_NAME)"; 463 | PROVISIONING_PROFILE_SPECIFIER = ""; 464 | "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Wildcard Development Profile"; 465 | SWIFT_EMIT_LOC_STRINGS = YES; 466 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 467 | SWIFT_VERSION = 5.0; 468 | TARGETED_DEVICE_FAMILY = "1,2"; 469 | }; 470 | name = Debug; 471 | }; 472 | 513DB0992A79AFD60098D767 /* Release */ = { 473 | isa = XCBuildConfiguration; 474 | buildSettings = { 475 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 476 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 477 | CLANG_ENABLE_MODULES = YES; 478 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 479 | CODE_SIGN_STYLE = Manual; 480 | CURRENT_PROJECT_VERSION = 1; 481 | DEVELOPMENT_ASSET_PATHS = "\"MorphTargetExample/Preview Content\""; 482 | DEVELOPMENT_TEAM = ""; 483 | "DEVELOPMENT_TEAM[sdk=iphoneos*]" = VG9R2N57GC; 484 | ENABLE_PREVIEWS = YES; 485 | GENERATE_INFOPLIST_FILE = YES; 486 | INFOPLIST_KEY_MetalCaptureEnabled = YES; 487 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 488 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 489 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 490 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 491 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 492 | LD_RUNPATH_SEARCH_PATHS = ( 493 | "$(inherited)", 494 | "@executable_path/Frameworks", 495 | ); 496 | MARKETING_VERSION = 1.0; 497 | PRODUCT_BUNDLE_IDENTIFIER = SaltPig.MorphTargetExample; 498 | PRODUCT_NAME = "$(TARGET_NAME)"; 499 | PROVISIONING_PROFILE_SPECIFIER = ""; 500 | "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "Wildcard Development Profile"; 501 | SWIFT_EMIT_LOC_STRINGS = YES; 502 | SWIFT_VERSION = 5.0; 503 | TARGETED_DEVICE_FAMILY = "1,2"; 504 | }; 505 | name = Release; 506 | }; 507 | 513DB09B2A79AFD60098D767 /* Debug */ = { 508 | isa = XCBuildConfiguration; 509 | buildSettings = { 510 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 511 | BUNDLE_LOADER = "$(TEST_HOST)"; 512 | CODE_SIGN_STYLE = Automatic; 513 | CURRENT_PROJECT_VERSION = 1; 514 | DEVELOPMENT_TEAM = ST84B2RF4F; 515 | GENERATE_INFOPLIST_FILE = YES; 516 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 517 | MARKETING_VERSION = 1.0; 518 | PRODUCT_BUNDLE_IDENTIFIER = SaltPig.MorphTargetExampleTests; 519 | PRODUCT_NAME = "$(TARGET_NAME)"; 520 | SWIFT_EMIT_LOC_STRINGS = NO; 521 | SWIFT_VERSION = 5.0; 522 | TARGETED_DEVICE_FAMILY = "1,2"; 523 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MorphTargetExample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MorphTargetExample"; 524 | }; 525 | name = Debug; 526 | }; 527 | 513DB09C2A79AFD60098D767 /* Release */ = { 528 | isa = XCBuildConfiguration; 529 | buildSettings = { 530 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 531 | BUNDLE_LOADER = "$(TEST_HOST)"; 532 | CODE_SIGN_STYLE = Automatic; 533 | CURRENT_PROJECT_VERSION = 1; 534 | DEVELOPMENT_TEAM = ST84B2RF4F; 535 | GENERATE_INFOPLIST_FILE = YES; 536 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 537 | MARKETING_VERSION = 1.0; 538 | PRODUCT_BUNDLE_IDENTIFIER = SaltPig.MorphTargetExampleTests; 539 | PRODUCT_NAME = "$(TARGET_NAME)"; 540 | SWIFT_EMIT_LOC_STRINGS = NO; 541 | SWIFT_VERSION = 5.0; 542 | TARGETED_DEVICE_FAMILY = "1,2"; 543 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/MorphTargetExample.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/MorphTargetExample"; 544 | }; 545 | name = Release; 546 | }; 547 | 513DB09E2A79AFD60098D767 /* Debug */ = { 548 | isa = XCBuildConfiguration; 549 | buildSettings = { 550 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 551 | CODE_SIGN_STYLE = Automatic; 552 | CURRENT_PROJECT_VERSION = 1; 553 | DEVELOPMENT_TEAM = ST84B2RF4F; 554 | GENERATE_INFOPLIST_FILE = YES; 555 | MARKETING_VERSION = 1.0; 556 | PRODUCT_BUNDLE_IDENTIFIER = SaltPig.MorphTargetExampleUITests; 557 | PRODUCT_NAME = "$(TARGET_NAME)"; 558 | SWIFT_EMIT_LOC_STRINGS = NO; 559 | SWIFT_VERSION = 5.0; 560 | TARGETED_DEVICE_FAMILY = "1,2"; 561 | TEST_TARGET_NAME = MorphTargetExample; 562 | }; 563 | name = Debug; 564 | }; 565 | 513DB09F2A79AFD60098D767 /* Release */ = { 566 | isa = XCBuildConfiguration; 567 | buildSettings = { 568 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 569 | CODE_SIGN_STYLE = Automatic; 570 | CURRENT_PROJECT_VERSION = 1; 571 | DEVELOPMENT_TEAM = ST84B2RF4F; 572 | GENERATE_INFOPLIST_FILE = YES; 573 | MARKETING_VERSION = 1.0; 574 | PRODUCT_BUNDLE_IDENTIFIER = SaltPig.MorphTargetExampleUITests; 575 | PRODUCT_NAME = "$(TARGET_NAME)"; 576 | SWIFT_EMIT_LOC_STRINGS = NO; 577 | SWIFT_VERSION = 5.0; 578 | TARGETED_DEVICE_FAMILY = "1,2"; 579 | TEST_TARGET_NAME = MorphTargetExample; 580 | }; 581 | name = Release; 582 | }; 583 | /* End XCBuildConfiguration section */ 584 | 585 | /* Begin XCConfigurationList section */ 586 | 513DB06E2A79AFD30098D767 /* Build configuration list for PBXProject "MorphTargetExample" */ = { 587 | isa = XCConfigurationList; 588 | buildConfigurations = ( 589 | 513DB0952A79AFD60098D767 /* Debug */, 590 | 513DB0962A79AFD60098D767 /* Release */, 591 | ); 592 | defaultConfigurationIsVisible = 0; 593 | defaultConfigurationName = Release; 594 | }; 595 | 513DB0972A79AFD60098D767 /* Build configuration list for PBXNativeTarget "MorphTargetExample" */ = { 596 | isa = XCConfigurationList; 597 | buildConfigurations = ( 598 | 513DB0982A79AFD60098D767 /* Debug */, 599 | 513DB0992A79AFD60098D767 /* Release */, 600 | ); 601 | defaultConfigurationIsVisible = 0; 602 | defaultConfigurationName = Release; 603 | }; 604 | 513DB09A2A79AFD60098D767 /* Build configuration list for PBXNativeTarget "MorphTargetExampleTests" */ = { 605 | isa = XCConfigurationList; 606 | buildConfigurations = ( 607 | 513DB09B2A79AFD60098D767 /* Debug */, 608 | 513DB09C2A79AFD60098D767 /* Release */, 609 | ); 610 | defaultConfigurationIsVisible = 0; 611 | defaultConfigurationName = Release; 612 | }; 613 | 513DB09D2A79AFD60098D767 /* Build configuration list for PBXNativeTarget "MorphTargetExampleUITests" */ = { 614 | isa = XCConfigurationList; 615 | buildConfigurations = ( 616 | 513DB09E2A79AFD60098D767 /* Debug */, 617 | 513DB09F2A79AFD60098D767 /* Release */, 618 | ); 619 | defaultConfigurationIsVisible = 0; 620 | defaultConfigurationName = Release; 621 | }; 622 | /* End XCConfigurationList section */ 623 | 624 | /* Begin XCLocalSwiftPackageReference section */ 625 | 5170B3C12A8A7ACF006B590C /* XCLocalSwiftPackageReference ".." */ = { 626 | isa = XCLocalSwiftPackageReference; 627 | relativePath = ..; 628 | }; 629 | /* End XCLocalSwiftPackageReference section */ 630 | 631 | /* Begin XCSwiftPackageProductDependency section */ 632 | 5170B3C22A8A7ACF006B590C /* RealityMorpher */ = { 633 | isa = XCSwiftPackageProductDependency; 634 | productName = RealityMorpher; 635 | }; 636 | /* End XCSwiftPackageProductDependency section */ 637 | }; 638 | rootObject = 513DB06B2A79AFD30098D767 /* Project object */; 639 | } 640 | -------------------------------------------------------------------------------- /Example/MorphTargetExample.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Example/MorphTargetExample.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Example/MorphTargetExample.xcodeproj/xcshareddata/xcschemes/MorphTargetExample.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 34 | 35 | 36 | 37 | 40 | 46 | 47 | 48 | 51 | 57 | 58 | 59 | 60 | 61 | 74 | 76 | 82 | 83 | 84 | 85 | 91 | 93 | 99 | 100 | 101 | 102 | 104 | 105 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /Example/MorphTargetExample/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Example/MorphTargetExample/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Example/MorphTargetExample/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/MorphTargetExample/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // MorphTargetExample 4 | // 5 | // Created by Oliver Dew on 01/08/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | 12 | @State private var presenter = Presenter() 13 | 14 | var body: some View { 15 | RealityView { scene in 16 | presenter.setup(scene: scene) 17 | } 18 | .onTapGesture { 19 | presenter.onTap() 20 | } 21 | .edgesIgnoringSafeArea(.all) 22 | 23 | } 24 | } 25 | 26 | #Preview { 27 | ContentView() 28 | } 29 | -------------------------------------------------------------------------------- /Example/MorphTargetExample/MorphTargetExampleApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MorphTargetExampleApp.swift 3 | // MorphTargetExample 4 | // 5 | // Created by Oliver Dew on 01/08/2023. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct MorphTargetExampleApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Example/MorphTargetExample/Presenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Presenter.swift 3 | // MorphTargetExample 4 | // 5 | // Created by Oliver Dew on 09/08/2023. 6 | // 7 | 8 | import RealityKit 9 | import SwiftUI 10 | import RealityMorpher 11 | 12 | @Observable 13 | final class Presenter { 14 | private var clothEntity: ModelEntity? 15 | 16 | init() { 17 | MorphComponent.registerComponent() 18 | } 19 | 20 | func setup(scene: RealityKit.Scene) { 21 | guard let model = try? ModelEntity.loadModel(named: "cloth0", in: .main), 22 | let target = try? ModelEntity.loadModel(named: "cloth1", in: .main).model 23 | else { return } 24 | let light = DirectionalLight() 25 | light.look(at: .zero, from: [4, 6, -1], relativeTo: nil) 26 | 27 | let camera = PerspectiveCamera() 28 | camera.look(at: .zero, from: [2, 3, -3], relativeTo: nil) 29 | 30 | let root = AnchorEntity() 31 | root.addChild(model) 32 | root.addChild(light) 33 | root.addChild(camera) 34 | scene.addAnchor(root) 35 | 36 | model.generateCollisionShapes(recursive: true) 37 | model.components.set(Rotatable()) 38 | model.components.set(Draggable()) 39 | model.name = "Cloth" 40 | let morphComponent = try! MorphComponent(entity: model, targets: [target]) 41 | clothEntity = model 42 | model.components.set(morphComponent) 43 | } 44 | 45 | func setupDebug(scene: RealityKit.Scene) { 46 | BillboardSystem.registerSystem() 47 | Billboard.registerComponent() 48 | 49 | setup(scene: scene) 50 | guard let clothEntity, let materials = clothEntity.model?.materials as? [CustomMaterial] else { return } 51 | for (i, material) in materials.enumerated() { 52 | guard let resource = material.custom.texture?.resource else { continue } 53 | let height = Float(resource.height) / 100 54 | var debugMat = UnlitMaterial() 55 | debugMat.color.texture = .init(resource) 56 | let plane = ModelEntity(mesh: .generatePlane(width: Float(resource.width) / 100, height: height), materials: [debugMat]) 57 | plane.components.set(Billboard()) 58 | clothEntity.addChild(plane) 59 | plane.transform.translation = [0, -0.5 + (Float(i) * height), 0] 60 | } 61 | } 62 | 63 | func onTap() { 64 | guard var morphComponent = clothEntity?.components[MorphComponent.self] as? MorphComponent else { return } 65 | let currentWeight = morphComponent.weights[0] 66 | morphComponent.setTargetWeights([1 - currentWeight, 0, 0], animation: .spring(duration: 1, bounce: 0.5)) 67 | clothEntity?.components.set(morphComponent) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Example/MorphTargetExample/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Example/MorphTargetExample/RealityView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RealityView.swift 3 | // MorphTargetExample 4 | // 5 | // Created by Oliver Dew on 02/08/2023. 6 | // 7 | 8 | import SwiftUI 9 | import RealityKit 10 | 11 | struct RealityView: UIViewRepresentable { 12 | 13 | let content: (RealityKit.Scene) -> Void 14 | 15 | func makeUIView(context: Context) -> ARView { 16 | let view = ARView(frame: .zero, cameraMode: .nonAR, automaticallyConfigureSession: false) 17 | Rotatable.registerComponent() 18 | Draggable.registerComponent() 19 | 20 | content(view.scene) 21 | for rotatable in view.scene.performQuery(EntityQuery(where: .has(Rotatable.self) && .has(CollisionComponent.self))) { 22 | view.installGestures(.rotation, for: rotatable as! HasCollision) 23 | } 24 | for draggable in view.scene.performQuery(EntityQuery(where: .has(Draggable.self) && .has(CollisionComponent.self))) { 25 | view.installGestures(.translation, for: draggable as! HasCollision) 26 | } 27 | return view 28 | } 29 | 30 | func updateUIView(_ view: ARView, context: Context) { 31 | } 32 | } 33 | 34 | struct Rotatable: Component {} 35 | struct Draggable: Component {} 36 | struct Billboard: Component {} 37 | 38 | final class BillboardSystem: System { 39 | private var camera: Entity? 40 | 41 | init(scene: RealityKit.Scene) { 42 | 43 | } 44 | 45 | func update(context: SceneUpdateContext) { 46 | if camera == nil { 47 | camera = retrieveCamera(scene: context.scene) 48 | } 49 | guard let camera else { return } 50 | for entity in context.scene.performQuery(EntityQuery(where: .has(Billboard.self))) { 51 | let cameraPosition = entity.convert(position: .zero, from: camera) 52 | entity.look(at: -cameraPosition, from: .zero, relativeTo: entity) 53 | } 54 | } 55 | 56 | private func retrieveCamera(scene: RealityKit.Scene) -> Entity? { 57 | scene.performQuery(EntityQuery(where: .has(PerspectiveCameraComponent.self))).first(where: {_ in true }) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Example/MorphTargetExample/cloth0.usdz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oliver-dew/RealityMorpher/8ee8c7a0d9229b27a163240f45fb5b5fb1fdb817/Example/MorphTargetExample/cloth0.usdz -------------------------------------------------------------------------------- /Example/MorphTargetExample/cloth1.usdz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oliver-dew/RealityMorpher/8ee8c7a0d9229b27a163240f45fb5b5fb1fdb817/Example/MorphTargetExample/cloth1.usdz -------------------------------------------------------------------------------- /Example/MorphTargetExampleTests/MorphTargetExample.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "6733674D-2A8F-40D6-A9B9-7855DDA59C1B", 5 | "name" : "Test Scheme Action", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | "targetForVariableExpansion" : { 13 | "containerPath" : "container:MorphTargetExample.xcodeproj", 14 | "identifier" : "513DB0722A79AFD30098D767", 15 | "name" : "MorphTargetExample" 16 | } 17 | }, 18 | "testTargets" : [ 19 | { 20 | "parallelizable" : true, 21 | "target" : { 22 | "containerPath" : "container:MorphTargetExample.xcodeproj", 23 | "identifier" : "513DB0822A79AFD60098D767", 24 | "name" : "MorphTargetExampleTests" 25 | } 26 | } 27 | ], 28 | "version" : 1 29 | } 30 | -------------------------------------------------------------------------------- /Example/MorphTargetExampleTests/MorphTargetExampleTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MorphTargetExampleTests.swift 3 | // MorphTargetExampleTests 4 | // 5 | // Created by Oliver Dew on 01/08/2023. 6 | // 7 | 8 | import XCTest 9 | @testable import MorphTargetExample 10 | 11 | final class MorphTargetExampleTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | // Any test you write for XCTest can be annotated as throws and async. 25 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. 26 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. 27 | } 28 | 29 | func testPerformanceExample() throws { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Oliver Dew 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "RealityMorpher", 8 | platforms: [.iOS(.v15), .macOS(.v12)], 9 | products: [ 10 | .library( 11 | name: "RealityMorpher", 12 | targets: ["RealityMorpher"]), 13 | ], 14 | targets: [ 15 | .target( 16 | name: "RealityMorpher", dependencies: ["RealityMorpherKernels"] 17 | ), 18 | .target(name: "RealityMorpherKernels"), 19 | .testTarget( 20 | name: "RealityMorpherTests", 21 | dependencies: ["RealityMorpher"]), 22 | ] 23 | ) 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reality Morpher 2 | 3 | Adds Morph Target / Shape Key / Blend Shape animations to RealityKit 4 | 5 | ![Cloth simulation animation](/Example/Cloth-simulation.gif) 6 | 7 | ### Installation 8 | 9 | #### Xcode project 10 | 11 | From the Xcode menu select `File > Add Package Dependencies...` and enter `https://github.com/Utsira/RealityMorpher` 12 | 13 | #### SPM Package 14 | 15 | Add RealityMorpher as a dependency in the `Package.swift` file: 16 | 17 | ```swift 18 | dependencies: [ 19 | .package(url: "https://github.com/Utsira/RealityMorpher.git", .upToNextMajor(from: "0.0.1")) 20 | ] 21 | ``` 22 | 23 | ### Documentation 24 | 25 | From the Xcode menu select `Product > Build Documentation` to view the documentation. 26 | 27 | ### Requirements 28 | 29 | Minimum iOS 15 or macOS 12. 30 | Keyframe animation features require iOS 17 or macOS 14. 31 | Not compatible with visionOS because it uses `CustomMaterial`. 32 | 33 | ### To-do 34 | 35 | - Update boundsMargin on the base ModelComponent 36 | -------------------------------------------------------------------------------- /Sources/RealityMorpher/Documentation.docc/Getting Started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | Follow these steps to get up and running 4 | 5 | ## Overview 6 | 7 | `RealityMorpher` fully integrates with RealityKit's Entity Component System. The core functionality is encapsulated within ``MorphComponent``. After registering the component, you instantiate it with some target geometries and attach it to a `ModelEntity`. Use one of the `setTargetWeights` methods, such as ``MorphComponent/setTargetWeights(_:animation:)``, to animate a transition between the base and one or more of the target geometries. 8 | 9 | ### Registration 10 | 11 | You must call the static registration method ``MorphComponent/registerComponent()`` on ``MorphComponent`` before you start using RealityNorpher. 12 | 13 | ### Adding the Morpher to an Entity 14 | 15 | When you instantiate a ``MorphComponent`` you pass the base Entity you intend to add the component to, as well as an array of the target models: ``MorphComponent/init(entity:targets:weights:options:)``. After creating the component, you must add it to the base entity: 16 | 17 | ```swift 18 | MorphComponent.registerComponent() 19 | do { 20 | let model = try ModelEntity.loadModel(named: "rock"), 21 | let target = try ModelEntity.loadModel(named: "rock_shattered").model 22 | let morphComponent = try MorphComponent(entity: model, targets: [target].compactMap { $0 }) 23 | model.components.set(morphComponent) 24 | } catch { 25 | // handle errors 26 | } 27 | ``` 28 | 29 | ### Blending between Morph Targets 30 | 31 | Animate between the different Morph Targets by assigning a weight for each target. Up to 4 weights are passed in a ``MorphWeights`` object. A weight of 0 means the corresponding target has no influence at all, while a weight of 1.0 means it is fully applied. 32 | - ``MorphComponent/setTargetWeights(_:animation:)`` 33 | 34 | ### Limitations 35 | 36 | - A maximum of 4 morph targets can be added to any Entity 37 | - The morph targets must all be topologically identical to the base model (in other words have the same number of submodels, parts, and vertices) 38 | - The 4 targets cannot have more than 33,554,432 vertices combined -------------------------------------------------------------------------------- /Sources/RealityMorpher/Documentation.docc/RealityMorpher.md: -------------------------------------------------------------------------------- 1 | # ``RealityMorpher`` 2 | 3 | Animate smooth transitions between an Entity's ModelComponent and up to four target geometries 4 | 5 | ## Overview 6 | 7 | Manage these geometry transitions by attaching a ``MorphComponent`` to a `ModelEntity`. The morpher maintains a set of up to four target geometries and associated weights. When all the weights are zero, the Entity takes the form of its base mesh, supplied by its `ModelComponent`. When you use the ``MorphComponent/setTargetWeights(_:animation:)`` method to increase one or more of the weights to 1.0, the Entity takes on the form of the corresponding target geometry, smoothly interpolating each of its positions and normals between the base and the target. If you use a variety of weight values for several targets, the surface takes a form that proportionally interpolates between the target geometries. 8 | 9 | The base geometry and all target geometries must be topologically identical—that is, they must contain the same number and structural arrangement of vertices. 10 | 11 | ## Topics 12 | 13 | ### Essentials 14 | 15 | - ``MorphComponent`` 16 | 17 | ### Animating transitions 18 | 19 | - ``MorphWeights`` 20 | - ``MorphAnimation`` 21 | -------------------------------------------------------------------------------- /Sources/RealityMorpher/MorphAnimating.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MorphAnimating.swift 3 | // 4 | // 5 | // Created by Oliver Dew on 11/08/2023. 6 | // 7 | 8 | import simd 9 | import SwiftUI 10 | 11 | protocol MorphAnimating { 12 | mutating func update(with deltaTime: TimeInterval) -> MorphEvent 13 | } 14 | 15 | struct MorphEvent { 16 | enum Status { 17 | case running, completed 18 | } 19 | let status: Status 20 | let weights: MorphWeights 21 | } 22 | 23 | @available(iOS 17.0, macOS 14.0, *) 24 | struct TimelineAnimator: MorphAnimating { 25 | let timeline: KeyframeTimeline 26 | private var timeElapsed: TimeInterval = .zero 27 | 28 | init(origin: MorphWeights, target: MorphWeights, animation: MorphAnimation) { 29 | timeline = KeyframeTimeline(initialValue: origin) { 30 | switch animation { 31 | case .linear(let duration): 32 | LinearKeyframe(target, duration: duration) 33 | case .cubic(let duration): 34 | CubicKeyframe(target, duration: duration) 35 | case let .spring(duration, bounce): 36 | SpringKeyframe(target, spring: Spring(duration: duration, bounce: bounce)) 37 | } 38 | } 39 | } 40 | 41 | init(origin: MorphWeights, @KeyframesBuilder animations: () -> some Keyframes) { 42 | timeline = KeyframeTimeline(initialValue: origin, content: animations) 43 | } 44 | 45 | mutating func update(with deltaTime: TimeInterval) -> MorphEvent { 46 | if timeElapsed > timeline.duration { 47 | return MorphEvent(status: .completed, weights: timeline.value(progress: 1)) 48 | } 49 | timeElapsed += deltaTime 50 | let value = timeline.value(time: timeElapsed) 51 | return MorphEvent(status: .running, weights: value) 52 | } 53 | } 54 | 55 | struct LinearAnimator: MorphAnimating { 56 | private var timeElapsed: TimeInterval = .zero 57 | private let origin: MorphWeights 58 | private let target: MorphWeights 59 | private let duration: TimeInterval 60 | 61 | init(origin: MorphWeights, target: MorphWeights, duration: TimeInterval) { 62 | self.origin = origin 63 | self.target = target 64 | self.duration = duration 65 | } 66 | 67 | mutating func update(with deltaTime: TimeInterval) -> MorphEvent { 68 | if timeElapsed > duration { 69 | return MorphEvent(status: .completed, weights: target) 70 | } else if duration == .zero { 71 | timeElapsed += deltaTime 72 | return MorphEvent(status: .running, weights: target) 73 | } 74 | timeElapsed += deltaTime 75 | let value = mix(origin.values, target.values, t: Float(timeElapsed / duration)) 76 | return MorphEvent(status: .running, weights: MorphWeights(values: value)) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Sources/RealityMorpher/MorphAnimation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MorphAnimation.swift 3 | // 4 | // 5 | // Created by Oliver Dew on 11/08/2023. 6 | // 7 | 8 | import Foundation 9 | 10 | /// Describes how a change in morph target weighting should be animated 11 | @available(iOS 17.0, macOS 14.0, *) 12 | public enum MorphAnimation { 13 | case linear(duration: TimeInterval) 14 | case cubic(duration: TimeInterval) 15 | 16 | /// - Parameters: 17 | /// - duration: duration of animation in seconds 18 | /// - bounce: The bounciness of the spring. 0 is fully damped, 1 is not damped at all. High bounciness will increase the duration of the transition 19 | case spring(duration: TimeInterval, bounce: Double = 0.2) 20 | 21 | /// Change in weights is applied immediately with no animation 22 | public static var noAnimation: MorphAnimation { .linear(duration: 0) } 23 | 24 | var duration: TimeInterval { 25 | switch self { 26 | case .linear(let duration), .cubic(let duration), .spring(let duration, _): duration 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Sources/RealityMorpher/MorphComponent.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import RealityKit 3 | import CoreGraphics 4 | import RealityMorpherKernels 5 | import Accelerate 6 | import SwiftUI 7 | 8 | /// Add this component to a `ModelEntity` to enable morph target (AKA shape key or blend shape) animations. 9 | public struct MorphComponent: Component { 10 | /// Debug options 11 | public enum Option: String { 12 | /// Display normals as vertex colors 13 | case debugNormals 14 | } 15 | 16 | /// The weights for each of the targets, not accounting for any animations that are in flight. 17 | /// 18 | /// When you set a desired weight using ``setTargetWeights(_:animation:)``, this ``weights`` parameter will immediately reflect that change, regardless of what animation duration has been set 19 | public private(set) var weights: MorphWeights 20 | 21 | /// We need to keep a reference to the texture resources we create, otherwise the custom textures get nilled when they update 22 | let textureResources: [TextureResource] 23 | 24 | private(set) var currentWeights: SIMD4 25 | private var animator: MorphAnimating? 26 | private static let maxTextureWidth = 8192 27 | 28 | /// Initialises a new MorphComponent for animating deforms to a model's geometry. 29 | /// 30 | /// - Parameters: 31 | /// - entity: the `ModelEntity` that this component will be added to. This entity's materials will all be converted into `CustomMaterial`s in order to deform the geometry 32 | /// - targets: an array of target geometries that can be morphed to. There must be between 1 and 4 geometries in this array. Each geometry must be topologically identical to the base entity's model (in other words have the same number of submodels, composed of the same number of parts, each of which must have the same number of vertices) 33 | /// - weights: a collection of weights describing the extent to which each target in the `targets` parameter should be applied. Typically these are in the range 0 to 1, 0 indicating the target is not applied at all, 1 indicating it is fully applied. Each element corresponds to the element at the same index in the `targets` property. Defaults to zero. 34 | /// - options: a set of ``Option`` flags that can be passed, Defaults to an empty set. 35 | /// 36 | /// - Throws: See ``Error`` for errors thrown from this initialiser 37 | public init(entity: HasModel, targets: [ModelComponent], weights: MorphWeights = .zero, options: Set