├── .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 | 
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