├── README.md ├── Swift3D XcodeProj ├── .DS_Store ├── blank.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcuserdata │ │ │ ├── christian.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ │ │ └── privitec.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ └── xcuserdata │ │ ├── christian.xcuserdatad │ │ └── xcschemes │ │ │ └── xcschememanagement.plist │ │ └── privitec.xcuserdatad │ │ ├── xcdebugger │ │ └── Breakpoints_v2.xcbkptlist │ │ └── xcschemes │ │ └── xcschememanagement.plist └── blank │ ├── .DS_Store │ ├── Assets.xcassets │ ├── .DS_Store │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── background.imageset │ │ ├── Artboard Copy 3.png │ │ └── Contents.json │ ├── horizon.imageset │ │ ├── An_orbital_sunrise_pillars.jpg │ │ └── Contents.json │ └── meshGradientBackground.imageset │ │ ├── Contents.json │ │ └── meshGradientBackground.png │ ├── ContentView.swift │ ├── Info.plist │ ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json │ ├── Swift3D │ ├── .DS_Store │ ├── ARScene.swift │ ├── Animation3D.swift │ ├── Box.swift │ ├── Capsule.swift │ ├── Cone.swift │ ├── Cylinder.swift │ ├── DynamicProperty3D.swift │ ├── EmptyObject.swift │ ├── Helpers.swift │ ├── Object.swift │ ├── ObjectAttributes.swift │ ├── ObjectBuilder.swift │ ├── Plane.swift │ ├── Pyramid.swift │ ├── Scene3D.swift │ ├── Scene3DProtocol.swift │ ├── Sphere.swift │ ├── Stack.swift │ ├── State3D.swift │ ├── Torus.swift │ ├── Tube.swift │ └── ViewPlane.swift │ └── blankApp.swift └── Swift3D.playgroundbook └── Contents ├── Chapters ├── Conclusion.playgroundchapter │ ├── Manifest.plist │ └── Pages │ │ ├── 1HowItWorks.playgroundpage │ │ ├── LiveView.swift │ │ ├── Manifest.plist │ │ └── main.swift │ │ └── 2Conclusion.playgroundpage │ │ ├── Manifest.plist │ │ └── main.swift ├── Creating Something.playgroundchapter │ ├── Manifest.plist │ └── Pages │ │ ├── 1AnExample.playgroundpage │ │ ├── Manifest.plist │ │ └── main.swift │ │ └── 2TryItYourself.playgroundpage │ │ ├── Manifest.plist │ │ └── main.swift ├── Introduction.playgroundchapter │ ├── Manifest.plist │ └── Pages │ │ └── Introduction.playgroundpage │ │ ├── LiveView.swift │ │ ├── Manifest.plist │ │ └── main.swift └── The Basics.playgroundchapter │ ├── Manifest.plist │ └── Pages │ ├── 1MyFirstObject.playgroundpage │ ├── Manifest.plist │ ├── PrivateResources │ │ ├── Hint1.txt │ │ ├── Hint2.txt │ │ ├── Hint3.txt │ │ ├── Hint4.txt │ │ └── Hints.plist │ └── main.swift │ ├── 2Modifiers.playgroundpage │ ├── Manifest.plist │ └── main.swift │ ├── 3Stacks.playgroundpage │ ├── Manifest.plist │ └── main.swift │ ├── 4AnimatingObjects.playgroundpage │ ├── Manifest.plist │ ├── PrivateResources │ │ ├── Hint1.txt │ │ ├── Hint2.txt │ │ └── Hints.plist │ └── main.swift │ ├── 5Bringing2DInto3D.playgroundpage │ ├── Manifest.plist │ └── main.swift │ └── 6IntegrateWithSwiftUI.playgroundpage │ ├── Manifest.plist │ └── main.swift ├── Manifest.plist ├── PublicResources ├── iconnew.png ├── iconold.png ├── logo.png ├── mainSceneBackground.png ├── meshGradientBackground.png └── protocolDesign.png └── UserModules ├── Page1.playgroundmodule └── Sources │ ├── DefaultLiveView.swift │ ├── OrbThingy.swift │ ├── SquareLogo.swift │ ├── View+Foreground.swift │ └── View+InnerShadow.swift └── Swift3D.playgroundmodule └── Sources ├── ARScene.swift ├── Animation3D.swift ├── Box.swift ├── Capsule.swift ├── Cone.swift ├── Cylinder.swift ├── DynamicProperty3D.swift ├── EmptyObject.swift ├── Helpers.swift ├── Object.swift ├── ObjectAttributes.swift ├── ObjectBuilder.swift ├── Plane.swift ├── Pyramid.swift ├── Scene3D.swift ├── Scene3DProtocol.swift ├── Sphere.swift ├── Stack.swift ├── State3D.swift ├── Torus.swift ├── Tube.swift └── ViewPlane.swift /README.md: -------------------------------------------------------------------------------- 1 | # Swift3D 2 | 3 | ### A 3D framework for everyone. 4 | 5 | I love SwiftUI. I have been using it constantly since it came out in 2019 and its power and ease of use are what ignited my passion for Swift development. 6 | 7 | Creating reusable views with simple declarations meant anyone could quickly learn how to make anything from a super simple app with text that says “Hello World!” to a fully-featured app. 8 | 9 | 2D is great, but in the future, there will need to be something equivalent to SwiftUI that will let us create 3D applications just as easily for use on either a 2D screen or to bring into your world with AR. I set a goal to try and create a concept of what this new framework could look like and how the features and power of the Swift language could make this possible. The perfect place to do this and present my findings is in a Swift Playground! 10 | 11 | This playground will walk you through the main concepts of Swift3D and teach you how to create your own custom 3D experiences. I hope you enjoy it! 12 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Priva28/Swift3D/6a8f89c24d8d25aabe52b780306bedde965c7e8b/Swift3D XcodeProj/.DS_Store -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 302142E926273559006955A5 /* Plane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302142E826273559006955A5 /* Plane.swift */; }; 11 | 302142EB26274655006955A5 /* Animation3D.swift in Sources */ = {isa = PBXBuildFile; fileRef = 302142EA26274655006955A5 /* Animation3D.swift */; }; 12 | 3037A74A262875A3007D190F /* ViewPlane.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3037A749262875A3007D190F /* ViewPlane.swift */; }; 13 | 3037A7512629E202007D190F /* Scene3DProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3037A7502629E202007D190F /* Scene3DProtocol.swift */; }; 14 | 309EAAD02621EB2A009972FE /* blankApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 309EAACF2621EB2A009972FE /* blankApp.swift */; }; 15 | 309EAAD22621EB2A009972FE /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 309EAAD12621EB2A009972FE /* ContentView.swift */; }; 16 | 309EAAD42621EB2D009972FE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 309EAAD32621EB2D009972FE /* Assets.xcassets */; }; 17 | 309EAAD72621EB2D009972FE /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 309EAAD62621EB2D009972FE /* Preview Assets.xcassets */; }; 18 | 309EAAEB2621EB4A009972FE /* Scene3D.swift in Sources */ = {isa = PBXBuildFile; fileRef = 309EAADE2621EB49009972FE /* Scene3D.swift */; }; 19 | 309EAAEC2621EB4A009972FE /* Pyramid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 309EAADF2621EB49009972FE /* Pyramid.swift */; }; 20 | 309EAAED2621EB4A009972FE /* ObjectAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 309EAAE02621EB49009972FE /* ObjectAttributes.swift */; }; 21 | 309EAAEF2621EB4A009972FE /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = 309EAAE22621EB4A009972FE /* Box.swift */; }; 22 | 309EAAF02621EB4A009972FE /* DynamicProperty3D.swift in Sources */ = {isa = PBXBuildFile; fileRef = 309EAAE32621EB4A009972FE /* DynamicProperty3D.swift */; }; 23 | 309EAAF12621EB4A009972FE /* Object.swift in Sources */ = {isa = PBXBuildFile; fileRef = 309EAAE42621EB4A009972FE /* Object.swift */; }; 24 | 309EAAF22621EB4A009972FE /* State3D.swift in Sources */ = {isa = PBXBuildFile; fileRef = 309EAAE52621EB4A009972FE /* State3D.swift */; }; 25 | 309EAAF32621EB4A009972FE /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 309EAAE62621EB4A009972FE /* Helpers.swift */; }; 26 | 309EAAF42621EB4B009972FE /* Stack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 309EAAE72621EB4A009972FE /* Stack.swift */; }; 27 | 309EAAF52621EB4B009972FE /* Sphere.swift in Sources */ = {isa = PBXBuildFile; fileRef = 309EAAE82621EB4A009972FE /* Sphere.swift */; }; 28 | 309EAAF62621EB4B009972FE /* ObjectBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 309EAAE92621EB4A009972FE /* ObjectBuilder.swift */; }; 29 | 309EAAF72621EB4B009972FE /* EmptyObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 309EAAEA2621EB4A009972FE /* EmptyObject.swift */; }; 30 | 30DC42F62629EFAA00758023 /* Capsule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC42F52629EFAA00758023 /* Capsule.swift */; }; 31 | 30DC42F82629F05100758023 /* Cone.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC42F72629F05100758023 /* Cone.swift */; }; 32 | 30DC42FA2629F0FA00758023 /* Cylinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC42F92629F0FA00758023 /* Cylinder.swift */; }; 33 | 30DC42FC2629F15500758023 /* Torus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC42FB2629F15500758023 /* Torus.swift */; }; 34 | 30DC42FE2629F1B900758023 /* Tube.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30DC42FD2629F1B900758023 /* Tube.swift */; }; 35 | AA55920726299FBA0051205C /* ARScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA55920626299FBA0051205C /* ARScene.swift */; }; 36 | /* End PBXBuildFile section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 302142E826273559006955A5 /* Plane.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Plane.swift; sourceTree = ""; }; 40 | 302142EA26274655006955A5 /* Animation3D.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Animation3D.swift; sourceTree = ""; }; 41 | 3037A749262875A3007D190F /* ViewPlane.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewPlane.swift; sourceTree = ""; }; 42 | 3037A7502629E202007D190F /* Scene3DProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Scene3DProtocol.swift; sourceTree = ""; }; 43 | 309EAACC2621EB2A009972FE /* blank.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = blank.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 309EAACF2621EB2A009972FE /* blankApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = blankApp.swift; sourceTree = ""; }; 45 | 309EAAD12621EB2A009972FE /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 46 | 309EAAD32621EB2D009972FE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 47 | 309EAAD62621EB2D009972FE /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 48 | 309EAAD82621EB2D009972FE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | 309EAADE2621EB49009972FE /* Scene3D.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scene3D.swift; sourceTree = ""; }; 50 | 309EAADF2621EB49009972FE /* Pyramid.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pyramid.swift; sourceTree = ""; }; 51 | 309EAAE02621EB49009972FE /* ObjectAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectAttributes.swift; sourceTree = ""; }; 52 | 309EAAE22621EB4A009972FE /* Box.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = ""; }; 53 | 309EAAE32621EB4A009972FE /* DynamicProperty3D.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicProperty3D.swift; sourceTree = ""; }; 54 | 309EAAE42621EB4A009972FE /* Object.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Object.swift; sourceTree = ""; }; 55 | 309EAAE52621EB4A009972FE /* State3D.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = State3D.swift; sourceTree = ""; }; 56 | 309EAAE62621EB4A009972FE /* Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; 57 | 309EAAE72621EB4A009972FE /* Stack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stack.swift; sourceTree = ""; }; 58 | 309EAAE82621EB4A009972FE /* Sphere.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Sphere.swift; sourceTree = ""; }; 59 | 309EAAE92621EB4A009972FE /* ObjectBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectBuilder.swift; sourceTree = ""; }; 60 | 309EAAEA2621EB4A009972FE /* EmptyObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyObject.swift; sourceTree = ""; }; 61 | 30DC42F52629EFAA00758023 /* Capsule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Capsule.swift; sourceTree = ""; }; 62 | 30DC42F72629F05100758023 /* Cone.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cone.swift; sourceTree = ""; }; 63 | 30DC42F92629F0FA00758023 /* Cylinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cylinder.swift; sourceTree = ""; }; 64 | 30DC42FB2629F15500758023 /* Torus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Torus.swift; sourceTree = ""; }; 65 | 30DC42FD2629F1B900758023 /* Tube.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tube.swift; sourceTree = ""; }; 66 | AA55920626299FBA0051205C /* ARScene.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ARScene.swift; sourceTree = ""; }; 67 | /* End PBXFileReference section */ 68 | 69 | /* Begin PBXFrameworksBuildPhase section */ 70 | 309EAAC92621EB2A009972FE /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | ); 75 | runOnlyForDeploymentPostprocessing = 0; 76 | }; 77 | /* End PBXFrameworksBuildPhase section */ 78 | 79 | /* Begin PBXGroup section */ 80 | 309EAAC32621EB2A009972FE = { 81 | isa = PBXGroup; 82 | children = ( 83 | 309EAACE2621EB2A009972FE /* blank */, 84 | 309EAACD2621EB2A009972FE /* Products */, 85 | ); 86 | sourceTree = ""; 87 | }; 88 | 309EAACD2621EB2A009972FE /* Products */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 309EAACC2621EB2A009972FE /* blank.app */, 92 | ); 93 | name = Products; 94 | sourceTree = ""; 95 | }; 96 | 309EAACE2621EB2A009972FE /* blank */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 309EAACF2621EB2A009972FE /* blankApp.swift */, 100 | 309EAAD12621EB2A009972FE /* ContentView.swift */, 101 | 309EAAD32621EB2D009972FE /* Assets.xcassets */, 102 | 309EAAD82621EB2D009972FE /* Info.plist */, 103 | 309EAAF92621EB56009972FE /* Swift3D */, 104 | 309EAAD52621EB2D009972FE /* Preview Content */, 105 | ); 106 | path = blank; 107 | sourceTree = ""; 108 | }; 109 | 309EAAD52621EB2D009972FE /* Preview Content */ = { 110 | isa = PBXGroup; 111 | children = ( 112 | 309EAAD62621EB2D009972FE /* Preview Assets.xcassets */, 113 | ); 114 | path = "Preview Content"; 115 | sourceTree = ""; 116 | }; 117 | 309EAAF92621EB56009972FE /* Swift3D */ = { 118 | isa = PBXGroup; 119 | children = ( 120 | 309EAAE42621EB4A009972FE /* Object.swift */, 121 | 309EAAE92621EB4A009972FE /* ObjectBuilder.swift */, 122 | 309EAAE02621EB49009972FE /* ObjectAttributes.swift */, 123 | 302142EA26274655006955A5 /* Animation3D.swift */, 124 | AA55920626299FBA0051205C /* ARScene.swift */, 125 | 309EAADE2621EB49009972FE /* Scene3D.swift */, 126 | 3037A7502629E202007D190F /* Scene3DProtocol.swift */, 127 | 309EAAE22621EB4A009972FE /* Box.swift */, 128 | 309EAAE82621EB4A009972FE /* Sphere.swift */, 129 | 309EAADF2621EB49009972FE /* Pyramid.swift */, 130 | 30DC42F52629EFAA00758023 /* Capsule.swift */, 131 | 30DC42F72629F05100758023 /* Cone.swift */, 132 | 30DC42F92629F0FA00758023 /* Cylinder.swift */, 133 | 30DC42FB2629F15500758023 /* Torus.swift */, 134 | 30DC42FD2629F1B900758023 /* Tube.swift */, 135 | 309EAAE72621EB4A009972FE /* Stack.swift */, 136 | 302142E826273559006955A5 /* Plane.swift */, 137 | 3037A749262875A3007D190F /* ViewPlane.swift */, 138 | 309EAAEA2621EB4A009972FE /* EmptyObject.swift */, 139 | 309EAAE32621EB4A009972FE /* DynamicProperty3D.swift */, 140 | 309EAAE52621EB4A009972FE /* State3D.swift */, 141 | 309EAAE62621EB4A009972FE /* Helpers.swift */, 142 | ); 143 | path = Swift3D; 144 | sourceTree = ""; 145 | }; 146 | /* End PBXGroup section */ 147 | 148 | /* Begin PBXNativeTarget section */ 149 | 309EAACB2621EB2A009972FE /* blank */ = { 150 | isa = PBXNativeTarget; 151 | buildConfigurationList = 309EAADB2621EB2D009972FE /* Build configuration list for PBXNativeTarget "blank" */; 152 | buildPhases = ( 153 | 309EAAC82621EB2A009972FE /* Sources */, 154 | 309EAAC92621EB2A009972FE /* Frameworks */, 155 | 309EAACA2621EB2A009972FE /* Resources */, 156 | ); 157 | buildRules = ( 158 | ); 159 | dependencies = ( 160 | ); 161 | name = blank; 162 | productName = blank; 163 | productReference = 309EAACC2621EB2A009972FE /* blank.app */; 164 | productType = "com.apple.product-type.application"; 165 | }; 166 | /* End PBXNativeTarget section */ 167 | 168 | /* Begin PBXProject section */ 169 | 309EAAC42621EB2A009972FE /* Project object */ = { 170 | isa = PBXProject; 171 | attributes = { 172 | LastSwiftUpdateCheck = 1250; 173 | LastUpgradeCheck = 1250; 174 | TargetAttributes = { 175 | 309EAACB2621EB2A009972FE = { 176 | CreatedOnToolsVersion = 12.5; 177 | }; 178 | }; 179 | }; 180 | buildConfigurationList = 309EAAC72621EB2A009972FE /* Build configuration list for PBXProject "blank" */; 181 | compatibilityVersion = "Xcode 9.3"; 182 | developmentRegion = en; 183 | hasScannedForEncodings = 0; 184 | knownRegions = ( 185 | en, 186 | Base, 187 | ); 188 | mainGroup = 309EAAC32621EB2A009972FE; 189 | productRefGroup = 309EAACD2621EB2A009972FE /* Products */; 190 | projectDirPath = ""; 191 | projectRoot = ""; 192 | targets = ( 193 | 309EAACB2621EB2A009972FE /* blank */, 194 | ); 195 | }; 196 | /* End PBXProject section */ 197 | 198 | /* Begin PBXResourcesBuildPhase section */ 199 | 309EAACA2621EB2A009972FE /* Resources */ = { 200 | isa = PBXResourcesBuildPhase; 201 | buildActionMask = 2147483647; 202 | files = ( 203 | 309EAAD72621EB2D009972FE /* Preview Assets.xcassets in Resources */, 204 | 309EAAD42621EB2D009972FE /* Assets.xcassets in Resources */, 205 | ); 206 | runOnlyForDeploymentPostprocessing = 0; 207 | }; 208 | /* End PBXResourcesBuildPhase section */ 209 | 210 | /* Begin PBXSourcesBuildPhase section */ 211 | 309EAAC82621EB2A009972FE /* Sources */ = { 212 | isa = PBXSourcesBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | 30DC42F82629F05100758023 /* Cone.swift in Sources */, 216 | 309EAAF62621EB4B009972FE /* ObjectBuilder.swift in Sources */, 217 | 30DC42F62629EFAA00758023 /* Capsule.swift in Sources */, 218 | 309EAAF72621EB4B009972FE /* EmptyObject.swift in Sources */, 219 | 309EAAF22621EB4A009972FE /* State3D.swift in Sources */, 220 | 30DC42FE2629F1B900758023 /* Tube.swift in Sources */, 221 | 302142EB26274655006955A5 /* Animation3D.swift in Sources */, 222 | 309EAAD22621EB2A009972FE /* ContentView.swift in Sources */, 223 | AA55920726299FBA0051205C /* ARScene.swift in Sources */, 224 | 309EAAEF2621EB4A009972FE /* Box.swift in Sources */, 225 | 3037A7512629E202007D190F /* Scene3DProtocol.swift in Sources */, 226 | 309EAAF42621EB4B009972FE /* Stack.swift in Sources */, 227 | 309EAAF32621EB4A009972FE /* Helpers.swift in Sources */, 228 | 302142E926273559006955A5 /* Plane.swift in Sources */, 229 | 309EAAF02621EB4A009972FE /* DynamicProperty3D.swift in Sources */, 230 | 30DC42FC2629F15500758023 /* Torus.swift in Sources */, 231 | 309EAAF12621EB4A009972FE /* Object.swift in Sources */, 232 | 309EAAF52621EB4B009972FE /* Sphere.swift in Sources */, 233 | 309EAAD02621EB2A009972FE /* blankApp.swift in Sources */, 234 | 309EAAEC2621EB4A009972FE /* Pyramid.swift in Sources */, 235 | 3037A74A262875A3007D190F /* ViewPlane.swift in Sources */, 236 | 30DC42FA2629F0FA00758023 /* Cylinder.swift in Sources */, 237 | 309EAAEB2621EB4A009972FE /* Scene3D.swift in Sources */, 238 | 309EAAED2621EB4A009972FE /* ObjectAttributes.swift in Sources */, 239 | ); 240 | runOnlyForDeploymentPostprocessing = 0; 241 | }; 242 | /* End PBXSourcesBuildPhase section */ 243 | 244 | /* Begin XCBuildConfiguration section */ 245 | 309EAAD92621EB2D009972FE /* Debug */ = { 246 | isa = XCBuildConfiguration; 247 | buildSettings = { 248 | ALWAYS_SEARCH_USER_PATHS = NO; 249 | CLANG_ANALYZER_NONNULL = YES; 250 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 251 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 252 | CLANG_CXX_LIBRARY = "libc++"; 253 | CLANG_ENABLE_MODULES = YES; 254 | CLANG_ENABLE_OBJC_ARC = YES; 255 | CLANG_ENABLE_OBJC_WEAK = YES; 256 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 257 | CLANG_WARN_BOOL_CONVERSION = YES; 258 | CLANG_WARN_COMMA = YES; 259 | CLANG_WARN_CONSTANT_CONVERSION = YES; 260 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 261 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 262 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 263 | CLANG_WARN_EMPTY_BODY = YES; 264 | CLANG_WARN_ENUM_CONVERSION = YES; 265 | CLANG_WARN_INFINITE_RECURSION = YES; 266 | CLANG_WARN_INT_CONVERSION = YES; 267 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 268 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 269 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 270 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 271 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 272 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 273 | CLANG_WARN_STRICT_PROTOTYPES = YES; 274 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 275 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 276 | CLANG_WARN_UNREACHABLE_CODE = YES; 277 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 278 | COPY_PHASE_STRIP = NO; 279 | DEBUG_INFORMATION_FORMAT = dwarf; 280 | ENABLE_STRICT_OBJC_MSGSEND = YES; 281 | ENABLE_TESTABILITY = YES; 282 | GCC_C_LANGUAGE_STANDARD = gnu11; 283 | GCC_DYNAMIC_NO_PIC = NO; 284 | GCC_NO_COMMON_BLOCKS = YES; 285 | GCC_OPTIMIZATION_LEVEL = 0; 286 | GCC_PREPROCESSOR_DEFINITIONS = ( 287 | "DEBUG=1", 288 | "$(inherited)", 289 | ); 290 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 291 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 292 | GCC_WARN_UNDECLARED_SELECTOR = YES; 293 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 294 | GCC_WARN_UNUSED_FUNCTION = YES; 295 | GCC_WARN_UNUSED_VARIABLE = YES; 296 | IPHONEOS_DEPLOYMENT_TARGET = 14.5; 297 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 298 | MTL_FAST_MATH = YES; 299 | ONLY_ACTIVE_ARCH = YES; 300 | SDKROOT = iphoneos; 301 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 302 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 303 | }; 304 | name = Debug; 305 | }; 306 | 309EAADA2621EB2D009972FE /* Release */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | ALWAYS_SEARCH_USER_PATHS = NO; 310 | CLANG_ANALYZER_NONNULL = YES; 311 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 312 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 313 | CLANG_CXX_LIBRARY = "libc++"; 314 | CLANG_ENABLE_MODULES = YES; 315 | CLANG_ENABLE_OBJC_ARC = YES; 316 | CLANG_ENABLE_OBJC_WEAK = YES; 317 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 318 | CLANG_WARN_BOOL_CONVERSION = YES; 319 | CLANG_WARN_COMMA = YES; 320 | CLANG_WARN_CONSTANT_CONVERSION = YES; 321 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 322 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 323 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 324 | CLANG_WARN_EMPTY_BODY = YES; 325 | CLANG_WARN_ENUM_CONVERSION = YES; 326 | CLANG_WARN_INFINITE_RECURSION = YES; 327 | CLANG_WARN_INT_CONVERSION = YES; 328 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 329 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 330 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 331 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 332 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 333 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 334 | CLANG_WARN_STRICT_PROTOTYPES = YES; 335 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 336 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 337 | CLANG_WARN_UNREACHABLE_CODE = YES; 338 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 339 | COPY_PHASE_STRIP = NO; 340 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 341 | ENABLE_NS_ASSERTIONS = NO; 342 | ENABLE_STRICT_OBJC_MSGSEND = YES; 343 | GCC_C_LANGUAGE_STANDARD = gnu11; 344 | GCC_NO_COMMON_BLOCKS = YES; 345 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 346 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 347 | GCC_WARN_UNDECLARED_SELECTOR = YES; 348 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 349 | GCC_WARN_UNUSED_FUNCTION = YES; 350 | GCC_WARN_UNUSED_VARIABLE = YES; 351 | IPHONEOS_DEPLOYMENT_TARGET = 14.5; 352 | MTL_ENABLE_DEBUG_INFO = NO; 353 | MTL_FAST_MATH = YES; 354 | SDKROOT = iphoneos; 355 | SWIFT_COMPILATION_MODE = wholemodule; 356 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 357 | VALIDATE_PRODUCT = YES; 358 | }; 359 | name = Release; 360 | }; 361 | 309EAADC2621EB2D009972FE /* Debug */ = { 362 | isa = XCBuildConfiguration; 363 | buildSettings = { 364 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 365 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 366 | CODE_SIGN_STYLE = Automatic; 367 | DEVELOPMENT_ASSET_PATHS = "\"blank/Preview Content\""; 368 | DEVELOPMENT_TEAM = TW2C8488Q9; 369 | ENABLE_PREVIEWS = YES; 370 | INFOPLIST_FILE = blank/Info.plist; 371 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 372 | LD_RUNPATH_SEARCH_PATHS = ( 373 | "$(inherited)", 374 | "@executable_path/Frameworks", 375 | ); 376 | PRODUCT_BUNDLE_IDENTIFIER = com.priva28.blank; 377 | PRODUCT_NAME = "$(TARGET_NAME)"; 378 | SWIFT_VERSION = 5.0; 379 | TARGETED_DEVICE_FAMILY = "1,2"; 380 | }; 381 | name = Debug; 382 | }; 383 | 309EAADD2621EB2D009972FE /* Release */ = { 384 | isa = XCBuildConfiguration; 385 | buildSettings = { 386 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 387 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 388 | CODE_SIGN_STYLE = Automatic; 389 | DEVELOPMENT_ASSET_PATHS = "\"blank/Preview Content\""; 390 | DEVELOPMENT_TEAM = TW2C8488Q9; 391 | ENABLE_PREVIEWS = YES; 392 | INFOPLIST_FILE = blank/Info.plist; 393 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 394 | LD_RUNPATH_SEARCH_PATHS = ( 395 | "$(inherited)", 396 | "@executable_path/Frameworks", 397 | ); 398 | PRODUCT_BUNDLE_IDENTIFIER = com.priva28.blank; 399 | PRODUCT_NAME = "$(TARGET_NAME)"; 400 | SWIFT_VERSION = 5.0; 401 | TARGETED_DEVICE_FAMILY = "1,2"; 402 | }; 403 | name = Release; 404 | }; 405 | /* End XCBuildConfiguration section */ 406 | 407 | /* Begin XCConfigurationList section */ 408 | 309EAAC72621EB2A009972FE /* Build configuration list for PBXProject "blank" */ = { 409 | isa = XCConfigurationList; 410 | buildConfigurations = ( 411 | 309EAAD92621EB2D009972FE /* Debug */, 412 | 309EAADA2621EB2D009972FE /* Release */, 413 | ); 414 | defaultConfigurationIsVisible = 0; 415 | defaultConfigurationName = Release; 416 | }; 417 | 309EAADB2621EB2D009972FE /* Build configuration list for PBXNativeTarget "blank" */ = { 418 | isa = XCConfigurationList; 419 | buildConfigurations = ( 420 | 309EAADC2621EB2D009972FE /* Debug */, 421 | 309EAADD2621EB2D009972FE /* Release */, 422 | ); 423 | defaultConfigurationIsVisible = 0; 424 | defaultConfigurationName = Release; 425 | }; 426 | /* End XCConfigurationList section */ 427 | }; 428 | rootObject = 309EAAC42621EB2A009972FE /* Project object */; 429 | } 430 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank.xcodeproj/project.xcworkspace/xcuserdata/christian.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Priva28/Swift3D/6a8f89c24d8d25aabe52b780306bedde965c7e8b/Swift3D XcodeProj/blank.xcodeproj/project.xcworkspace/xcuserdata/christian.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank.xcodeproj/project.xcworkspace/xcuserdata/privitec.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Priva28/Swift3D/6a8f89c24d8d25aabe52b780306bedde965c7e8b/Swift3D XcodeProj/blank.xcodeproj/project.xcworkspace/xcuserdata/privitec.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank.xcodeproj/xcuserdata/christian.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | blank.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank.xcodeproj/xcuserdata/privitec.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank.xcodeproj/xcuserdata/privitec.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | blank.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Priva28/Swift3D/6a8f89c24d8d25aabe52b780306bedde965c7e8b/Swift3D XcodeProj/blank/.DS_Store -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Assets.xcassets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Priva28/Swift3D/6a8f89c24d8d25aabe52b780306bedde965c7e8b/Swift3D XcodeProj/blank/Assets.xcassets/.DS_Store -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/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 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Assets.xcassets/background.imageset/Artboard Copy 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Priva28/Swift3D/6a8f89c24d8d25aabe52b780306bedde965c7e8b/Swift3D XcodeProj/blank/Assets.xcassets/background.imageset/Artboard Copy 3.png -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Assets.xcassets/background.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "Artboard Copy 3.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Assets.xcassets/horizon.imageset/An_orbital_sunrise_pillars.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Priva28/Swift3D/6a8f89c24d8d25aabe52b780306bedde965c7e8b/Swift3D XcodeProj/blank/Assets.xcassets/horizon.imageset/An_orbital_sunrise_pillars.jpg -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Assets.xcassets/horizon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "An_orbital_sunrise_pillars.jpg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Assets.xcassets/meshGradientBackground.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "meshGradientBackground.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Assets.xcassets/meshGradientBackground.imageset/meshGradientBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Priva28/Swift3D/6a8f89c24d8d25aabe52b780306bedde965c7e8b/Swift3D XcodeProj/blank/Assets.xcassets/meshGradientBackground.imageset/meshGradientBackground.png -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 11/4/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct obj: Object { 11 | @State3D var test = false 12 | 13 | var object: Object { 14 | return Stack(.y, spacing: 0) { 15 | Plane() 16 | .color(.blue) 17 | Stack(.x) { 18 | Box() 19 | .color(test ? .red : .white) 20 | .offset(x: test ? -1 : 0) 21 | .opacity(test ? 1 : 0.5) 22 | .animation(Animation3D.easeOut(duration: 5).repeatForever()) 23 | Stack(.y) { 24 | Box() 25 | .color(.red) 26 | Sphere() 27 | .onAppear { 28 | test.toggle() 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } 35 | 36 | struct obj2: Object { 37 | @State3D var test2 = false 38 | var attributes: ObjectAttributes = ObjectAttributes() 39 | var object: Object { 40 | Stack(.y) { 41 | Box() 42 | Sphere() 43 | Pyramid() 44 | Capsule() 45 | Cone() 46 | Cylinder() 47 | Torus() 48 | Tube() 49 | } 50 | } 51 | } 52 | 53 | struct obj3: Object { 54 | @State3D var test = 0 55 | var attributes: ObjectAttributes = ObjectAttributes() 56 | var object: Object { 57 | Box() 58 | .chamferRadius(0.1) 59 | .offset(x: test == 0 ? 0 : 1) 60 | .color(test == 0 ? .white : .red) 61 | .animation(.linear(duration: 3).repeatForever()) 62 | .rotation(x: test == 0 ? 0 : 45) 63 | //.opacity(test == 0 ? 0.9 : 1) 64 | .onAppear { 65 | test = 3 66 | } 67 | } 68 | } 69 | 70 | struct TrulyCustomObject: Object { 71 | @State3D private var specialVariable: Bool = /*#-editable-code*/true/*#-end-editable-code*/ 72 | 73 | var object: Object { 74 | ViewPlane(size: CGSize(width: 20, height: 30)) { 75 | VStack { 76 | Text("Hello my name is viewplane") 77 | Spacer() 78 | } 79 | } 80 | .color(.red) 81 | //Box(size: .init(width: <#T##CGFloat#>, height: <#T##CGFloat#>, length: <#T##CGFloat#>), chamferRadius: <#T##CGFloat#>) 82 | } 83 | } 84 | 85 | struct TwoDInThreeD: Object { 86 | var object: Object { 87 | ViewPlane(vertical: true) { 88 | //#-editable-code 89 | VStack { 90 | Text("This is a 2D SwiftUI view inside a 3D world!") 91 | .font(.title) 92 | .padding() 93 | Text("Try adding your own view here!") 94 | } 95 | //#-end-editable-code 96 | } 97 | } 98 | } 99 | 100 | struct ContentView: View { 101 | var body: some View { 102 | ARScene3D(baseObject: WeatherWidget(), realisticLighting: false) 103 | } 104 | } 105 | 106 | struct WeatherWidget: Object { 107 | var object: Object { 108 | Stack(.y) { 109 | ViewPlane(size: .init(width: 5.5, height: 7.5), vertical: false, body: WeatherView()) 110 | .rotation(x: -80) 111 | .opacity(0.8) 112 | Cloud() 113 | } 114 | .offset(y: -5) 115 | } 116 | } 117 | 118 | struct Cloud: Object { 119 | var object: Object { 120 | Stack(.y) { 121 | Stack(.x, spacing: 0.3) { 122 | Capsule(capRadius: 0.1, height: 0.8) 123 | .rotation(z: -40) 124 | .color(.blue) 125 | Capsule(capRadius: 0.1, height: 0.9) 126 | .rotation(z: -40) 127 | .color(.blue) 128 | Capsule(capRadius: 0.1, height: 0.8) 129 | .rotation(z: -40) 130 | .color(.blue) 131 | } 132 | .offset(x: 1.1, y: 0.8) 133 | Stack(.x) { 134 | Sphere(radius: 0.6) 135 | .offset(x: 1) 136 | .opacity(0.8) 137 | Sphere(radius: 1) 138 | .offset(x: -0.5, y: 0.35) 139 | .opacity(0.95) 140 | Sphere(radius: 0.6) 141 | .offset(x: -0.5) 142 | .opacity(0.8) 143 | } 144 | } 145 | .offset(x: -2, y: -2) 146 | } 147 | } 148 | 149 | struct WeatherView: View { 150 | var body: some View { 151 | ZStack { 152 | RoundedRectangle(cornerRadius: 10, style: .continuous) 153 | .foregroundColor(Color.gray.opacity(0.4)) 154 | .overlay( 155 | RoundedRectangle(cornerRadius: 20, style: .continuous) 156 | .stroke(Color.gray.opacity(0.8), lineWidth: 3) 157 | ) 158 | 159 | VStack { 160 | weatherDay(day: "Monday") 161 | Divider() 162 | weatherDay(day: "Tuesday") 163 | Divider() 164 | weatherDay(day: "Wednesday") 165 | Divider() 166 | Group { 167 | weatherDay(day: "Thursday") 168 | Divider() 169 | weatherDay(day: "Friday") 170 | Divider() 171 | weatherDay(day: "Saturday") 172 | Divider() 173 | weatherDay(day: "Sunday") 174 | } 175 | } 176 | .foregroundColor(.white) 177 | .padding() 178 | } 179 | } 180 | 181 | let randomWeather = ["cloud.drizzle.fill", "cloud.bolt.rain.fill", "cloud.sun.fill", "sun.max.fill", "cloud.rain.fill"] 182 | 183 | func weatherDay(day: String) -> some View { 184 | HStack { 185 | Text(day) 186 | Spacer() 187 | Image(systemName: randomWeather.randomElement()!) 188 | .resizable() 189 | .frame(width: 15, height: 15) 190 | .padding(.horizontal) 191 | Text("\(Int.random(in: 18...24))") 192 | .padding(.horizontal) 193 | Text("\(Int.random(in: 5...13))") 194 | } 195 | .padding(.vertical, 3) 196 | } 197 | } 198 | 199 | struct ContentView_Previews: PreviewProvider { 200 | static var previews: some View { 201 | ContentView() 202 | } 203 | } 204 | 205 | struct ContentView2: View { 206 | var body: some View { 207 | ZStack { 208 | Rectangle() 209 | .foregroundColor(.black) 210 | Image(uiImage: UIImage(named: "protocolDesign")!) 211 | .resizable() 212 | .aspectRatio(contentMode: .fit) 213 | } 214 | .edgesIgnoringSafeArea(.all) 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | NSCameraUsageDescription 24 | yes 25 | UIApplicationSceneManifest 26 | 27 | UIApplicationSupportsMultipleScenes 28 | 29 | 30 | UIApplicationSupportsIndirectInputEvents 31 | 32 | UILaunchScreen 33 | 34 | UIRequiredDeviceCapabilities 35 | 36 | armv7 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Priva28/Swift3D/6a8f89c24d8d25aabe52b780306bedde965c7e8b/Swift3D XcodeProj/blank/Swift3D/.DS_Store -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/ARScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARScene.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 16/4/21. 6 | // 7 | 8 | import SwiftUI 9 | import ARKit 10 | 11 | public struct ARScene3D: UIViewRepresentable, Scene3DProtocol { 12 | public init(baseObject: Object, realisticLighting: Bool = true) { 13 | self.realisticLighting = realisticLighting 14 | self.baseObject = baseObject 15 | 16 | // Setup AR view. 17 | arView = ARSCNView(frame: .init(x: 1, y: 1, width: 1, height: 1)) 18 | coachingOverlay = ARCoachingOverlayView() 19 | 20 | configureARView() 21 | configureCoachingOverlay() 22 | self.baseObject.bindProperties(update) 23 | } 24 | 25 | public func makeUIView(context: Context) -> ARSCNView { 26 | coachingOverlay.delegate = context.coordinator 27 | arView.delegate = context.coordinator 28 | return arView 29 | } 30 | 31 | internal var scene: SCNScene { arView.scene } 32 | internal var realisticLighting: Bool 33 | internal var baseObject: Object 34 | private var arView: ARSCNView 35 | private var coachingOverlay: ARCoachingOverlayView 36 | 37 | func configureARView() { 38 | let config = ARWorldTrackingConfiguration() 39 | config.planeDetection = .horizontal 40 | config.environmentTexturing = .automatic 41 | arView.autoenablesDefaultLighting = !realisticLighting 42 | arView.automaticallyUpdatesLighting = !realisticLighting 43 | arView.session.run(config) 44 | } 45 | 46 | func configureCoachingOverlay() { 47 | coachingOverlay.autoresizingMask = [ 48 | .flexibleWidth, .flexibleHeight 49 | ] 50 | coachingOverlay.goal = .horizontalPlane 51 | coachingOverlay.session = arView.session 52 | arView.addSubview(coachingOverlay) 53 | } 54 | } 55 | 56 | // MARK: - Coordinator 57 | 58 | extension ARScene3D { 59 | public func makeCoordinator() -> Coordinator { 60 | Coordinator(self) 61 | } 62 | 63 | public class Coordinator: NSObject, ARSCNViewDelegate, ARCoachingOverlayViewDelegate { 64 | init(_ arScene: ARScene3D) { 65 | self.parent = arScene 66 | } 67 | 68 | var parent: ARScene3D 69 | var scene: SCNScene { parent.arView.scene } 70 | var arView: ARSCNView { parent.arView } 71 | 72 | var raycastFirstResult: ARRaycastResult! 73 | 74 | // MARK: - ARCoachingOverlayViewDelegate Methods 75 | public func coachingOverlayViewWillActivate(_ coachingOverlayView: ARCoachingOverlayView) { 76 | resetAR() 77 | } 78 | 79 | public func coachingOverlayViewDidRequestSessionReset(_ coachingOverlayView: ARCoachingOverlayView) { 80 | resetAR() 81 | } 82 | 83 | func resetAR() { 84 | let configuration = ARWorldTrackingConfiguration() 85 | configuration.planeDetection = [.horizontal] 86 | _ = scene.rootNode.childNodes.map { $0.removeFromParentNode() } 87 | arView.session.run( 88 | configuration, 89 | options: [.resetTracking, .removeExistingAnchors, .resetSceneReconstruction, .stopTrackedRaycasts] 90 | ) 91 | } 92 | 93 | public func coachingOverlayViewDidDeactivate(_ coachingOverlayView: ARCoachingOverlayView) { 94 | guard let query = arView.raycastQuery( 95 | from: arView.screenCenter, 96 | allowing: .existingPlaneInfinite, 97 | alignment: .horizontal 98 | ) else { return } 99 | 100 | let raycastResults = arView.session.raycast(query) 101 | if raycastResults.isEmpty { 102 | print("FAIL THIS WONT HAPPEN THO") 103 | } else if let result = raycastResults.first { 104 | let anchor = ARAnchor(name: "BaseObjectAnchor", transform: result.worldTransform) 105 | raycastFirstResult = result 106 | arView.session.add(anchor: anchor) 107 | } 108 | } 109 | 110 | public func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { 111 | if anchor.name == "BaseObjectAnchor" { 112 | // Make the node face the user. 113 | let rotate = simd_float4x4( 114 | SCNMatrix4MakeRotation(arView.session.currentFrame!.camera.eulerAngles.y, 0, 1, 0) 115 | ) 116 | let rotateTransform = simd_mul(raycastFirstResult.worldTransform, rotate) 117 | node.transform = SCNMatrix4(rotateTransform) 118 | 119 | // Create the plane that will display shadows from lights 120 | let plane = SCNPlane(width: 2, height: 2) 121 | plane.heightSegmentCount = 1 122 | plane.widthSegmentCount = 1 123 | 124 | let planeNode = SCNNode(geometry: plane) 125 | planeNode.renderingOrder = -10 126 | planeNode.castsShadow = false 127 | planeNode.geometry?.firstMaterial?.diffuse.contents = UIColor(red: 1, green: 1, blue: 1, alpha: 1) 128 | planeNode.geometry?.firstMaterial?.colorBufferWriteMask = SCNColorMask(rawValue: 0) 129 | planeNode.geometry?.firstMaterial?.lightingModel = .physicallyBased 130 | planeNode.geometry?.firstMaterial?.isDoubleSided = true 131 | planeNode.transform = SCNMatrix4MakeRotation(-Float.pi / 2, 1, 0, 0) 132 | 133 | let baseObject = parent.baseObject.scnNode 134 | let baseNodeContainer = SCNNode() 135 | baseNodeContainer.addChildNode(baseObject) 136 | baseNodeContainer.scale = SCNVector3(0.1, 0.1, 0.1) 137 | baseNodeContainer.position.y = (baseObject.boundingBox.max.y*0.1) 138 | 139 | let ambientLightNode = parent.ambientLight 140 | ambientLightNode.light?.intensity = 1000 141 | let directionalLightNode = parent.directionalLight 142 | 143 | arView.prepare( 144 | [baseNodeContainer, planeNode, ambientLightNode, directionalLightNode] 145 | ) { _ in 146 | if self.parent.realisticLighting { 147 | node.addChildNode(ambientLightNode) 148 | node.addChildNode(directionalLightNode) 149 | } 150 | node.addChildNode(baseNodeContainer) 151 | node.addChildNode(planeNode) 152 | } 153 | } 154 | } 155 | } 156 | } 157 | 158 | // Required but unused method. 159 | extension ARScene3D { 160 | public func updateUIView(_ uiView: ARSCNView, context: Context) { } 161 | } 162 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/Animation3D.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Animation3D.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 15/4/21. 6 | // 7 | 8 | import SceneKit 9 | import SwiftUI 10 | 11 | public struct Animation3D { 12 | private init(option: SCNActionTimingMode, duration: Double, repeatForever: Bool = false) { 13 | self.option = option 14 | self.duration = duration 15 | self.repeatForever = repeatForever 16 | } 17 | var option: SCNActionTimingMode 18 | var duration: Double 19 | var repeatForever: Bool 20 | enum Options { 21 | case easeInOut 22 | case easeIn 23 | case easeOut 24 | case linear 25 | } 26 | 27 | // Ease In Out 28 | public static func easeInOut(duration: Double) -> Animation3D { 29 | return Animation3D(option: .easeInEaseOut, duration: duration) 30 | } 31 | public static var easeInOut: Animation3D { easeInOut(duration: 1) } 32 | 33 | // Ease In 34 | public static func easeIn(duration: Double) -> Animation3D { 35 | return Animation3D(option: .easeIn, duration: duration) 36 | } 37 | public static var easeIn: Animation3D { easeIn(duration: 1) } 38 | 39 | // Ease Out 40 | public static func easeOut(duration: Double) -> Animation3D { 41 | return Animation3D(option: .easeOut, duration: duration) 42 | } 43 | public static var easeOut: Animation3D { easeOut(duration: 1) } 44 | 45 | public static func linear(duration: Double) -> Animation3D { 46 | return Animation3D(option: .linear, duration: duration) 47 | } 48 | public static var linear: Animation3D { linear(duration: 1) } 49 | 50 | public func repeatForever(autoreverses: Bool = true) -> Animation3D { 51 | var me = self 52 | me.repeatForever = true 53 | return me 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/Box.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Box.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | public struct Box: Object { 11 | public init(size: Size3D = .init(width: 1, height: 1, length: 1), chamferRadius: CGFloat = 0) { 12 | self.size = size 13 | self.chamferRadius = chamferRadius 14 | } 15 | 16 | public var object: Object { self } 17 | public var attributes = ObjectAttributes() 18 | 19 | private let size: Size3D 20 | private var chamferRadius: CGFloat 21 | } 22 | 23 | extension Box { 24 | public func renderScnNode() -> (SCNNode, [Attributes]) { 25 | let box = SCNBox(width: size.width, height: size.height, length: size.length, chamferRadius: chamferRadius) 26 | var node = SCNNode(geometry: box) 27 | let changedAttributes = applyAttributes(to: &node) 28 | return (node, changedAttributes) 29 | } 30 | } 31 | 32 | extension Box { 33 | public func chamferRadius(_ radius: CGFloat) -> Box { 34 | var me = self 35 | me.chamferRadius = radius 36 | return me 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/Capsule.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Capsule.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 17/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | public struct Capsule: Object { 11 | public init(capRadius: CGFloat = 0.2, height: CGFloat = 1) { 12 | self.capRadius = capRadius 13 | self.height = height 14 | } 15 | 16 | public var object: Object { self } 17 | public var attributes = ObjectAttributes() 18 | 19 | private let capRadius: CGFloat 20 | private let height: CGFloat 21 | } 22 | 23 | extension Capsule { 24 | public func renderScnNode() -> (SCNNode, [Attributes]) { 25 | let capsule = SCNCapsule(capRadius: capRadius, height: height) 26 | var node = SCNNode(geometry: capsule) 27 | let changedAttributes = applyAttributes(to: &node) 28 | return (node, changedAttributes) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/Cone.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cone.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 17/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | public struct Cone: Object { 11 | public init(topRadius: CGFloat = 0, bottomRadius: CGFloat = 0.5, height: CGFloat = 1) { 12 | self.topRadius = topRadius 13 | self.bottomRadius = bottomRadius 14 | self.height = height 15 | } 16 | 17 | public var object: Object { self } 18 | public var attributes = ObjectAttributes() 19 | 20 | private let topRadius: CGFloat 21 | private let bottomRadius: CGFloat 22 | private let height: CGFloat 23 | } 24 | 25 | extension Cone { 26 | public func renderScnNode() -> (SCNNode, [Attributes]) { 27 | let cone = SCNCone(topRadius: topRadius, bottomRadius: bottomRadius, height: height) 28 | var node = SCNNode(geometry: cone) 29 | let changedAttributes = applyAttributes(to: &node) 30 | return (node, changedAttributes) 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/Cylinder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cylinder.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 17/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | public struct Cylinder: Object { 11 | public init(radius: CGFloat = 0.5, height: CGFloat = 1) { 12 | self.radius = radius 13 | self.height = height 14 | } 15 | 16 | public var object: Object { self } 17 | public var attributes = ObjectAttributes() 18 | 19 | private let radius: CGFloat 20 | private let height: CGFloat 21 | } 22 | 23 | extension Cylinder { 24 | public func renderScnNode() -> (SCNNode, [Attributes]) { 25 | let cylinder = SCNCylinder(radius: radius, height: height) 26 | var node = SCNNode(geometry: cylinder) 27 | let changedAttributes = applyAttributes(to: &node) 28 | return (node, changedAttributes) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/DynamicProperty3D.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARDynamicProperty.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | public protocol DynamicProperty3D { 9 | var update: () -> Void { get set } 10 | } 11 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/EmptyObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyObject.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct EmptyObject: Object { 11 | public init() { } 12 | 13 | public var object: Object { self } 14 | public var attributes = ObjectAttributes() 15 | } 16 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARHelpers.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | import ARKit 9 | 10 | public enum XYZ: String { 11 | case x = "x" 12 | case y = "y" 13 | case z = "z" 14 | } 15 | 16 | public struct Size3D { 17 | public init(width: CGFloat, height: CGFloat, length: CGFloat) { 18 | self.width = width 19 | self.height = height 20 | self.length = length 21 | } 22 | 23 | public var width: CGFloat 24 | public var height: CGFloat 25 | public var length: CGFloat 26 | } 27 | 28 | public struct Location3D { 29 | public init(x: Float, y: Float, z: Float) { 30 | self.x = x 31 | self.y = y 32 | self.z = z 33 | } 34 | 35 | public var x: Float 36 | public var y: Float 37 | public var z: Float 38 | 39 | static public var zero: Location3D { 40 | return Location3D(x: 0, y: 0, z: 0) 41 | } 42 | } 43 | 44 | extension Location3D { 45 | public var scnVector3: SCNVector3 { 46 | return SCNVector3(self.x, self.y, self.z) 47 | } 48 | } 49 | 50 | extension SCNActionTimingMode { 51 | func caMediaTimingFunction() -> CAMediaTimingFunction { 52 | switch self { 53 | case .easeIn: 54 | return CAMediaTimingFunction(name: .easeIn) 55 | case .easeInEaseOut: 56 | return CAMediaTimingFunction(name: .easeInEaseOut) 57 | case .easeOut: 58 | return CAMediaTimingFunction(name: .easeOut) 59 | case .linear: 60 | return CAMediaTimingFunction(name: .linear) 61 | default: 62 | return CAMediaTimingFunction(name: .linear) 63 | } 64 | } 65 | } 66 | 67 | extension ARSCNView { 68 | var screenCenter: CGPoint { 69 | return CGPoint(x: bounds.midX, y: bounds.midY) 70 | } 71 | } 72 | 73 | func deg2rad(_ number: Float) -> Float { 74 | return number * .pi / 180 75 | } 76 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/Object.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Object.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | import SceneKit 9 | import Combine 10 | 11 | public protocol Object: ObjectSupportedAttributes, ObjectGroup { 12 | var object: Object { get } 13 | var scnNode: SCNNode { get } 14 | var id: String { get } 15 | func renderScnNode() -> (SCNNode, [Attributes]) 16 | } 17 | 18 | extension Object { 19 | public var objects: [Object] { [self] } 20 | } 21 | 22 | // MARK: - Rendering of objects into SCNNode. 23 | 24 | extension Object { 25 | public var scnNode: SCNNode { 26 | let render = renderScnNode() 27 | _ = render.1.compactMap { 28 | switch $0 { 29 | case .onAppear(let function): 30 | // please don't judge me i didn't have the time to implement this properly and i'm assuming it would have appeared at least 0.8 seconds after this 31 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { 32 | function() 33 | } 34 | default: 35 | break 36 | } 37 | } 38 | return render.0 39 | } 40 | 41 | // THIS IS THE DEFAULT ONLY DONT EXPECT THIS TO APPLY CHANGES TO EVERYTHING AS MOST HAVE CUSTOM IMPLEMENTATIONS 42 | public func renderScnNode() -> (SCNNode, [Attributes]) { 43 | let render = object.renderScnNode() 44 | var node = render.0 45 | var changedAttributes = applyAttributes(to: &node) 46 | changedAttributes.append(contentsOf: render.1) 47 | return (node, changedAttributes) 48 | } 49 | } 50 | 51 | // MARK: - Bind properties of @State3D to scene update method. 52 | 53 | extension Object { 54 | func bindProperties(_ update: @escaping () -> Void) { 55 | let mirror = Mirror(reflecting: self) 56 | for child in mirror.children { 57 | if var child = child.value as? DynamicProperty3D { 58 | child.update = update 59 | } 60 | } 61 | } 62 | } 63 | 64 | // MARK: - Search for child object with id. 65 | 66 | extension Object { 67 | func childWithId(id: String) -> Object? { 68 | var array: [Object] = [] 69 | var timeout = 0 70 | if array.isEmpty { 71 | array.append(self) 72 | } 73 | while !array.contains(where: { $0.id == id }) { 74 | switch array.last! { 75 | case is Stack: 76 | let stack = array.last! as! Stack 77 | for object in stack.content { 78 | array.append(object) 79 | } 80 | default: 81 | array.append(array.last!.object) 82 | } 83 | timeout += 1 84 | if timeout > 500 { 85 | print("too many objects to search") 86 | return nil 87 | } 88 | } 89 | return array.first { $0.id == id } 90 | } 91 | } 92 | 93 | // MARK: - Getter for id. 94 | 95 | extension Object { 96 | public var id: String { 97 | if self is Stack || object is Stack { 98 | let stack = self as? Stack ?? self.object as! Stack 99 | var base = "Stack,\(stack.xyz.rawValue),\(stack.spacing ?? 0)," 100 | if color != nil || object.color != nil { base.append("color") } 101 | if offset != nil || object.offset != nil { base.append("offset") } 102 | if opacity != nil || object.opacity != nil { base.append("opacity") } 103 | if rotation != nil || object.rotation != nil { base.append("rotation") } 104 | return base + ",\(String(format: "%04d", index))" 105 | } else { 106 | var base = "\(type(of: self))," 107 | if color != nil || object.color != nil { base.append("color") } 108 | if offset != nil || object.offset != nil { base.append("offset") } 109 | if opacity != nil || object.opacity != nil { base.append("opacity") } 110 | if rotation != nil || object.rotation != nil { base.append("rotation") } 111 | return base + ",\(String(format: "%04d", index))" 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/ObjectAttributes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ObjectAttributes.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | import SwiftUI 9 | import SceneKit 10 | 11 | public struct ObjectAttributes { 12 | public var index: Int = 0 13 | public var color: Color? = nil 14 | public var offset: Location3D? = nil 15 | public var opacity: CGFloat? = nil 16 | public var rotation: Location3D? = nil 17 | public var animation: Animation3D? = nil 18 | public var onAppear: (() -> Void)? = nil 19 | public init() { } 20 | } 21 | 22 | public enum Attributes { 23 | case opacity 24 | case color 25 | case offset 26 | case rotation 27 | case onAppear(() -> Void) 28 | } 29 | 30 | public protocol ObjectSupportedAttributes { 31 | var attributes: ObjectAttributes { get set } 32 | } 33 | 34 | // THIS WILL MEAN CUSTOM VIEWS WONT BE ABLE TO SAVE ATTRIBUTES BUT IT LOOKS CLEANER SO ITS OK FOR NOW. 35 | extension ObjectSupportedAttributes { 36 | public var attributes: ObjectAttributes { get {ObjectAttributes()} set {} } 37 | } 38 | 39 | extension Object { 40 | public func applyAttributes(to node: inout SCNNode) -> [Attributes] { 41 | var changedAttributes: [Attributes] = [] 42 | 43 | if let color = color { 44 | changedAttributes.append(.color) 45 | node.geometry?.firstMaterial?.diffuse.contents = UIColor(color) 46 | } 47 | 48 | if let offset = offset { 49 | changedAttributes.append(.offset) 50 | node.position.x = node.position.x + offset.x 51 | node.position.y = node.position.y + offset.y 52 | node.position.z = node.position.z + offset.z 53 | } 54 | 55 | if let rotation = rotation { 56 | changedAttributes.append(.rotation) 57 | node.eulerAngles.x = deg2rad(rotation.x) 58 | node.eulerAngles.y = deg2rad(rotation.y) 59 | node.eulerAngles.z = deg2rad(rotation.z) 60 | } 61 | 62 | if let opacity = opacity { 63 | changedAttributes.append(.opacity) 64 | node.opacity = opacity 65 | } 66 | 67 | if let onAppear = onAppear { 68 | changedAttributes.append(.onAppear(onAppear)) 69 | } 70 | 71 | node.name = id 72 | 73 | return changedAttributes 74 | } 75 | } 76 | 77 | extension Object { 78 | internal var index: Int { 79 | get { 80 | return attributes.index 81 | } 82 | set { 83 | attributes.index = newValue 84 | } 85 | } 86 | } 87 | 88 | extension Object { 89 | internal var color: Color? { 90 | get { 91 | return attributes.color 92 | } 93 | set { 94 | attributes.color = newValue 95 | } 96 | } 97 | public func color(_ color: Color) -> some Object { 98 | var me = self 99 | me.attributes.color = color 100 | return me 101 | } 102 | } 103 | 104 | extension Object { 105 | internal var offset: Location3D? { 106 | get { 107 | return attributes.offset 108 | } 109 | set { 110 | attributes.offset = newValue 111 | } 112 | } 113 | public func offset(_ offset: Location3D) -> some Object { 114 | var me = self 115 | me.offset = offset 116 | return me 117 | } 118 | public func offset(x: Float = 0, y: Float = 0, z: Float = 0) -> some Object { 119 | var me = self 120 | me.offset = Location3D(x: x + (offset?.x ?? 0), y: y + (offset?.y ?? 0), z: z + (offset?.z ?? 0)) 121 | return me 122 | } 123 | } 124 | 125 | extension Object { 126 | internal var opacity: CGFloat? { 127 | get { 128 | return attributes.opacity 129 | } 130 | set { 131 | attributes.opacity = newValue 132 | } 133 | } 134 | public func opacity(_ opacity: CGFloat) -> some Object { 135 | var me = self 136 | me.opacity = opacity 137 | return me 138 | } 139 | } 140 | 141 | extension Object { 142 | internal var animation: Animation3D? { 143 | get { 144 | return attributes.animation 145 | } 146 | set { 147 | attributes.animation = newValue 148 | } 149 | } 150 | public func animation(_ animation: Animation3D?) -> some Object { 151 | var me = self 152 | me.attributes.animation = animation 153 | return me 154 | } 155 | } 156 | 157 | extension Object { 158 | internal var rotation: Location3D? { 159 | get { 160 | return attributes.rotation 161 | } 162 | set { 163 | attributes.rotation = newValue 164 | } 165 | } 166 | public func rotation(_ rotation: Location3D) -> some Object { 167 | var me = self 168 | me.attributes.rotation = rotation 169 | return me 170 | } 171 | public func rotation(x: Float = 0, y: Float = 0, z: Float = 0) -> some Object { 172 | var me = self 173 | me.rotation = Location3D(x: x + (rotation?.x ?? 0), y: y + (rotation?.y ?? 0), z: z + (rotation?.z ?? 0)) 174 | return me 175 | } 176 | } 177 | 178 | 179 | extension Object { 180 | internal var onAppear: (() -> Void)? { 181 | get { 182 | return attributes.onAppear 183 | } 184 | set { 185 | attributes.onAppear = newValue 186 | } 187 | } 188 | public func onAppear(_ closure: @escaping () -> Void) -> some Object { 189 | var me = self 190 | me.onAppear = closure 191 | return me 192 | } 193 | } 194 | 195 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/ObjectBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ObjectBuilder.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | public protocol ObjectGroup { 9 | var objects: [Object] { get } 10 | } 11 | 12 | extension Array: ObjectGroup where Element == Object { 13 | public var objects: [Object] { self } 14 | } 15 | 16 | @resultBuilder public struct ObjectBuilder { 17 | public static func buildBlock(_ objects: ObjectGroup...) -> [Object] { 18 | return objects.flatMap { $0.objects } 19 | } 20 | public static func buildOptional(_ object: [ObjectGroup]?) -> [Object] { 21 | return object?.flatMap { $0.objects } ?? [] 22 | } 23 | public static func buildEither(first object: [ObjectGroup]) -> [Object] { 24 | return object.flatMap { $0.objects } 25 | } 26 | public static func buildEither(second object: [ObjectGroup]) -> [Object] { 27 | return object.flatMap { $0.objects } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/Plane.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Plane.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 15/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | public struct Plane: Object { 11 | public init(size: CGSize = .init(width: 10, height: 10), vertical: Bool = false, doubleSided: Bool = true, cornerRadius: CGFloat = 0) { 12 | self.size = size 13 | self.doubleSided = doubleSided 14 | self.vertical = vertical 15 | self.cornerRadius = cornerRadius 16 | } 17 | 18 | public var object: Object { self } 19 | public var attributes = ObjectAttributes() 20 | 21 | private let size: CGSize 22 | private var doubleSided: Bool 23 | private var vertical: Bool 24 | private var cornerRadius: CGFloat 25 | } 26 | 27 | extension Plane { 28 | public func renderScnNode() -> (SCNNode, [Attributes]) { 29 | let plane = SCNPlane(width: size.width, height: size.height) 30 | plane.materials.first?.isDoubleSided = doubleSided 31 | plane.cornerRadius = cornerRadius 32 | var node = SCNNode(geometry: plane) 33 | if !vertical { 34 | node.transform = SCNMatrix4MakeRotation(-Float.pi / 2, 1, 0, 0) 35 | } 36 | let changedAttributes = applyAttributes(to: &node) 37 | return (node, changedAttributes) 38 | } 39 | } 40 | 41 | extension Plane { 42 | public func doubleSided(_ bool: Bool) -> Plane { 43 | var me = self 44 | me.doubleSided = bool 45 | return me 46 | } 47 | } 48 | 49 | extension Plane { 50 | public func cornerRadius(_ cornerRadius: CGFloat) -> Plane { 51 | var me = self 52 | me.cornerRadius = cornerRadius 53 | return me 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/Pyramid.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sphere.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | public struct Pyramid: Object { 11 | public init(size: Size3D = Size3D(width: 1, height: 1, length: 1)) { 12 | self.size = size 13 | } 14 | 15 | public var object: Object { self } 16 | public var attributes = ObjectAttributes() 17 | 18 | private let size: Size3D 19 | } 20 | 21 | extension Pyramid { 22 | public func renderScnNode() -> (SCNNode, [Attributes]) { 23 | let pyramid = SCNPyramid(width: size.width, height: size.height, length: size.length) 24 | var node = SCNNode(geometry: pyramid) 25 | node.position.y = -node.boundingBox.max.y/2 26 | let containerNode = SCNNode() 27 | containerNode.addChildNode(node) 28 | let changedAttributes = applyAttributes(to: &node) 29 | return (containerNode, changedAttributes) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/Scene3D.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Scene.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 11/4/21. 6 | // 7 | 8 | import SwiftUI 9 | import SceneKit 10 | 11 | public struct Scene3D: View, Scene3DProtocol { 12 | 13 | public init(baseObject: Object, realisticLighting: Bool = true) { 14 | // Setup camera and scene. 15 | let cameraNode = SCNNode() 16 | cameraNode.camera = SCNCamera() 17 | cameraNode.position = SCNVector3(x: 0, y: 0, z: 15) 18 | 19 | let scene = SCNScene() 20 | scene.rootNode.addChildNode(cameraNode) 21 | 22 | // Initialise properties. 23 | self.scene = scene 24 | self.camera = cameraNode 25 | self.realisticLighting = realisticLighting 26 | self.baseObject = baseObject 27 | self.baseObject.bindProperties(update) 28 | 29 | // Add lighting and objects to scene. 30 | if self.realisticLighting { 31 | scene.rootNode.addChildNode(ambientLight) 32 | scene.rootNode.addChildNode(directionalLight) 33 | } 34 | scene.rootNode.addChildNode(baseObject.scnNode) 35 | } 36 | 37 | public var body: some View { 38 | var defaultOptions: SceneView.Options = [.allowsCameraControl, .jitteringEnabled] 39 | if !realisticLighting { defaultOptions.insert(.autoenablesDefaultLighting) } 40 | return SceneView(scene: scene, pointOfView: camera, options: defaultOptions) 41 | } 42 | 43 | internal var scene: SCNScene 44 | internal var realisticLighting: Bool 45 | internal var baseObject: Object 46 | private var camera: SCNNode 47 | 48 | fileprivate init( 49 | baseObject: Object, 50 | backgroundColor: Color? = nil, 51 | backgroundImage: UIImage? = nil 52 | ) { 53 | self.init(baseObject: baseObject) 54 | 55 | if let backgroundColor = backgroundColor { 56 | scene.background.contents = UIColor(backgroundColor) 57 | } 58 | 59 | if let backgroundImage = backgroundImage { 60 | scene.background.contents = backgroundImage 61 | } 62 | } 63 | } 64 | 65 | extension Scene3D { 66 | public func backgroundColor(_ color: Color) -> some View { 67 | return Scene3D(baseObject: self.baseObject, backgroundColor: color) 68 | } 69 | } 70 | 71 | extension Scene3D { 72 | public func backgroundImage(_ image: UIImage) -> some View { 73 | return Scene3D(baseObject: self.baseObject, backgroundImage: image) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/Scene3DProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Scene3DProtocol.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 17/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | internal protocol Scene3DProtocol { 11 | var scene: SCNScene { get } 12 | var baseObject: Object { get set } 13 | 14 | var realisticLighting: Bool { get } 15 | var ambientLight: SCNNode { get } 16 | var directionalLight: SCNNode { get } 17 | func update() 18 | } 19 | 20 | // MARK: - Configure Scene Lighting 21 | 22 | extension Scene3DProtocol { 23 | // Ambient light lights areas not lit by directional light. 24 | var ambientLight: SCNNode { 25 | let ambientLight = SCNLight() 26 | ambientLight.type = .ambient 27 | ambientLight.color = UIColor.white 28 | ambientLight.intensity = 2000 29 | ambientLight.categoryBitMask = -1 30 | 31 | let ambientLightNode = SCNNode() 32 | ambientLightNode.light = ambientLight 33 | ambientLightNode.position = SCNVector3(x: 0, y: 5, z: 0) 34 | return ambientLightNode 35 | } 36 | 37 | // Directional light creates shadows 38 | var directionalLight: SCNNode { 39 | let directionalLight = SCNLight() 40 | directionalLight.type = .directional 41 | directionalLight.castsShadow = true 42 | directionalLight.color = UIColor.white 43 | directionalLight.automaticallyAdjustsShadowProjection = true 44 | directionalLight.shadowColor = UIColor.black.withAlphaComponent(0.5) 45 | directionalLight.shadowMode = .deferred 46 | directionalLight.shadowRadius = 8 47 | directionalLight.zNear = 0 48 | directionalLight.zFar = 50 49 | directionalLight.shadowSampleCount = 32 50 | directionalLight.shadowMapSize = CGSize(width: 4096, height: 4096) 51 | directionalLight.categoryBitMask = -1 52 | 53 | let directionalLightNode = SCNNode() 54 | directionalLightNode.light = directionalLight 55 | directionalLightNode.position = SCNVector3(x: 0, y: 15, z: 0) 56 | directionalLightNode.eulerAngles = SCNVector3(deg2rad(-88), 0, deg2rad(-2)) 57 | 58 | return directionalLightNode 59 | } 60 | } 61 | 62 | // MARK: - Configure update method. 63 | 64 | extension Scene3DProtocol { 65 | func update() { 66 | scene.rootNode.enumerateChildNodes { node, stop in 67 | // Ensure node has a name. 68 | // If so search for a child object from base object with matching id. 69 | if let name = node.name, 70 | let object = baseObject.childWithId(id: name) 71 | { 72 | let child = object.object 73 | 74 | let animationDuration = child.animation?.duration ?? 0 75 | let animationCurve = child.animation?.option ?? .linear 76 | let animationRepeatsForever = child.animation?.repeatForever ?? false 77 | 78 | var animationGroup: [SCNAction] = [] 79 | 80 | // MARK: - Update color 81 | if name.contains("color"), 82 | let newColor = child.color, 83 | UIColor(newColor) != node.geometry?.firstMaterial?.diffuse.contents as? UIColor 84 | { 85 | let oldColor = node.geometry?.firstMaterial?.diffuse.contents as? UIColor ?? .white 86 | 87 | func newColorAction(toColor: UIColor) -> SCNAction { 88 | SCNAction.customAction(duration: animationDuration) { actionNode, time in 89 | SCNTransaction.begin() 90 | SCNTransaction.animationDuration = animationDuration 91 | SCNTransaction.animationTimingFunction = animationCurve.caMediaTimingFunction() 92 | node.geometry?.firstMaterial?.diffuse.contents = toColor 93 | SCNTransaction.commit() 94 | } 95 | } 96 | 97 | let colorAction = newColorAction(toColor: UIColor(newColor)) 98 | colorAction.timingMode = animationCurve 99 | 100 | if animationRepeatsForever { 101 | let colorAction2 = newColorAction(toColor: oldColor) 102 | colorAction2.timingMode = animationCurve 103 | animationGroup.append(SCNAction.sequence([colorAction, colorAction2])) 104 | } else { 105 | animationGroup.append(colorAction) 106 | } 107 | } 108 | 109 | // MARK: - Update offset 110 | if name.contains("offset"), 111 | let newOffset = child.offset 112 | { 113 | var newVector = SCNVector3() 114 | 115 | // Check if object is in a stack. 116 | if let parentNode = node.parent, 117 | let parentName = parentNode.name, 118 | parentName.hasPrefix("Stack") 119 | { 120 | let stackProperties = parentName.components(separatedBy: ",") 121 | 122 | let xyz = XYZ(rawValue: stackProperties[1])! 123 | let spacing = Float(stackProperties[2]) ?? 0 124 | let index = Int(name.suffix(4))! 125 | 126 | var totalWidth: Float = 0 127 | for childIndex in 0...index { 128 | if childIndex != 0 { 129 | totalWidth += parentNode.childNodes[childIndex-1].boundingBox.max.x 130 | totalWidth += parentNode.childNodes[childIndex].boundingBox.max.x 131 | } 132 | } 133 | 134 | var totalHeight: Float = 0 135 | for childIndex in 0...index { 136 | if childIndex != 0 { 137 | totalHeight += parentNode.childNodes[childIndex-1].boundingBox.max.y 138 | totalHeight += parentNode.childNodes[childIndex].boundingBox.max.y 139 | } 140 | } 141 | 142 | var totalLength: Float = 0 143 | for childIndex in 0...index { 144 | if childIndex != 0 { 145 | totalLength += parentNode.childNodes[childIndex-1].boundingBox.max.z 146 | totalLength += parentNode.childNodes[childIndex].boundingBox.max.z 147 | } 148 | } 149 | 150 | print("calc: \(totalWidth)") 151 | print(node.position.x) 152 | let xTranslation = totalWidth + ((spacing)*Float(parentNode.childNodes.count-1)) 153 | let yTranslation = totalHeight + ((spacing)*Float(parentNode.childNodes.count-1)) 154 | let zTranslation = totalLength + ((spacing)*Float(parentNode.childNodes.count-1)) 155 | 156 | switch xyz { 157 | case .x: 158 | newVector = SCNVector3( 159 | x: xTranslation - node.position.x, 160 | y: newOffset.y - node.position.y, 161 | z: newOffset.z - node.position.z 162 | ) 163 | case .y: 164 | newVector = SCNVector3( 165 | x: newOffset.x - node.position.x, 166 | y: yTranslation + newOffset.y - node.position.y, 167 | z: newOffset.z - node.position.z 168 | ) 169 | case .z: 170 | newVector = SCNVector3( 171 | x: newOffset.x - node.position.x, 172 | y: newOffset.y - node.position.y, 173 | z: zTranslation + newOffset.z - node.position.z 174 | ) 175 | } 176 | let offsetAction = SCNAction.move(by: newVector, duration: animationDuration) 177 | offsetAction.timingMode = animationCurve 178 | 179 | if animationRepeatsForever { 180 | animationGroup.append(SCNAction.sequence([offsetAction, offsetAction.reversed()])) 181 | } else { 182 | animationGroup.append(offsetAction) 183 | } 184 | } else if 185 | newOffset.x != node.position.x || 186 | newOffset.y != node.position.y || 187 | newOffset.z != node.position.z 188 | { 189 | newVector = SCNVector3( 190 | x: newOffset.x - node.position.x, 191 | y: newOffset.y - node.position.y, 192 | z: newOffset.z - node.position.z 193 | ) 194 | let offsetAction = SCNAction.move(by: newVector, duration: animationDuration) 195 | offsetAction.timingMode = animationCurve 196 | 197 | if animationRepeatsForever { 198 | animationGroup.append(SCNAction.sequence([offsetAction, offsetAction.reversed()])) 199 | } else { 200 | animationGroup.append(offsetAction) 201 | } 202 | } 203 | } 204 | 205 | // MARK: - Update rotation 206 | if name.contains("rotation"), 207 | let newRotation = child.rotation, 208 | deg2rad(newRotation.x) != node.eulerAngles.x || 209 | deg2rad(newRotation.y) != node.eulerAngles.y || 210 | deg2rad(newRotation.z) != node.eulerAngles.z 211 | { 212 | let rotationAction = SCNAction.rotateBy( 213 | x: CGFloat(deg2rad(newRotation.x) - node.eulerAngles.x), 214 | y: CGFloat(deg2rad(newRotation.y) - node.eulerAngles.y), 215 | z: CGFloat(deg2rad(newRotation.z) - node.eulerAngles.z), 216 | duration: animationDuration 217 | ) 218 | rotationAction.timingMode = animationCurve 219 | 220 | if animationRepeatsForever { 221 | animationGroup.append(SCNAction.sequence([rotationAction, rotationAction.reversed()])) 222 | } else { 223 | animationGroup.append(rotationAction) 224 | } 225 | } 226 | 227 | // MARK: - Update opacity 228 | // Yes my code is messy but if anyone at apple who works with scenekit is reading this, SCNActions modifying opacity are broken... no time to fix only 3 days left and still need to do the playground pages and walkthroughs. 229 | if name.contains("opacity"), 230 | let newOpacity = child.opacity, 231 | newOpacity != node.opacity 232 | { 233 | let opacityChange = newOpacity - node.opacity 234 | let opacityAction = SCNAction.fadeOpacity(by: opacityChange, duration: animationDuration) 235 | opacityAction.timingMode = animationCurve 236 | 237 | if animationRepeatsForever { 238 | let opacityAction2 = SCNAction.fadeOpacity(by: -opacityChange, duration: animationDuration) 239 | opacityAction2.timingMode = animationCurve 240 | animationGroup.append(SCNAction.sequence([opacityAction, opacityAction2])) 241 | } else { 242 | animationGroup.append(opacityAction) 243 | } 244 | } 245 | 246 | 247 | let groupAction = SCNAction.group(animationGroup) 248 | 249 | if animationRepeatsForever { 250 | let repeatAction = SCNAction.repeatForever(groupAction) 251 | node.runAction(repeatAction) 252 | } else { 253 | node.runAction(groupAction) 254 | } 255 | } 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/Sphere.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sphere.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | public struct Sphere: Object { 11 | public init(radius: CGFloat = 0.5) { 12 | self.radius = radius 13 | } 14 | 15 | public var object: Object { self } 16 | public var attributes = ObjectAttributes() 17 | 18 | private let radius: CGFloat 19 | } 20 | 21 | extension Sphere { 22 | public func renderScnNode() -> (SCNNode, [Attributes]) { 23 | let sphere = SCNSphere(radius: radius) 24 | var node = SCNNode(geometry: sphere) 25 | let changedAttributes = applyAttributes(to: &node) 26 | return (node, changedAttributes) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/Stack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XStack.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | import SwiftUI 9 | import SceneKit 10 | 11 | public struct Stack: Object { 12 | public init(_ xyz: XYZ, spacing: Float? = nil, @ObjectBuilder content: () -> [Object]) { 13 | self.xyz = xyz 14 | self.spacing = spacing 15 | 16 | var objects: [Object] = [] 17 | for (index, i) in content().enumerated() { 18 | var object = i 19 | object.index = index 20 | objects.append(object) 21 | } 22 | 23 | self.content = objects 24 | } 25 | 26 | public var object: Object { self } 27 | public var attributes = ObjectAttributes() 28 | 29 | internal var content: [Object] 30 | internal var xyz: XYZ 31 | internal var spacing: Float? 32 | } 33 | 34 | extension Stack { 35 | func stackToNode(xyz: XYZ, spacing: Float?, color: Color?) -> SCNNode { 36 | let parentNode = SCNNode() 37 | 38 | for (index, object) in content.enumerated() { 39 | let childNode = object.scnNode 40 | let spacing = spacing ?? 0 41 | let xTranslation = index == 0 ? 0 : parentNode.boundingBox.max.x + spacing + childNode.boundingBox.max.x 42 | let yTranslation = index == 0 ? 0 : parentNode.boundingBox.max.y + spacing + childNode.boundingBox.max.y 43 | let zTranslation = index == 0 ? 0 : parentNode.boundingBox.max.z + spacing + childNode.boundingBox.max.z 44 | switch xyz { 45 | case .x: 46 | childNode.position.x += xTranslation 47 | case .y: 48 | childNode.position.y += yTranslation 49 | case .z: 50 | childNode.position.z += zTranslation 51 | } 52 | parentNode.addChildNode(childNode) 53 | } 54 | return parentNode 55 | } 56 | } 57 | 58 | extension Stack { 59 | public func renderScnNode() -> (SCNNode, [Attributes]) { 60 | var node = stackToNode(xyz: xyz, spacing: spacing, color: color) 61 | let changedAttributes = applyAttributes(to: &node) 62 | return (node, changedAttributes) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/State3D.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARState.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @propertyWrapper public class State3D: DynamicProperty3D { 11 | 12 | /// Set the initial default value. 13 | public init(wrappedValue value: Value) { 14 | self.storage.internalValue = value 15 | } 16 | 17 | /// This is the value that is accessed by default. Eg `@CustomState var test = 5` access this wrapped value with just `test`. 18 | public var wrappedValue: Value { 19 | /// Get the wrappedValue from the internal value of the model. 20 | get { self.storage.internalValue! } 21 | 22 | /// When a user changes the wrappedValue, it will change the internalValue meaning that with @CustomState, they can mutate and access a value without storing it in the struct/classes self. 23 | set { 24 | self.storage.internalValue = newValue 25 | self.update() 26 | } 27 | } 28 | 29 | public var update: () -> Void = { } 30 | 31 | private var storage = InternalStorage() 32 | 33 | private class InternalStorage { 34 | var internalValue: Value? 35 | /// Be nil by default, but this doesn't matter as when a @CustomState is created, the internalValue is set to the default it is created with. 36 | internal init() { self.internalValue = nil } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/Torus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Torus.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 17/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | public struct Torus: Object { 11 | public init(ringRadius: CGFloat = 0.5, pipeRadius: CGFloat = 0.2) { 12 | self.ringRadius = ringRadius 13 | self.pipeRadius = pipeRadius 14 | } 15 | 16 | public var object: Object { self } 17 | public var attributes = ObjectAttributes() 18 | 19 | private let ringRadius: CGFloat 20 | private let pipeRadius: CGFloat 21 | } 22 | 23 | extension Torus { 24 | public func renderScnNode() -> (SCNNode, [Attributes]) { 25 | let torus = SCNTorus(ringRadius: ringRadius, pipeRadius: pipeRadius) 26 | var node = SCNNode(geometry: torus) 27 | let changedAttributes = applyAttributes(to: &node) 28 | return (node, changedAttributes) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/Tube.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tube.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 17/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | public struct Tube: Object { 11 | public init(outerRadius: CGFloat = 0.5, innerRadius: CGFloat = 0.1, height: CGFloat = 1) { 12 | self.outerRadius = outerRadius 13 | self.innerRadius = innerRadius 14 | self.height = height 15 | } 16 | 17 | public var object: Object { self } 18 | public var attributes = ObjectAttributes() 19 | 20 | private let outerRadius: CGFloat 21 | private let innerRadius: CGFloat 22 | private let height: CGFloat 23 | } 24 | 25 | extension Tube { 26 | public func renderScnNode() -> (SCNNode, [Attributes]) { 27 | let torus = SCNTube(innerRadius: innerRadius, outerRadius: outerRadius, height: height) 28 | var node = SCNNode(geometry: torus) 29 | let changedAttributes = applyAttributes(to: &node) 30 | return (node, changedAttributes) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/Swift3D/ViewPlane.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewPlane.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 15/4/21. 6 | // 7 | 8 | import SceneKit 9 | import SwiftUI 10 | 11 | public struct ViewPlane: Object where Content: View { 12 | public init(size: CGSize = .init(width: 10, height: 10), doubleSided: Bool = true, vertical: Bool = false, cornerRadius: CGFloat = 0, @ViewBuilder body: () -> Content) { 13 | self.size = size 14 | self.doubleSided = doubleSided 15 | self.vertical = vertical 16 | self.cornerRadius = cornerRadius 17 | self.body = body() 18 | } 19 | 20 | public init(size: CGSize = .init(width: 10, height: 10), doubleSided: Bool = true, vertical: Bool = false, cornerRadius: CGFloat = 0, body: Content) { 21 | self.size = size 22 | self.doubleSided = doubleSided 23 | self.vertical = vertical 24 | self.cornerRadius = cornerRadius 25 | self.body = body 26 | } 27 | 28 | public var object: Object { self } 29 | public var attributes = ObjectAttributes() 30 | 31 | private let size: CGSize 32 | private var doubleSided: Bool 33 | private var vertical: Bool 34 | private var cornerRadius: CGFloat 35 | private var body: Content 36 | } 37 | 38 | extension ViewPlane { 39 | public func renderScnNode() -> (SCNNode, [Attributes]) { 40 | let plane = SCNPlane(width: size.width, height: size.height) 41 | 42 | plane.cornerRadius = cornerRadius 43 | var node = SCNNode(geometry: plane) 44 | if !vertical { 45 | node.transform = SCNMatrix4MakeRotation(-Float.pi / 2, 1, 0, 0) 46 | } 47 | 48 | let changedAttributes = applyAttributes(to: &node) 49 | 50 | DispatchQueue.main.async { 51 | let materialView = UIHostingController(rootView: body).view! 52 | materialView.isOpaque = false 53 | materialView.backgroundColor = UIColor(color ?? .clear) 54 | materialView.frame = CGRect(x: 0, y: 0, width: plane.width*50, height: plane.height*50) 55 | 56 | let material = SCNMaterial() 57 | material.diffuse.contents = viewToImage(view: materialView) 58 | node.geometry?.materials = [material] 59 | node.geometry?.materials.first?.isDoubleSided = doubleSided 60 | } 61 | 62 | let containerNode = SCNNode() 63 | containerNode.addChildNode(node) 64 | 65 | return (containerNode, changedAttributes) 66 | } 67 | 68 | private func viewToImage(view: UIView) -> UIImage { 69 | let renderer = UIGraphicsImageRenderer(size: view.bounds.size) 70 | let image = renderer.image { ctx in 71 | view.drawHierarchy(in: view.bounds, afterScreenUpdates: true) 72 | } 73 | return image 74 | } 75 | } 76 | 77 | extension ViewPlane { 78 | public func doubleSided(_ bool: Bool) -> ViewPlane { 79 | var me = self 80 | me.doubleSided = bool 81 | return me 82 | } 83 | } 84 | 85 | extension ViewPlane { 86 | public func cornerRadius(_ cornerRadius: CGFloat) -> ViewPlane { 87 | var me = self 88 | me.cornerRadius = cornerRadius 89 | return me 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Swift3D XcodeProj/blank/blankApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // blankApp.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 11/4/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct blankApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/Conclusion.playgroundchapter/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Name 6 | Conclusion 7 | Pages 8 | 9 | 1HowItWorks.playgroundpage 10 | 2Conclusion.playgroundpage 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/Conclusion.playgroundchapter/Pages/1HowItWorks.playgroundpage/LiveView.swift: -------------------------------------------------------------------------------- 1 | import PlaygroundSupport 2 | import SwiftUI 3 | struct ContentView: View { 4 | var body: some View { 5 | ZStack { 6 | Rectangle() 7 | .foregroundColor(.black) 8 | Image(uiImage: UIImage(named: "protocolDesign")!) 9 | .resizable() 10 | .aspectRatio(contentMode: .fit) 11 | } 12 | .edgesIgnoringSafeArea(.all) 13 | } 14 | } 15 | 16 | let vc = UIHostingController(rootView: ContentView()) 17 | PlaygroundPage.current.liveView = vc 18 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/Conclusion.playgroundchapter/Pages/1HowItWorks.playgroundpage/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PlaygroundLoggingMode 6 | Off 7 | Name 8 | How It Works 9 | LiveViewEdgeToEdge 10 | 11 | LiveViewMode 12 | VisibleByDefault 13 | 14 | 15 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/Conclusion.playgroundchapter/Pages/1HowItWorks.playgroundpage/main.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | ## You may be wondering how this all works. I go into more depth and show what Swift features I use on my Swift Student Challenge application but here is a diagram to better highlight how Swift3D works under the hood. 4 | 5 | */ 6 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/Conclusion.playgroundchapter/Pages/2Conclusion.playgroundpage/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PlaygroundLoggingMode 6 | Off 7 | Name 8 | Conclusion 9 | LiveViewEdgeToEdge 10 | 11 | LiveViewMode 12 | HiddenByDefault 13 | 14 | 15 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/Conclusion.playgroundchapter/Pages/2Conclusion.playgroundpage/main.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | # Thank you for trying my playground! 4 | 5 | I hope that you found it interesting. Can't wait to see what's to come for WWDC21!! 6 | 7 | */ 8 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/Creating Something.playgroundchapter/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Name 6 | Creating Something 7 | Pages 8 | 9 | 1AnExample.playgroundpage 10 | 2TryItYourself.playgroundpage 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/Creating Something.playgroundchapter/Pages/1AnExample.playgroundpage/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PlaygroundLoggingMode 6 | Off 7 | Name 8 | An Example 9 | LiveViewEdgeToEdge 10 | 11 | LiveViewMode 12 | VisibleByDefault 13 | 14 | 15 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/Creating Something.playgroundchapter/Pages/1AnExample.playgroundpage/main.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | ### Now that you know the basics, you can have a go at creating something yourself. 4 | # Here's an example. 5 | 6 | I have used Stacks to bring multiple objects together, ViewPlane to bring in a SwiftUI view and multiple modifiers to move around and change how the objects look. When you press run my code you should see be able to place an AR weather widget into your world. In the future, when AR glasses are a thing, imagine having widgets in your real world! This is a great way to prototype things like that. 7 | 8 | */ 9 | import Swift3D 10 | import SwiftUI 11 | 12 | struct WeatherWidget: Object { 13 | var object: Object { 14 | // Y Stack containing the view and the cloud. See the cloud object below. 15 | Stack(.y) { 16 | ViewPlane(size: .init(width: 5.5, height: 7.5), vertical: false, body: WeatherView()) 17 | .rotation(x: -80) 18 | .opacity(0.8) 19 | Cloud() 20 | } 21 | .offset(y: -5) 22 | } 23 | } 24 | 25 | struct Cloud: Object { 26 | var object: Object { 27 | Stack(.y) { 28 | // The cloud raindrops - three capsules rotated and with blue color. 29 | Stack(.x, spacing: 0.3) { 30 | Capsule(capRadius: 0.1, height: 0.8) 31 | .rotation(z: -40) 32 | .color(.blue) 33 | Capsule(capRadius: 0.1, height: 0.9) 34 | .rotation(z: -40) 35 | .color(.blue) 36 | Capsule(capRadius: 0.1, height: 0.8) 37 | .rotation(z: -40) 38 | .color(.blue) 39 | } 40 | .offset(x: 1.1, y: 0.8) 41 | 42 | // The cloud itself - 3 differently sized spheres with different offsets. 43 | Stack(.x) { 44 | Sphere(radius: 0.6) 45 | .offset(x: 1) 46 | .opacity(0.8) 47 | Sphere(radius: 1) 48 | .offset(x: -0.5, y: 0.35) 49 | .opacity(0.95) 50 | Sphere(radius: 0.6) 51 | .offset(x: -0.5) 52 | .opacity(0.8) 53 | } 54 | } 55 | .offset(x: -2, y: -2) 56 | } 57 | } 58 | 59 | // The SwiftUI view that will appear in 3D. 60 | struct WeatherView: View { 61 | var body: some View { 62 | ZStack { 63 | RoundedRectangle(cornerRadius: 10, style: .continuous) 64 | .foregroundColor(Color.gray.opacity(0.4)) 65 | .overlay( 66 | RoundedRectangle(cornerRadius: 20, style: .continuous) 67 | .stroke(Color.gray.opacity(0.8), lineWidth: 3) 68 | ) 69 | 70 | VStack { 71 | weatherDay(day: "Monday") 72 | Divider() 73 | weatherDay(day: "Tuesday") 74 | Divider() 75 | weatherDay(day: "Wednesday") 76 | Divider() 77 | Group { 78 | weatherDay(day: "Thursday") 79 | Divider() 80 | weatherDay(day: "Friday") 81 | Divider() 82 | weatherDay(day: "Saturday") 83 | Divider() 84 | weatherDay(day: "Sunday") 85 | } 86 | } 87 | .foregroundColor(.white) 88 | .padding() 89 | } 90 | } 91 | 92 | let randomWeather = ["cloud.drizzle.fill", "cloud.bolt.rain.fill", "cloud.sun.fill", "sun.max.fill", "cloud.rain.fill"] 93 | 94 | func weatherDay(day: String) -> some View { 95 | HStack { 96 | Text(day) 97 | Spacer() 98 | Image(systemName: randomWeather.randomElement()!) 99 | .resizable() 100 | .frame(width: 15, height: 15) 101 | .padding(.horizontal) 102 | Text("\(Int.random(in: 18...24))") 103 | .padding(.horizontal) 104 | Text("\(Int.random(in: 5...13))") 105 | } 106 | .padding(.vertical, 3) 107 | } 108 | } 109 | 110 | // The content view with ARScene3D to add the weather widget to the real world. 111 | struct ContentView: View { 112 | var body: some View { 113 | ARScene3D(baseObject: WeatherWidget()) 114 | } 115 | } 116 | 117 | //#-hidden-code 118 | import PlaygroundSupport 119 | PlaygroundPage.current.setLiveView(ContentView()) 120 | //#-end-hidden-code 121 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/Creating Something.playgroundchapter/Pages/2TryItYourself.playgroundpage/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PlaygroundLoggingMode 6 | Off 7 | Name 8 | Try It Yourself 9 | LiveViewEdgeToEdge 10 | 11 | LiveViewMode 12 | HiddenByDefault 13 | 14 | 15 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/Creating Something.playgroundchapter/Pages/2TryItYourself.playgroundpage/main.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | ### Using inspiration from the last page or your own idea try creating your own object! 4 | 5 | */ 6 | import Swift3D 7 | import SwiftUI 8 | 9 | //#-editable-code 10 | struct YourObject: Object { 11 | var object: Object { 12 | } 13 | } 14 | //#-end-editable-code 15 | 16 | struct ContentView: View { 17 | var body: some View { 18 | //#-editable-code 19 | ARScene3D(baseObject: YourObject()) 20 | //#-end-editable-code 21 | } 22 | } 23 | 24 | //#-hidden-code 25 | import PlaygroundSupport 26 | PlaygroundPage.current.setLiveView(ContentView()) 27 | //#-end-hidden-code 28 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/Introduction.playgroundchapter/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Name 6 | Introduction 7 | Pages 8 | 9 | Introduction.playgroundpage 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/Introduction.playgroundchapter/Pages/Introduction.playgroundpage/LiveView.swift: -------------------------------------------------------------------------------- 1 | 2 | import PlaygroundSupport 3 | import SwiftUI 4 | import Page1 5 | 6 | let vc = UIHostingController(rootView: DefaultLiveView()) 7 | 8 | PlaygroundPage.current.liveView = vc 9 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/Introduction.playgroundchapter/Pages/Introduction.playgroundpage/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PlaygroundLoggingMode 6 | Off 7 | Name 8 | Introduction 9 | LiveViewEdgeToEdge 10 | 11 | LiveViewMode 12 | VisibleByDefault 13 | 14 | 15 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/Introduction.playgroundchapter/Pages/Introduction.playgroundpage/main.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | # Welcome to Swift3D! 4 | 5 | Hello, my name is Christian! 6 | 7 | I love SwiftUI. I have been using it constantly since it came out in 2019 and its power and ease of use are what ignited my passion for Swift development. 8 | 9 | Creating reusable views with simple declarations meant anyone could quickly learn how to make anything from a super simple app with text that says “Hello World!” to a fully-featured app. 10 | 11 | 2D is great, but in the future, there will need to be something equivalent to SwiftUI that will let us create 3D applications just as easily for use on either a 2D screen or to bring into your world with AR. I set a goal to try and create a concept of what this new framework could look like and how the features and power of the Swift language could make this possible. The perfect place to do this and present my findings is in a Swift Playground! 12 | 13 | This playground will walk you through the main concepts of Swift3D and teach you how to create your own custom 3D experiences. I hope you enjoy it! 14 | 15 | */ 16 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Name 6 | The Basics 7 | Pages 8 | 9 | 1MyFirstObject.playgroundpage 10 | 2Modifiers.playgroundpage 11 | 3Stacks.playgroundpage 12 | 4AnimatingObjects.playgroundpage 13 | 5Bringing2DInto3D.playgroundpage 14 | 6IntegrateWithSwiftUI.playgroundpage 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/1MyFirstObject.playgroundpage/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PlaygroundLoggingMode 6 | Off 7 | Name 8 | My First Object! 9 | LiveViewEdgeToEdge 10 | 11 | LiveViewMode 12 | HiddenByDefault 13 | 14 | 15 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/1MyFirstObject.playgroundpage/PrivateResources/Hint1.txt: -------------------------------------------------------------------------------- 1 | Box supports setting a custom size and chamfer radius. 2 | Try something like this: 3 | 4 | `Box(size: .init(width: 1, height: 2, length: 2), chamferRadius: 0.15)` -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/1MyFirstObject.playgroundpage/PrivateResources/Hint2.txt: -------------------------------------------------------------------------------- 1 | Sphere supports setting a custom radius to change its size. Try something like this: 2 | 3 | `Sphere(radius: 0.85)` -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/1MyFirstObject.playgroundpage/PrivateResources/Hint3.txt: -------------------------------------------------------------------------------- 1 | Pyramid supports setting a custom size. Try something like this: 2 | 3 | `Pyramid(size: .init(width: 3, height: 0.5, length: 2))` -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/1MyFirstObject.playgroundpage/PrivateResources/Hint4.txt: -------------------------------------------------------------------------------- 1 | Plane supports setting a custom size, if it should be vertical, if it should be rendered on both sides and a corner radius. Try something like this and see how it affects the resulting plane: 2 | 3 | `Plane(size: .init(width: 12, length: 12), vertical: false, doubleSided: false, cornerRadius: 1)` -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/1MyFirstObject.playgroundpage/PrivateResources/Hints.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hints 6 | 7 | 8 | FileReference 9 | Hint1.txt 10 | 11 | 12 | FileReference 13 | Hint2.txt 14 | 15 | 16 | FileReference 17 | Hint3.txt 18 | 19 | 20 | FileReference 21 | Hint4.txt 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/1MyFirstObject.playgroundpage/main.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | 3 | # Let’s get started! 4 | 5 | + Callout(It's all objects...): 6 | Just like how SwiftUI is all based on views, Swift3D is all based on objects. 7 | 8 | Just like views in SwiftUI, objects should be reusable, modifiable, and easy to combine to create larger, more complex objects. Let's see how you can make your first object. 9 | 10 | As you can see below, it's just as easy as creating a SwiftUI view. Try setting your first object to be a `Box()`, `Sphere()`, `Pyramid()` or try finding another object you like then press run my code. 11 | 12 | */ 13 | import Swift3D 14 | 15 | struct MyFirstObject: Object { 16 | var object: Object { 17 | /*#-editable-code enter your first object here!*/Box()/*#-end-editable-code*/ 18 | } 19 | } 20 | /*: 21 | First, we import Swift3D. Then we can make a `struct` that conforms to the `Object` protocol. What your object looks like should be declared in the `object` property, just like a SwiftUI views `body`. 22 | */ 23 | 24 | /*: 25 | * Experiment: Try changing the object initialisers to customise things like size, chamfer radius, radius or corner radius. 26 | 27 | 28 | + Note: Take a look at the hints in the bottom right of your screen for some examples. 29 | */ 30 | //#-hidden-code 31 | import SwiftUI 32 | import PlaygroundSupport 33 | struct MainView: View { 34 | var body: some View { 35 | Scene3D(baseObject: MyFirstObject(), realisticLighting: false) 36 | .backgroundImage(UIImage(named: "mainSceneBackground")!) 37 | } 38 | } 39 | PlaygroundPage.current.setLiveView(MainView()) 40 | //#-end-hidden-code 41 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/2Modifiers.playgroundpage/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PlaygroundLoggingMode 6 | Off 7 | Name 8 | Modifying Objects 9 | LiveViewEdgeToEdge 10 | 11 | LiveViewMode 12 | HiddenByDefault 13 | 14 | 15 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/2Modifiers.playgroundpage/main.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | # Modifying Objects 3 | 4 | We can customize objects to look and behave however we like with modifiers. Some basic modifiers include `color(_ : Color)`, `offset(_ : Size3D)` and `opacity(_ : CGFloat)`. 5 | 6 | Try using them yourself, then tapping run my code to see them work! 7 | 8 | */ 9 | import Swift3D 10 | 11 | struct ModifiedObject: Object { 12 | var object: Object { 13 | /*#-editable-code*/Box()/*#-end-editable-code*/ 14 | .color(/*#-editable-code*/.white/*#-end-editable-code*/) 15 | .offset(x: /*#-editable-code*/0/*#-end-editable-code*/, y: /*#-editable-code*/0/*#-end-editable-code*/, z: /*#-editable-code*/0/*#-end-editable-code*/) 16 | .opacity(/*#-editable-code*/1.0/*#-end-editable-code*/) 17 | } 18 | } 19 | //#-hidden-code 20 | import SwiftUI 21 | import PlaygroundSupport 22 | struct MainView: View { 23 | var body: some View { 24 | Scene3D(baseObject: ModifiedObject(), realisticLighting: false) 25 | .backgroundImage(UIImage(named: "mainSceneBackground")!) 26 | } 27 | } 28 | 29 | PlaygroundPage.current.setLiveView(MainView()) 30 | //#-end-hidden-code 31 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/3Stacks.playgroundpage/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PlaygroundLoggingMode 6 | Off 7 | Name 8 | Combining Objects With Stacks 9 | LiveViewEdgeToEdge 10 | 11 | LiveViewMode 12 | HiddenByDefault 13 | 14 | 15 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/3Stacks.playgroundpage/main.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | # Stacks! 3 | 4 | This is useless, you might say. It's just basic shapes with simple modifications, right? And you would be right. But with the power of Swift, making stacks of objects is easy, with simple declarations. 5 | 6 | Swift3D uses `Stack` to combine objects into one single object. A stack can be in the `x`, `y`, or `z` axis. 7 | 8 | Try doing it yourself below. Set the axis you want to make the stack in and the objects you want to combine. 9 | */ 10 | import Swift3D 11 | 12 | struct TrulyCustomObject: Object { 13 | var object: Object { 14 | Stack(/*#-editable-code*/.x/*#-end-editable-code*/) { 15 | /*#-editable-code*/Box()/*#-end-editable-code*/ 16 | /*#-editable-code*/Box()/*#-end-editable-code*/ 17 | } 18 | } 19 | } 20 | /*: 21 | + Experiment: Try changing the spacing between object in the stack. 22 | 23 | * Callout(Fun Fact:): 24 | Stacks work because of the new `@resultBuilder` (aka `@functionBuilder`) feature in Swift 5. It lets you return *something* from a list of declarations. In this case `Stack` works by taking in an axis as well as an array of Objects constructed with an `@ObjectBuilder`. This is similar to how SwiftUI uses `@ViewBuilder` to construct views from its stacks, groups and more. 25 | */ 26 | //#-hidden-code 27 | import SwiftUI 28 | import PlaygroundSupport 29 | struct MainView: View { 30 | var body: some View { 31 | Scene3D(baseObject: TrulyCustomObject(), realisticLighting: false) 32 | .backgroundImage(UIImage(named: "mainSceneBackground")!) 33 | } 34 | } 35 | 36 | PlaygroundPage.current.setLiveView(MainView()) 37 | //#-end-hidden-code 38 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/4AnimatingObjects.playgroundpage/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PlaygroundLoggingMode 6 | Off 7 | Name 8 | Animating Objects 9 | LiveViewEdgeToEdge 10 | 11 | LiveViewMode 12 | HiddenByDefault 13 | 14 | 15 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/4AnimatingObjects.playgroundpage/PrivateResources/Hint1.txt: -------------------------------------------------------------------------------- 1 | You can change the animation to use whatever animation curve/duration you like. 2 | Try something like this: 3 | 4 | `.animation(.linear(duration: 2.3))` -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/4AnimatingObjects.playgroundpage/PrivateResources/Hint2.txt: -------------------------------------------------------------------------------- 1 | You can set an animation to run forever! 2 | Try something like this: 3 | 4 | `.animation(Animation3D.easeOut.repeatForever())` -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/4AnimatingObjects.playgroundpage/PrivateResources/Hints.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Hints 6 | 7 | 8 | FileReference 9 | Hint1.txt 10 | 11 | 12 | FileReference 13 | Hint2.txt 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/4AnimatingObjects.playgroundpage/main.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | # Animating Objects! 3 | 4 | It's boring when things are still. Give them some life with animations! You can easily add an animation with the animation modifier. It works just like in SwiftUI. When the value of another modifier such as opacity, offset or color changes, it will animate that change. 5 | 6 | 7 | ### How can an object be animated? 8 | 9 | To see how this works, we need to know how to modify values in our object. 10 | 11 | **First**, just like how SwiftUI includes `@State`, we can use `@State3D` instead before a variable to make sure our object is recreated to represent it's new form when the state value changes. 12 | 13 | **Second**, there needs to be a way to modify this state variable. This could be external or could simply be when the object appears. The `onAppear` modifier runs a block of the code when the object is displayed on your screen. 14 | */ 15 | 16 | /*: 17 | Below, you see a new object with a state variable. The object has multiple modifiers applied to it that change depending on if the variable is `true` or `false`. The animation modifier specifies the animation to run when these values change. 18 | 19 | **Try modifying the values yourself and tap run my code to see what happens!** 20 | */ 21 | import Swift3D 22 | 23 | struct AnimatedObject: Object { 24 | @State3D private var specialVariable: Bool = true 25 | 26 | var object: Object { 27 | /*#-editable-code*/Box(chamferRadius: 0.1)/*#-end-editable-code*/ 28 | /*#-editable-code*/.opacity(specialVariable ? 0.6 : 1)/*#-end-editable-code*/ 29 | /*#-editable-code*/.offset(x: specialVariable ? 0 : 1)/*#-end-editable-code*/ 30 | /*#-editable-code*/.color(specialVariable ? .green : .red)/*#-end-editable-code*/ 31 | /*#-editable-code*/.animation(.easeInOut)/*#-end-editable-code*/ 32 | .onAppear { 33 | specialVariable = false 34 | } 35 | } 36 | } 37 | /*: 38 | * Experiment: Try changing the animation to use a different method and add your own duration. Also try making your animation run forever! 39 | 40 | + Note: Take a look at the hints in the bottom right of your screen if you need some help. 41 | */ 42 | //#-hidden-code 43 | import SwiftUI 44 | import PlaygroundSupport 45 | struct MainView: View { 46 | var body: some View { 47 | Scene3D(baseObject: AnimatedObject(), realisticLighting: false) 48 | .backgroundImage(UIImage(named: "mainSceneBackground")!) 49 | } 50 | } 51 | 52 | PlaygroundPage.current.setLiveView(MainView()) 53 | //#-end-hidden-code 54 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/5Bringing2DInto3D.playgroundpage/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PlaygroundLoggingMode 6 | Off 7 | Name 8 | Bringing 2D Into 3D 9 | LiveViewEdgeToEdge 10 | 11 | LiveViewMode 12 | HiddenByDefault 13 | 14 | 15 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/5Bringing2DInto3D.playgroundpage/main.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | # Bringing 2D Into 3D 3 | 4 | Swift3D was created with the future of a world full of 3D and AR(more on that next) in the future. 2D views are still very important in a 3D space and there needs to be a way to incorporate them easily. Swift3D lets you do this! 5 | */ 6 | 7 | /*: 8 | Simply just add a `ViewPlane` to your object. `ViewPlane` takes in a closure that should be a SwiftUI view and creates a plane with that view ontop. 9 | */ 10 | import Swift3D 11 | import SwiftUI 12 | 13 | struct TwoDInThreeD: Object { 14 | var object: Object { 15 | ViewPlane(vertical: true) { 16 | //#-editable-code 17 | VStack { 18 | Text("This is a 2D SwiftUI view inside a 3D world!") 19 | .font(.title) 20 | .padding() 21 | Text("Try adding your own view here!") 22 | } 23 | //#-end-editable-code 24 | } 25 | } 26 | } 27 | //#-hidden-code 28 | import PlaygroundSupport 29 | struct MainView: View { 30 | var body: some View { 31 | Scene3D(baseObject: TwoDInThreeD(), realisticLighting: false) 32 | .backgroundImage(UIImage(named: "mainSceneBackground")!) 33 | } 34 | } 35 | 36 | PlaygroundPage.current.setLiveView(MainView()) 37 | //#-end-hidden-code 38 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/6IntegrateWithSwiftUI.playgroundpage/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PlaygroundLoggingMode 6 | Off 7 | Name 8 | Integrate Into SwiftUI 9 | LiveViewEdgeToEdge 10 | 11 | LiveViewMode 12 | HiddenByDefault 13 | 14 | 15 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Chapters/The Basics.playgroundchapter/Pages/6IntegrateWithSwiftUI.playgroundpage/main.swift: -------------------------------------------------------------------------------- 1 | /*: 2 | # Interation with SwiftUI 3 | 4 | You can bring your custom 3D objects into a 2D application with `Scene3D` and `ARScene3D`. These views take in your base custom object and will automatically configure lighting, camera and more for you. 5 | 6 | Try turning `Scene3D` below into the AR version, `ARScene3D`, then set `realisticLighting` to `true` and run it to see your object in the real world! 7 | */ 8 | import Swift3D 9 | import SwiftUI 10 | 11 | struct RandomObject: Object { 12 | var object: Object { 13 | //#-editable-code 14 | Stack(.y) { 15 | Box() 16 | .color(.red) 17 | Sphere() 18 | .color(.blue) 19 | } 20 | //#-end-editable-code 21 | } 22 | } 23 | 24 | struct ContentView: View { 25 | var body: some View { 26 | //#-editable-code 27 | Scene3D(baseObject: RandomObject(), realisticLighting: false) 28 | //#-end-editable-code 29 | } 30 | } 31 | //#-hidden-code 32 | import PlaygroundSupport 33 | 34 | PlaygroundPage.current.setLiveView(ContentView()) 35 | //#-end-hidden-code 36 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/Manifest.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ImageReference 6 | iconnew.png 7 | Chapters 8 | 9 | Introduction.playgroundchapter 10 | The Basics.playgroundchapter 11 | Creating Something.playgroundchapter 12 | Conclusion.playgroundchapter 13 | 14 | ContentIdentifier 15 | com.apple.playgrounds.blank 16 | ContentVersion 17 | 1.0 18 | DeploymentTarget 19 | ios-current 20 | DevelopmentRegion 21 | en 22 | SwiftVersion 23 | 5.3 24 | Version 25 | 8.0 26 | UserAutoImportedAuxiliaryModules 27 | 28 | UserModuleMode 29 | Full 30 | 31 | 32 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/PublicResources/iconnew.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Priva28/Swift3D/6a8f89c24d8d25aabe52b780306bedde965c7e8b/Swift3D.playgroundbook/Contents/PublicResources/iconnew.png -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/PublicResources/iconold.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Priva28/Swift3D/6a8f89c24d8d25aabe52b780306bedde965c7e8b/Swift3D.playgroundbook/Contents/PublicResources/iconold.png -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/PublicResources/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Priva28/Swift3D/6a8f89c24d8d25aabe52b780306bedde965c7e8b/Swift3D.playgroundbook/Contents/PublicResources/logo.png -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/PublicResources/mainSceneBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Priva28/Swift3D/6a8f89c24d8d25aabe52b780306bedde965c7e8b/Swift3D.playgroundbook/Contents/PublicResources/mainSceneBackground.png -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/PublicResources/meshGradientBackground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Priva28/Swift3D/6a8f89c24d8d25aabe52b780306bedde965c7e8b/Swift3D.playgroundbook/Contents/PublicResources/meshGradientBackground.png -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/PublicResources/protocolDesign.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Priva28/Swift3D/6a8f89c24d8d25aabe52b780306bedde965c7e8b/Swift3D.playgroundbook/Contents/PublicResources/protocolDesign.png -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Page1.playgroundmodule/Sources/DefaultLiveView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | public struct DefaultLiveView: View { 4 | @State var orb: Bool = false 5 | public init() { 6 | self._orb = State(initialValue: false) 7 | } 8 | public var body: some View { 9 | ZStack { 10 | Image(uiImage: #imageLiteral(resourceName: "meshGradientBackground.png")) 11 | .resizable() 12 | .aspectRatio(contentMode: .fill) 13 | .saturation(1.1) 14 | .brightness(-0.13) 15 | VStack { 16 | SquareLogo() 17 | .padding(30) 18 | Rectangle() 19 | .frame(width: 300, height: 30) 20 | .foregroundColor(.white) 21 | .opacity(1) 22 | .blur(radius: 15) 23 | .mask( 24 | Text("Swift3D") 25 | .bold() 26 | .font(.largeTitle) 27 | ) 28 | .scaleEffect(1.1) 29 | Rectangle() 30 | .frame(width: 400, height: 30) 31 | .foregroundColor(.white) 32 | .opacity(1) 33 | .blur(radius: 10) 34 | .mask( 35 | Text("A 3D framework for everyone.") 36 | .font(.title) 37 | ) 38 | } 39 | .offset(y: -20) 40 | 41 | OrbThingy( 42 | size: 38, 43 | color1: .init(red: 253/255, green: 171/255, blue: 179/255), 44 | color2: .init(red: 255/255, green: 097/255, blue: 146/255), 45 | shadow1: .init(red: 1, green: 124/255, blue: 137/255), 46 | shadow2: .init(red: 1, green: 132/255, blue: 137/255) 47 | ) 48 | .offset(x: -162, y: -240) 49 | .offset(y: orb ? 15 : 0) 50 | 51 | OrbThingy( 52 | size: 76, 53 | color1: .init(red: 250/255, green: 170/255, blue: 192/255), 54 | color2: .init(red: 155/255, green: 163/255, blue: 249/255), 55 | shadow1: .init(red: 119/255, green: 110/255, blue: 255/255), 56 | shadow2: Color.white.opacity(0.3) 57 | ) 58 | .offset(x: 185, y: 262) 59 | .offset(y: orb ? 8 : 0) 60 | 61 | OrbThingy( 62 | size: 53, 63 | color1: .init(red: 130/255, green: 167/255, blue: 250/255), 64 | color2: .init(red: 249/255, green: 137/255, blue: 146/255), 65 | shadow1: .init(red: 175/255, green: 124/255, blue: 137/255), 66 | shadow2: .clear 67 | ) 68 | .offset(x: 150, y: 230) 69 | .offset(y: orb ? 12 : 0) 70 | 71 | 72 | } 73 | .onAppear { 74 | withAnimation(Animation.easeInOut(duration: 3).repeatForever(autoreverses: true)) { 75 | orb.toggle() 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Page1.playgroundmodule/Sources/OrbThingy.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | public struct OrbThingy: View { 4 | // if anyone at apple is looking at this code... pls make memberwise inits public by default so i dont have to do this every time :) 5 | public init( 6 | size: CGFloat, 7 | color1: Color, 8 | color2: Color, 9 | shadow1: Color, 10 | shadow2: Color, 11 | startPoint: UnitPoint = .top, 12 | endPoint: UnitPoint = .bottom 13 | ) { 14 | self.size = size 15 | self.color1 = color1 16 | self.color2 = color2 17 | self.shadow1 = shadow1 18 | self.shadow2 = shadow2 19 | self.startPoint = startPoint 20 | self.endPoint = endPoint 21 | } 22 | 23 | private var size: CGFloat 24 | private var color1: Color 25 | private var color2: Color 26 | private var shadow1: Color 27 | private var shadow2: Color 28 | private var startPoint: UnitPoint = .top 29 | private var endPoint: UnitPoint = .bottom 30 | 31 | public var body: some View { 32 | Circle() 33 | .fill(LinearGradient( 34 | gradient: Gradient(colors: [color1, color2]), 35 | startPoint: .top, 36 | endPoint: .bottom 37 | )) 38 | .frame(width: size, height: size) 39 | .overlay( 40 | RadialGradient( 41 | gradient: Gradient(colors: [.white, Color.white.opacity(0.00001)]), 42 | center: .center, 43 | startRadius: 0, 44 | endRadius: size/6.5 45 | ) 46 | .blur(radius: size/6) 47 | .offset(x: -(size/7), y: -(size/7)) 48 | ) 49 | .drawingGroup() 50 | .innerShadow( 51 | Circle(), 52 | upperShadow: Color.white, 53 | lowerShadow: Color.black, 54 | spread: size/650, 55 | radius: size/8 56 | ) 57 | .shadow(color: shadow1, radius: 5, x: 3, y: 3) 58 | .shadow(color: shadow2, radius: 6, x: -3, y: -3) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Page1.playgroundmodule/Sources/SquareLogo.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct SquareLogo: View { 4 | var body: some View { 5 | ZStack { 6 | 7 | square( 8 | size: 282, 9 | cornerRadius: 70, 10 | foregroundColor: .init(red: 1, green: 0.13, blue: 0.13) 11 | ) 12 | .shadow(color: Color.white.opacity(0.7), radius: 23) 13 | 14 | square( 15 | size: 268, 16 | cornerRadius: 68, 17 | foregroundColor: .init(red: 0.84, green: 0.12, blue: 0.09) 18 | ) 19 | 20 | square( 21 | size: 254, 22 | cornerRadius: 66, 23 | foregroundColor: .init(red: 0.7, green: 0.1, blue: 0.01) 24 | ) 25 | 26 | square( 27 | size: 240, 28 | cornerRadius: 64, 29 | foregroundColor: .init(red: 89/255, green: 8/255, blue: 2/255) 30 | ) 31 | Image(uiImage: UIImage(named: "logo")!) 32 | .resizable() 33 | .frame(width: 190, height: 190) 34 | .aspectRatio(contentMode: .fit) 35 | // update 36 | .shadow(radius: 18) 37 | } 38 | .opacity(0.6) 39 | } 40 | 41 | func square( 42 | size: CGFloat, 43 | cornerRadius: CGFloat, 44 | foregroundColor: Color 45 | ) -> some View { 46 | RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) 47 | .frame(width: size, height: size) 48 | .foregroundColor(foregroundColor) 49 | .innerShadow( 50 | RoundedRectangle(cornerRadius: cornerRadius, style: .continuous), 51 | upperShadow: Color(red: 215/255, green: 200/255, blue: 200/255), 52 | lowerShadow: Color.black.opacity(70), 53 | spread: 0.05, 54 | radius: 25 55 | ) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Page1.playgroundmodule/Sources/View+Foreground.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | extension View { 4 | public func foreground( 5 | _ overlay: Overlay 6 | ) -> some View { 7 | self.overlay(overlay).mask(self) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Page1.playgroundmodule/Sources/View+InnerShadow.swift: -------------------------------------------------------------------------------- 1 | // Code from https://github.com/costachung/neumorphic 2 | // License is at the bottom of the file. 3 | 4 | import SwiftUI 5 | 6 | extension View { 7 | func inverseMask(_ mask: Mask) -> some View { 8 | self.mask( 9 | mask 10 | .foregroundColor(.black) 11 | .background(Color.white) 12 | .compositingGroup() 13 | .luminanceToAlpha() 14 | ) 15 | } 16 | } 17 | 18 | private struct SoftInnerShadowViewModifier : ViewModifier { 19 | var shape: S 20 | var upperShadow : Color = .black 21 | var lowerShadow : Color = .white 22 | var spread: CGFloat = 0.5 //The value of spread is between 0 to 1. Higher value makes the shadow look more intense. 23 | var radius: CGFloat = 10 24 | 25 | init(shape: S, upperShadow: Color, lowerShadow: Color, spread: CGFloat, radius:CGFloat) { 26 | self.shape = shape 27 | self.upperShadow = upperShadow 28 | self.lowerShadow = lowerShadow 29 | self.spread = spread 30 | self.radius = radius 31 | } 32 | 33 | fileprivate func strokeLineWidth(_ geo: GeometryProxy) -> CGFloat { 34 | return geo.size.width * 0.10 35 | } 36 | 37 | fileprivate func strokeLineScale(_ geo: GeometryProxy) -> CGFloat { 38 | let lineWidth = strokeLineWidth(geo) 39 | return geo.size.width / CGFloat(geo.size.width - lineWidth) 40 | } 41 | 42 | fileprivate func shadowOffset(_ geo: GeometryProxy) -> CGFloat { 43 | return (geo.size.width <= geo.size.height ? geo.size.width : geo.size.height) * 0.5 * min(max(spread, 0), 1) 44 | } 45 | 46 | 47 | fileprivate func addSoftInnerShadow(_ content: SoftInnerShadowViewModifier.Content) -> some View { 48 | return GeometryReader { geo in 49 | 50 | self.shape.fill(self.lowerShadow) 51 | .inverseMask( 52 | self.shape 53 | .offset(x: -self.shadowOffset(geo), y: -self.shadowOffset(geo)) 54 | ) 55 | .offset(x: self.shadowOffset(geo) , y: self.shadowOffset(geo)) 56 | .blur(radius: self.radius) 57 | .shadow(color: self.lowerShadow, radius: self.radius, x: -self.shadowOffset(geo)/2, y: -self.shadowOffset(geo)/2 ) 58 | .mask( 59 | self.shape 60 | ) 61 | .overlay( 62 | self.shape 63 | .fill(self.upperShadow) 64 | .inverseMask( 65 | self.shape 66 | .offset(x: self.shadowOffset(geo), y: self.shadowOffset(geo)) 67 | ) 68 | .offset(x: -self.shadowOffset(geo) , y: -self.shadowOffset(geo)) 69 | .blur(radius: self.radius) 70 | .shadow(color: self.upperShadow, radius: self.radius, x: self.shadowOffset(geo)/2, y: self.shadowOffset(geo)/2 ) 71 | ) 72 | .mask( 73 | self.shape 74 | ) 75 | } 76 | } 77 | 78 | func body(content: Content) -> some View { 79 | content.overlay( 80 | addSoftInnerShadow(content) 81 | ) 82 | } 83 | } 84 | 85 | 86 | //For more readable, we extend the View and create a softInnerShadow function. 87 | extension View { 88 | public func innerShadow( 89 | _ content: S, 90 | upperShadow: Color = .black, 91 | lowerShadow: Color = Color.white.opacity(0.7), 92 | spread: CGFloat = 0.1, 93 | radius: CGFloat = 10 94 | ) -> some View { 95 | modifier( 96 | SoftInnerShadowViewModifier( 97 | shape: content, 98 | upperShadow: upperShadow, 99 | lowerShadow: lowerShadow, 100 | spread: spread, 101 | radius: radius 102 | ) 103 | ) 104 | } 105 | } 106 | 107 | /* 108 | MIT License 109 | 110 | Copyright (c) 2020 Costa Chung 111 | 112 | Permission is hereby granted, free of charge, to any person obtaining a copy 113 | of this software and associated documentation files (the "Software"), to deal 114 | in the Software without restriction, including without limitation the rights 115 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 116 | copies of the Software, and to permit persons to whom the Software is 117 | furnished to do so, subject to the following conditions: 118 | 119 | The above copyright notice and this permission notice shall be included in all 120 | copies or substantial portions of the Software. 121 | 122 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 123 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 124 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 125 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 126 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 127 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 128 | SOFTWARE. 129 | */ 130 | 131 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/ARScene.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARScene.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 16/4/21. 6 | // 7 | 8 | import SwiftUI 9 | import ARKit 10 | 11 | public struct ARScene3D: UIViewRepresentable, Scene3DProtocol { 12 | public init(baseObject: Object, realisticLighting: Bool = true) { 13 | self.realisticLighting = realisticLighting 14 | self.baseObject = baseObject 15 | 16 | // Setup AR view. 17 | arView = ARSCNView(frame: .init(x: 1, y: 1, width: 1, height: 1)) 18 | coachingOverlay = ARCoachingOverlayView() 19 | 20 | configureARView() 21 | configureCoachingOverlay() 22 | self.baseObject.bindProperties(update) 23 | } 24 | 25 | public func makeUIView(context: Context) -> ARSCNView { 26 | coachingOverlay.delegate = context.coordinator 27 | arView.delegate = context.coordinator 28 | return arView 29 | } 30 | 31 | internal var scene: SCNScene { arView.scene } 32 | internal var realisticLighting: Bool 33 | internal var baseObject: Object 34 | private var arView: ARSCNView 35 | private var coachingOverlay: ARCoachingOverlayView 36 | 37 | func configureARView() { 38 | let config = ARWorldTrackingConfiguration() 39 | config.planeDetection = .horizontal 40 | config.environmentTexturing = .automatic 41 | arView.autoenablesDefaultLighting = !realisticLighting 42 | arView.automaticallyUpdatesLighting = !realisticLighting 43 | arView.session.run(config) 44 | } 45 | 46 | func configureCoachingOverlay() { 47 | coachingOverlay.autoresizingMask = [ 48 | .flexibleWidth, .flexibleHeight 49 | ] 50 | coachingOverlay.goal = .horizontalPlane 51 | coachingOverlay.session = arView.session 52 | arView.addSubview(coachingOverlay) 53 | } 54 | } 55 | 56 | // MARK: - Coordinator 57 | 58 | extension ARScene3D { 59 | public func makeCoordinator() -> Coordinator { 60 | Coordinator(self) 61 | } 62 | 63 | public class Coordinator: NSObject, ARSCNViewDelegate, ARCoachingOverlayViewDelegate { 64 | init(_ arScene: ARScene3D) { 65 | self.parent = arScene 66 | } 67 | 68 | var parent: ARScene3D 69 | var scene: SCNScene { parent.arView.scene } 70 | var arView: ARSCNView { parent.arView } 71 | 72 | var raycastFirstResult: ARRaycastResult! 73 | 74 | // MARK: - ARCoachingOverlayViewDelegate Methods 75 | public func coachingOverlayViewWillActivate(_ coachingOverlayView: ARCoachingOverlayView) { 76 | resetAR() 77 | } 78 | 79 | public func coachingOverlayViewDidRequestSessionReset(_ coachingOverlayView: ARCoachingOverlayView) { 80 | resetAR() 81 | } 82 | 83 | func resetAR() { 84 | let configuration = ARWorldTrackingConfiguration() 85 | configuration.planeDetection = [.horizontal] 86 | _ = scene.rootNode.childNodes.map { $0.removeFromParentNode() } 87 | arView.session.run( 88 | configuration, 89 | options: [.resetTracking, .removeExistingAnchors, .resetSceneReconstruction, .stopTrackedRaycasts] 90 | ) 91 | } 92 | 93 | public func coachingOverlayViewDidDeactivate(_ coachingOverlayView: ARCoachingOverlayView) { 94 | guard let query = arView.raycastQuery( 95 | from: arView.screenCenter, 96 | allowing: .existingPlaneInfinite, 97 | alignment: .horizontal 98 | ) else { return } 99 | 100 | let raycastResults = arView.session.raycast(query) 101 | if raycastResults.isEmpty { 102 | print("FAIL THIS WONT HAPPEN THO") 103 | } else if let result = raycastResults.first { 104 | let anchor = ARAnchor(name: "BaseObjectAnchor", transform: result.worldTransform) 105 | raycastFirstResult = result 106 | arView.session.add(anchor: anchor) 107 | } 108 | } 109 | 110 | public func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { 111 | if anchor.name == "BaseObjectAnchor" { 112 | // Make the node face the user. 113 | let rotate = simd_float4x4( 114 | SCNMatrix4MakeRotation(arView.session.currentFrame!.camera.eulerAngles.y, 0, 1, 0) 115 | ) 116 | let rotateTransform = simd_mul(raycastFirstResult.worldTransform, rotate) 117 | node.transform = SCNMatrix4(rotateTransform) 118 | 119 | // Create the plane that will display shadows from lights 120 | let plane = SCNPlane(width: 2, height: 2) 121 | plane.heightSegmentCount = 1 122 | plane.widthSegmentCount = 1 123 | 124 | let planeNode = SCNNode(geometry: plane) 125 | planeNode.renderingOrder = -10 126 | planeNode.castsShadow = false 127 | planeNode.geometry?.firstMaterial?.diffuse.contents = UIColor(red: 1, green: 1, blue: 1, alpha: 1) 128 | planeNode.geometry?.firstMaterial?.colorBufferWriteMask = SCNColorMask(rawValue: 0) 129 | planeNode.geometry?.firstMaterial?.lightingModel = .physicallyBased 130 | planeNode.geometry?.firstMaterial?.isDoubleSided = true 131 | planeNode.transform = SCNMatrix4MakeRotation(-Float.pi / 2, 1, 0, 0) 132 | 133 | let baseObject = parent.baseObject.scnNode 134 | let baseNodeContainer = SCNNode() 135 | baseNodeContainer.addChildNode(baseObject) 136 | baseNodeContainer.scale = SCNVector3(0.1, 0.1, 0.1) 137 | baseNodeContainer.position.y = (baseObject.boundingBox.max.y*0.1) 138 | 139 | let ambientLightNode = parent.ambientLight 140 | ambientLightNode.light?.intensity = 1000 141 | let directionalLightNode = parent.directionalLight 142 | 143 | arView.prepare( 144 | [baseNodeContainer, planeNode, ambientLightNode, directionalLightNode] 145 | ) { _ in 146 | if self.parent.realisticLighting { 147 | node.addChildNode(ambientLightNode) 148 | node.addChildNode(directionalLightNode) 149 | } 150 | node.addChildNode(baseNodeContainer) 151 | node.addChildNode(planeNode) 152 | } 153 | } 154 | } 155 | } 156 | } 157 | 158 | // Required but unused method. 159 | extension ARScene3D { 160 | public func updateUIView(_ uiView: ARSCNView, context: Context) { } 161 | } 162 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/Animation3D.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Animation3D.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 15/4/21. 6 | // 7 | 8 | import SceneKit 9 | import SwiftUI 10 | 11 | public struct Animation3D { 12 | private init(option: SCNActionTimingMode, duration: Double, repeatForever: Bool = false) { 13 | self.option = option 14 | self.duration = duration 15 | self.repeatForever = repeatForever 16 | } 17 | var option: SCNActionTimingMode 18 | var duration: Double 19 | var repeatForever: Bool 20 | enum Options { 21 | case easeInOut 22 | case easeIn 23 | case easeOut 24 | case linear 25 | } 26 | 27 | // Ease In Out 28 | public static func easeInOut(duration: Double) -> Animation3D { 29 | return Animation3D(option: .easeInEaseOut, duration: duration) 30 | } 31 | public static var easeInOut: Animation3D { easeInOut(duration: 1) } 32 | 33 | // Ease In 34 | public static func easeIn(duration: Double) -> Animation3D { 35 | return Animation3D(option: .easeIn, duration: duration) 36 | } 37 | public static var easeIn: Animation3D { easeIn(duration: 1) } 38 | 39 | // Ease Out 40 | public static func easeOut(duration: Double) -> Animation3D { 41 | return Animation3D(option: .easeOut, duration: duration) 42 | } 43 | public static var easeOut: Animation3D { easeOut(duration: 1) } 44 | 45 | public static func linear(duration: Double) -> Animation3D { 46 | return Animation3D(option: .linear, duration: duration) 47 | } 48 | public static var linear: Animation3D { linear(duration: 1) } 49 | 50 | public func repeatForever(autoreverses: Bool = true) -> Animation3D { 51 | var me = self 52 | me.repeatForever = true 53 | return me 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/Box.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Box.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | public struct Box: Object { 11 | public init(size: Size3D = .init(width: 1, height: 1, length: 1), chamferRadius: CGFloat = 0) { 12 | self.size = size 13 | self.chamferRadius = chamferRadius 14 | } 15 | 16 | public var object: Object { self } 17 | public var attributes = ObjectAttributes() 18 | 19 | private let size: Size3D 20 | private var chamferRadius: CGFloat 21 | } 22 | 23 | extension Box { 24 | public func renderScnNode() -> (SCNNode, [Attributes]) { 25 | let box = SCNBox(width: size.width, height: size.height, length: size.length, chamferRadius: chamferRadius) 26 | var node = SCNNode(geometry: box) 27 | let changedAttributes = applyAttributes(to: &node) 28 | return (node, changedAttributes) 29 | } 30 | } 31 | 32 | extension Box { 33 | public func chamferRadius(_ radius: CGFloat) -> Box { 34 | var me = self 35 | me.chamferRadius = radius 36 | return me 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/Capsule.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Capsule.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 17/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | public struct Capsule: Object { 11 | public init(capRadius: CGFloat = 0.2, height: CGFloat = 1) { 12 | self.capRadius = capRadius 13 | self.height = height 14 | } 15 | 16 | public var object: Object { self } 17 | public var attributes = ObjectAttributes() 18 | 19 | private let capRadius: CGFloat 20 | private let height: CGFloat 21 | } 22 | 23 | extension Capsule { 24 | public func renderScnNode() -> (SCNNode, [Attributes]) { 25 | let capsule = SCNCapsule(capRadius: capRadius, height: height) 26 | var node = SCNNode(geometry: capsule) 27 | let changedAttributes = applyAttributes(to: &node) 28 | return (node, changedAttributes) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/Cone.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cone.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 17/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | public struct Cone: Object { 11 | public init(topRadius: CGFloat = 0, bottomRadius: CGFloat = 0.5, height: CGFloat = 1) { 12 | self.topRadius = topRadius 13 | self.bottomRadius = bottomRadius 14 | self.height = height 15 | } 16 | 17 | public var object: Object { self } 18 | public var attributes = ObjectAttributes() 19 | 20 | private let topRadius: CGFloat 21 | private let bottomRadius: CGFloat 22 | private let height: CGFloat 23 | } 24 | 25 | extension Cone { 26 | public func renderScnNode() -> (SCNNode, [Attributes]) { 27 | let cone = SCNCone(topRadius: topRadius, bottomRadius: bottomRadius, height: height) 28 | var node = SCNNode(geometry: cone) 29 | let changedAttributes = applyAttributes(to: &node) 30 | return (node, changedAttributes) 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/Cylinder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cylinder.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 17/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | public struct Cylinder: Object { 11 | public init(radius: CGFloat = 0.5, height: CGFloat = 1) { 12 | self.radius = radius 13 | self.height = height 14 | } 15 | 16 | public var object: Object { self } 17 | public var attributes = ObjectAttributes() 18 | 19 | private let radius: CGFloat 20 | private let height: CGFloat 21 | } 22 | 23 | extension Cylinder { 24 | public func renderScnNode() -> (SCNNode, [Attributes]) { 25 | let cylinder = SCNCylinder(radius: radius, height: height) 26 | var node = SCNNode(geometry: cylinder) 27 | let changedAttributes = applyAttributes(to: &node) 28 | return (node, changedAttributes) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/DynamicProperty3D.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARDynamicProperty.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | public protocol DynamicProperty3D { 9 | var update: () -> Void { get set } 10 | } 11 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/EmptyObject.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmptyObject.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | import Foundation 9 | 10 | public struct EmptyObject: Object { 11 | public init() { } 12 | 13 | public var object: Object { self } 14 | public var attributes = ObjectAttributes() 15 | } 16 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/Helpers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARHelpers.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | import ARKit 9 | 10 | public enum XYZ: String { 11 | case x = "x" 12 | case y = "y" 13 | case z = "z" 14 | } 15 | 16 | public struct Size3D { 17 | public init(width: CGFloat, height: CGFloat, length: CGFloat) { 18 | self.width = width 19 | self.height = height 20 | self.length = length 21 | } 22 | 23 | public var width: CGFloat 24 | public var height: CGFloat 25 | public var length: CGFloat 26 | } 27 | 28 | public struct Location3D { 29 | public init(x: Float, y: Float, z: Float) { 30 | self.x = x 31 | self.y = y 32 | self.z = z 33 | } 34 | 35 | public var x: Float 36 | public var y: Float 37 | public var z: Float 38 | 39 | static public var zero: Location3D { 40 | return Location3D(x: 0, y: 0, z: 0) 41 | } 42 | } 43 | 44 | extension Location3D { 45 | public var scnVector3: SCNVector3 { 46 | return SCNVector3(self.x, self.y, self.z) 47 | } 48 | } 49 | 50 | extension SCNActionTimingMode { 51 | func caMediaTimingFunction() -> CAMediaTimingFunction { 52 | switch self { 53 | case .easeIn: 54 | return CAMediaTimingFunction(name: .easeIn) 55 | case .easeInEaseOut: 56 | return CAMediaTimingFunction(name: .easeInEaseOut) 57 | case .easeOut: 58 | return CAMediaTimingFunction(name: .easeOut) 59 | case .linear: 60 | return CAMediaTimingFunction(name: .linear) 61 | default: 62 | return CAMediaTimingFunction(name: .linear) 63 | } 64 | } 65 | } 66 | 67 | extension ARSCNView { 68 | var screenCenter: CGPoint { 69 | return CGPoint(x: bounds.midX, y: bounds.midY) 70 | } 71 | } 72 | 73 | func deg2rad(_ number: Float) -> Float { 74 | return number * .pi / 180 75 | } 76 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/Object.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Object.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | import SceneKit 9 | import Combine 10 | 11 | public protocol Object: ObjectSupportedAttributes, ObjectGroup { 12 | var object: Object { get } 13 | var scnNode: SCNNode { get } 14 | var id: String { get } 15 | func renderScnNode() -> (SCNNode, [Attributes]) 16 | } 17 | 18 | extension Object { 19 | public var objects: [Object] { [self] } 20 | } 21 | 22 | // MARK: - Rendering of objects into SCNNode. 23 | 24 | extension Object { 25 | public var scnNode: SCNNode { 26 | let render = renderScnNode() 27 | _ = render.1.compactMap { 28 | switch $0 { 29 | case .onAppear(let function): 30 | // please don't judge me i didn't have the time to implement this properly and i'm assuming it would have appeared at least 0.8 seconds after this 31 | DispatchQueue.main.asyncAfter(deadline: .now() + 0.8) { 32 | function() 33 | } 34 | default: 35 | break 36 | } 37 | } 38 | return render.0 39 | } 40 | 41 | // THIS IS THE DEFAULT ONLY DONT EXPECT THIS TO APPLY CHANGES TO EVERYTHING AS MOST HAVE CUSTOM IMPLEMENTATIONS 42 | public func renderScnNode() -> (SCNNode, [Attributes]) { 43 | let render = object.renderScnNode() 44 | var node = render.0 45 | var changedAttributes = applyAttributes(to: &node) 46 | changedAttributes.append(contentsOf: render.1) 47 | return (node, changedAttributes) 48 | } 49 | } 50 | 51 | // MARK: - Bind properties of @State3D to scene update method. 52 | 53 | extension Object { 54 | func bindProperties(_ update: @escaping () -> Void) { 55 | let mirror = Mirror(reflecting: self) 56 | for child in mirror.children { 57 | if var child = child.value as? DynamicProperty3D { 58 | child.update = update 59 | } 60 | } 61 | } 62 | } 63 | 64 | // MARK: - Search for child object with id. 65 | 66 | extension Object { 67 | func childWithId(id: String) -> Object? { 68 | var array: [Object] = [] 69 | var timeout = 0 70 | if array.isEmpty { 71 | array.append(self) 72 | } 73 | while !array.contains(where: { $0.id == id }) { 74 | switch array.last! { 75 | case is Stack: 76 | let stack = array.last! as! Stack 77 | for object in stack.content { 78 | array.append(object) 79 | } 80 | default: 81 | array.append(array.last!.object) 82 | } 83 | timeout += 1 84 | if timeout > 500 { 85 | print("too many objects to search") 86 | return nil 87 | } 88 | } 89 | return array.first { $0.id == id } 90 | } 91 | } 92 | 93 | // MARK: - Getter for id. 94 | 95 | extension Object { 96 | public var id: String { 97 | if self is Stack || object is Stack { 98 | let stack = self as? Stack ?? self.object as! Stack 99 | var base = "Stack,\(stack.xyz.rawValue),\(stack.spacing ?? 0)," 100 | if color != nil || object.color != nil { base.append("color") } 101 | if offset != nil || object.offset != nil { base.append("offset") } 102 | if opacity != nil || object.opacity != nil { base.append("opacity") } 103 | if rotation != nil || object.rotation != nil { base.append("rotation") } 104 | return base + ",\(String(format: "%04d", index))" 105 | } else { 106 | var base = "\(type(of: self))," 107 | if color != nil || object.color != nil { base.append("color") } 108 | if offset != nil || object.offset != nil { base.append("offset") } 109 | if opacity != nil || object.opacity != nil { base.append("opacity") } 110 | if rotation != nil || object.rotation != nil { base.append("rotation") } 111 | return base + ",\(String(format: "%04d", index))" 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/ObjectAttributes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ObjectAttributes.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | import SwiftUI 9 | import SceneKit 10 | 11 | public struct ObjectAttributes { 12 | public var index: Int = 0 13 | public var color: Color? = nil 14 | public var offset: Location3D? = nil 15 | public var opacity: CGFloat? = nil 16 | public var rotation: Location3D? = nil 17 | public var animation: Animation3D? = nil 18 | public var onAppear: (() -> Void)? = nil 19 | public init() { } 20 | } 21 | 22 | public enum Attributes { 23 | case opacity 24 | case color 25 | case offset 26 | case rotation 27 | case onAppear(() -> Void) 28 | } 29 | 30 | public protocol ObjectSupportedAttributes { 31 | var attributes: ObjectAttributes { get set } 32 | } 33 | 34 | // THIS WILL MEAN CUSTOM VIEWS WONT BE ABLE TO SAVE ATTRIBUTES BUT IT LOOKS CLEANER SO ITS OK FOR NOW. 35 | extension ObjectSupportedAttributes { 36 | public var attributes: ObjectAttributes { get {ObjectAttributes()} set {} } 37 | } 38 | 39 | extension Object { 40 | public func applyAttributes(to node: inout SCNNode) -> [Attributes] { 41 | var changedAttributes: [Attributes] = [] 42 | 43 | if let color = color { 44 | changedAttributes.append(.color) 45 | node.geometry?.firstMaterial?.diffuse.contents = UIColor(color) 46 | } 47 | 48 | if let offset = offset { 49 | changedAttributes.append(.offset) 50 | node.position.x = node.position.x + offset.x 51 | node.position.y = node.position.y + offset.y 52 | node.position.z = node.position.z + offset.z 53 | } 54 | 55 | if let rotation = rotation { 56 | changedAttributes.append(.rotation) 57 | node.eulerAngles.x = deg2rad(rotation.x) 58 | node.eulerAngles.y = deg2rad(rotation.y) 59 | node.eulerAngles.z = deg2rad(rotation.z) 60 | } 61 | 62 | if let opacity = opacity { 63 | changedAttributes.append(.opacity) 64 | node.opacity = opacity 65 | } 66 | 67 | if let onAppear = onAppear { 68 | changedAttributes.append(.onAppear(onAppear)) 69 | } 70 | 71 | node.name = id 72 | 73 | return changedAttributes 74 | } 75 | } 76 | 77 | extension Object { 78 | internal var index: Int { 79 | get { 80 | return attributes.index 81 | } 82 | set { 83 | attributes.index = newValue 84 | } 85 | } 86 | } 87 | 88 | extension Object { 89 | internal var color: Color? { 90 | get { 91 | return attributes.color 92 | } 93 | set { 94 | attributes.color = newValue 95 | } 96 | } 97 | public func color(_ color: Color) -> some Object { 98 | var me = self 99 | me.attributes.color = color 100 | return me 101 | } 102 | } 103 | 104 | extension Object { 105 | internal var offset: Location3D? { 106 | get { 107 | return attributes.offset 108 | } 109 | set { 110 | attributes.offset = newValue 111 | } 112 | } 113 | public func offset(_ offset: Location3D) -> some Object { 114 | var me = self 115 | me.offset = offset 116 | return me 117 | } 118 | public func offset(x: Float = 0, y: Float = 0, z: Float = 0) -> some Object { 119 | var me = self 120 | me.offset = Location3D(x: x + (offset?.x ?? 0), y: y + (offset?.y ?? 0), z: z + (offset?.z ?? 0)) 121 | return me 122 | } 123 | } 124 | 125 | extension Object { 126 | internal var opacity: CGFloat? { 127 | get { 128 | return attributes.opacity 129 | } 130 | set { 131 | attributes.opacity = newValue 132 | } 133 | } 134 | public func opacity(_ opacity: CGFloat) -> some Object { 135 | var me = self 136 | me.opacity = opacity 137 | return me 138 | } 139 | } 140 | 141 | extension Object { 142 | internal var animation: Animation3D? { 143 | get { 144 | return attributes.animation 145 | } 146 | set { 147 | attributes.animation = newValue 148 | } 149 | } 150 | public func animation(_ animation: Animation3D?) -> some Object { 151 | var me = self 152 | me.attributes.animation = animation 153 | return me 154 | } 155 | } 156 | 157 | extension Object { 158 | internal var rotation: Location3D? { 159 | get { 160 | return attributes.rotation 161 | } 162 | set { 163 | attributes.rotation = newValue 164 | } 165 | } 166 | public func rotation(_ rotation: Location3D) -> some Object { 167 | var me = self 168 | me.attributes.rotation = rotation 169 | return me 170 | } 171 | public func rotation(x: Float = 0, y: Float = 0, z: Float = 0) -> some Object { 172 | var me = self 173 | me.rotation = Location3D(x: x + (rotation?.x ?? 0), y: y + (rotation?.y ?? 0), z: z + (rotation?.z ?? 0)) 174 | return me 175 | } 176 | } 177 | 178 | 179 | extension Object { 180 | internal var onAppear: (() -> Void)? { 181 | get { 182 | return attributes.onAppear 183 | } 184 | set { 185 | attributes.onAppear = newValue 186 | } 187 | } 188 | public func onAppear(_ closure: @escaping () -> Void) -> some Object { 189 | var me = self 190 | me.onAppear = closure 191 | return me 192 | } 193 | } 194 | 195 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/ObjectBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ObjectBuilder.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | public protocol ObjectGroup { 9 | var objects: [Object] { get } 10 | } 11 | 12 | extension Array: ObjectGroup where Element == Object { 13 | public var objects: [Object] { self } 14 | } 15 | 16 | @_functionBuilder public struct ObjectBuilder { 17 | public static func buildBlock(_ objects: ObjectGroup...) -> [Object] { 18 | return objects.flatMap { $0.objects } 19 | } 20 | public static func buildOptional(_ object: [ObjectGroup]?) -> [Object] { 21 | return object?.flatMap { $0.objects } ?? [] 22 | } 23 | public static func buildEither(first object: [ObjectGroup]) -> [Object] { 24 | return object.flatMap { $0.objects } 25 | } 26 | public static func buildEither(second object: [ObjectGroup]) -> [Object] { 27 | return object.flatMap { $0.objects } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/Plane.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Plane.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 15/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | public struct Plane: Object { 11 | public init(size: CGSize = .init(width: 10, height: 10), vertical: Bool = false, doubleSided: Bool = true, cornerRadius: CGFloat = 0) { 12 | self.size = size 13 | self.doubleSided = doubleSided 14 | self.vertical = vertical 15 | self.cornerRadius = cornerRadius 16 | } 17 | 18 | public var object: Object { self } 19 | public var attributes = ObjectAttributes() 20 | 21 | private let size: CGSize 22 | private var doubleSided: Bool 23 | private var vertical: Bool 24 | private var cornerRadius: CGFloat 25 | } 26 | 27 | extension Plane { 28 | public func renderScnNode() -> (SCNNode, [Attributes]) { 29 | let plane = SCNPlane(width: size.width, height: size.height) 30 | plane.materials.first?.isDoubleSided = doubleSided 31 | plane.cornerRadius = cornerRadius 32 | var node = SCNNode(geometry: plane) 33 | if !vertical { 34 | node.transform = SCNMatrix4MakeRotation(-Float.pi / 2, 1, 0, 0) 35 | } 36 | let changedAttributes = applyAttributes(to: &node) 37 | return (node, changedAttributes) 38 | } 39 | } 40 | 41 | extension Plane { 42 | public func doubleSided(_ bool: Bool) -> Plane { 43 | var me = self 44 | me.doubleSided = bool 45 | return me 46 | } 47 | } 48 | 49 | extension Plane { 50 | public func cornerRadius(_ cornerRadius: CGFloat) -> Plane { 51 | var me = self 52 | me.cornerRadius = cornerRadius 53 | return me 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/Pyramid.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sphere.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | public struct Pyramid: Object { 11 | public init(size: Size3D = Size3D(width: 1, height: 1, length: 1)) { 12 | self.size = size 13 | } 14 | 15 | public var object: Object { self } 16 | public var attributes = ObjectAttributes() 17 | 18 | private let size: Size3D 19 | } 20 | 21 | extension Pyramid { 22 | public func renderScnNode() -> (SCNNode, [Attributes]) { 23 | let pyramid = SCNPyramid(width: size.width, height: size.height, length: size.length) 24 | var node = SCNNode(geometry: pyramid) 25 | node.position.y = -node.boundingBox.max.y/2 26 | let containerNode = SCNNode() 27 | containerNode.addChildNode(node) 28 | let changedAttributes = applyAttributes(to: &node) 29 | return (containerNode, changedAttributes) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/Scene3D.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Scene.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 11/4/21. 6 | // 7 | 8 | import SwiftUI 9 | import SceneKit 10 | 11 | public struct Scene3D: View, Scene3DProtocol { 12 | 13 | public init(baseObject: Object, realisticLighting: Bool = true) { 14 | // Setup camera and scene. 15 | let cameraNode = SCNNode() 16 | cameraNode.camera = SCNCamera() 17 | cameraNode.position = SCNVector3(x: 0, y: 0, z: 15) 18 | 19 | let scene = SCNScene() 20 | scene.rootNode.addChildNode(cameraNode) 21 | 22 | // Initialise properties. 23 | self.scene = scene 24 | self.camera = cameraNode 25 | self.realisticLighting = realisticLighting 26 | self.baseObject = baseObject 27 | self.baseObject.bindProperties(update) 28 | 29 | // Add lighting and objects to scene. 30 | if self.realisticLighting { 31 | scene.rootNode.addChildNode(ambientLight) 32 | scene.rootNode.addChildNode(directionalLight) 33 | } 34 | scene.rootNode.addChildNode(baseObject.scnNode) 35 | } 36 | 37 | public var body: some View { 38 | var defaultOptions: SceneView.Options = [.allowsCameraControl, .jitteringEnabled] 39 | if !realisticLighting { defaultOptions.insert(.autoenablesDefaultLighting) } 40 | return SceneView(scene: scene, pointOfView: camera, options: defaultOptions) 41 | } 42 | 43 | internal var scene: SCNScene 44 | internal var realisticLighting: Bool 45 | internal var baseObject: Object 46 | private var camera: SCNNode 47 | 48 | fileprivate init( 49 | baseObject: Object, 50 | realisticLighting: Bool = true, 51 | backgroundColor: Color? = nil, 52 | backgroundImage: UIImage? = nil 53 | ) { 54 | self.init(baseObject: baseObject, realisticLighting: realisticLighting) 55 | if let backgroundColor = backgroundColor { 56 | scene.background.contents = UIColor(backgroundColor) 57 | } 58 | 59 | if let backgroundImage = backgroundImage { 60 | scene.background.contents = backgroundImage 61 | } 62 | } 63 | } 64 | 65 | extension Scene3D { 66 | public func backgroundColor(_ color: Color) -> some View { 67 | return Scene3D(baseObject: self.baseObject, realisticLighting: realisticLighting, backgroundColor: color) 68 | } 69 | } 70 | 71 | extension Scene3D { 72 | public func backgroundImage(_ image: UIImage) -> some View { 73 | return Scene3D(baseObject: self.baseObject, realisticLighting: realisticLighting, backgroundImage: image) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/Scene3DProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Scene3DProtocol.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 17/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | internal protocol Scene3DProtocol { 11 | var scene: SCNScene { get } 12 | var baseObject: Object { get set } 13 | 14 | var realisticLighting: Bool { get } 15 | var ambientLight: SCNNode { get } 16 | var directionalLight: SCNNode { get } 17 | func update() 18 | } 19 | 20 | // MARK: - Configure Scene Lighting 21 | 22 | extension Scene3DProtocol { 23 | // Ambient light lights areas not lit by directional light. 24 | var ambientLight: SCNNode { 25 | let ambientLight = SCNLight() 26 | ambientLight.type = .ambient 27 | ambientLight.color = UIColor.white 28 | ambientLight.intensity = 2000 29 | ambientLight.categoryBitMask = -1 30 | 31 | let ambientLightNode = SCNNode() 32 | ambientLightNode.light = ambientLight 33 | ambientLightNode.position = SCNVector3(x: 0, y: 5, z: 0) 34 | return ambientLightNode 35 | } 36 | 37 | // Directional light creates shadows 38 | var directionalLight: SCNNode { 39 | let directionalLight = SCNLight() 40 | directionalLight.type = .directional 41 | directionalLight.castsShadow = true 42 | directionalLight.color = UIColor.white 43 | directionalLight.automaticallyAdjustsShadowProjection = true 44 | directionalLight.shadowColor = UIColor.black.withAlphaComponent(0.5) 45 | directionalLight.shadowMode = .deferred 46 | directionalLight.shadowRadius = 8 47 | directionalLight.zNear = 0 48 | directionalLight.zFar = 50 49 | directionalLight.shadowSampleCount = 32 50 | directionalLight.shadowMapSize = CGSize(width: 4096, height: 4096) 51 | directionalLight.categoryBitMask = -1 52 | 53 | let directionalLightNode = SCNNode() 54 | directionalLightNode.light = directionalLight 55 | directionalLightNode.position = SCNVector3(x: 0, y: 15, z: 0) 56 | directionalLightNode.eulerAngles = SCNVector3(deg2rad(-88), 0, deg2rad(-2)) 57 | 58 | return directionalLightNode 59 | } 60 | } 61 | 62 | // MARK: - Configure update method. 63 | 64 | extension Scene3DProtocol { 65 | func update() { 66 | scene.rootNode.enumerateChildNodes { node, stop in 67 | // Ensure node has a name. 68 | // If so search for a child object from base object with matching id. 69 | if let name = node.name, 70 | let object = baseObject.childWithId(id: name) 71 | { 72 | let child = object.object 73 | 74 | let animationDuration = child.animation?.duration ?? 0 75 | let animationCurve = child.animation?.option ?? .linear 76 | let animationRepeatsForever = child.animation?.repeatForever ?? false 77 | 78 | var animationGroup: [SCNAction] = [] 79 | 80 | // MARK: - Update color 81 | if name.contains("color"), 82 | let newColor = child.color, 83 | UIColor(newColor) != node.geometry?.firstMaterial?.diffuse.contents as? UIColor 84 | { 85 | let oldColor = node.geometry?.firstMaterial?.diffuse.contents as? UIColor ?? .white 86 | 87 | func newColorAction(toColor: UIColor) -> SCNAction { 88 | SCNAction.customAction(duration: animationDuration) { actionNode, time in 89 | SCNTransaction.begin() 90 | SCNTransaction.animationDuration = animationDuration 91 | SCNTransaction.animationTimingFunction = animationCurve.caMediaTimingFunction() 92 | node.geometry?.firstMaterial?.diffuse.contents = toColor 93 | SCNTransaction.commit() 94 | } 95 | } 96 | 97 | let colorAction = newColorAction(toColor: UIColor(newColor)) 98 | colorAction.timingMode = animationCurve 99 | 100 | if animationRepeatsForever { 101 | let colorAction2 = newColorAction(toColor: oldColor) 102 | colorAction2.timingMode = animationCurve 103 | animationGroup.append(SCNAction.sequence([colorAction, colorAction2])) 104 | } else { 105 | animationGroup.append(colorAction) 106 | } 107 | } 108 | 109 | // MARK: - Update offset 110 | if name.contains("offset"), 111 | let newOffset = child.offset 112 | { 113 | var newVector = SCNVector3() 114 | 115 | // Check if object is in a stack. 116 | if let parentNode = node.parent, 117 | let parentName = parentNode.name, 118 | parentName.hasPrefix("Stack") 119 | { 120 | let stackProperties = parentName.components(separatedBy: ",") 121 | 122 | let xyz = XYZ(rawValue: stackProperties[1])! 123 | let spacing = Float(stackProperties[2]) ?? 0 124 | let index = Int(name.suffix(4))! 125 | 126 | var totalWidth: Float = 0 127 | for childIndex in 0...index { 128 | if childIndex != 0 { 129 | totalWidth += parentNode.childNodes[childIndex-1].boundingBox.max.x 130 | totalWidth += parentNode.childNodes[childIndex].boundingBox.max.x 131 | } 132 | } 133 | 134 | var totalHeight: Float = 0 135 | for childIndex in 0...index { 136 | if childIndex != 0 { 137 | totalHeight += parentNode.childNodes[childIndex-1].boundingBox.max.y 138 | totalHeight += parentNode.childNodes[childIndex].boundingBox.max.y 139 | } 140 | } 141 | 142 | var totalLength: Float = 0 143 | for childIndex in 0...index { 144 | if childIndex != 0 { 145 | totalLength += parentNode.childNodes[childIndex-1].boundingBox.max.z 146 | totalLength += parentNode.childNodes[childIndex].boundingBox.max.z 147 | } 148 | } 149 | 150 | print("calc: \(totalWidth)") 151 | print(node.position.x) 152 | let xTranslation = totalWidth + ((spacing)*Float(parentNode.childNodes.count-1)) 153 | let yTranslation = totalHeight + ((spacing)*Float(parentNode.childNodes.count-1)) 154 | let zTranslation = totalLength + ((spacing)*Float(parentNode.childNodes.count-1)) 155 | 156 | switch xyz { 157 | case .x: 158 | newVector = SCNVector3( 159 | x: xTranslation - node.position.x, 160 | y: newOffset.y - node.position.y, 161 | z: newOffset.z - node.position.z 162 | ) 163 | case .y: 164 | newVector = SCNVector3( 165 | x: newOffset.x - node.position.x, 166 | y: yTranslation + newOffset.y - node.position.y, 167 | z: newOffset.z - node.position.z 168 | ) 169 | case .z: 170 | newVector = SCNVector3( 171 | x: newOffset.x - node.position.x, 172 | y: newOffset.y - node.position.y, 173 | z: zTranslation + newOffset.z - node.position.z 174 | ) 175 | } 176 | let offsetAction = SCNAction.move(by: newVector, duration: animationDuration) 177 | offsetAction.timingMode = animationCurve 178 | 179 | if animationRepeatsForever { 180 | animationGroup.append(SCNAction.sequence([offsetAction, offsetAction.reversed()])) 181 | } else { 182 | animationGroup.append(offsetAction) 183 | } 184 | } else if 185 | newOffset.x != node.position.x || 186 | newOffset.y != node.position.y || 187 | newOffset.z != node.position.z 188 | { 189 | newVector = SCNVector3( 190 | x: newOffset.x - node.position.x, 191 | y: newOffset.y - node.position.y, 192 | z: newOffset.z - node.position.z 193 | ) 194 | let offsetAction = SCNAction.move(by: newVector, duration: animationDuration) 195 | offsetAction.timingMode = animationCurve 196 | 197 | if animationRepeatsForever { 198 | animationGroup.append(SCNAction.sequence([offsetAction, offsetAction.reversed()])) 199 | } else { 200 | animationGroup.append(offsetAction) 201 | } 202 | } 203 | } 204 | 205 | // MARK: - Update rotation 206 | if name.contains("rotation"), 207 | let newRotation = child.rotation, 208 | deg2rad(newRotation.x) != node.eulerAngles.x || 209 | deg2rad(newRotation.y) != node.eulerAngles.y || 210 | deg2rad(newRotation.z) != node.eulerAngles.z 211 | { 212 | let rotationAction = SCNAction.rotateBy( 213 | x: CGFloat(deg2rad(newRotation.x) - node.eulerAngles.x), 214 | y: CGFloat(deg2rad(newRotation.y) - node.eulerAngles.y), 215 | z: CGFloat(deg2rad(newRotation.z) - node.eulerAngles.z), 216 | duration: animationDuration 217 | ) 218 | rotationAction.timingMode = animationCurve 219 | 220 | if animationRepeatsForever { 221 | animationGroup.append(SCNAction.sequence([rotationAction, rotationAction.reversed()])) 222 | } else { 223 | animationGroup.append(rotationAction) 224 | } 225 | } 226 | 227 | // MARK: - Update opacity 228 | // Yes my code is messy but if anyone at apple who works with scenekit is reading this, SCNActions modifying opacity are broken... no time to fix only 3 days left and still need to do the playground pages and walkthroughs. 229 | if name.contains("opacity"), 230 | let newOpacity = child.opacity, 231 | newOpacity != node.opacity 232 | { 233 | let opacityChange = newOpacity - node.opacity 234 | let opacityAction = SCNAction.fadeOpacity(by: opacityChange, duration: animationDuration) 235 | opacityAction.timingMode = animationCurve 236 | 237 | if animationRepeatsForever { 238 | let opacityAction2 = SCNAction.fadeOpacity(by: -opacityChange, duration: animationDuration) 239 | opacityAction2.timingMode = animationCurve 240 | animationGroup.append(SCNAction.sequence([opacityAction, opacityAction2])) 241 | } else { 242 | animationGroup.append(opacityAction) 243 | } 244 | } 245 | 246 | 247 | let groupAction = SCNAction.group(animationGroup) 248 | 249 | if animationRepeatsForever { 250 | let repeatAction = SCNAction.repeatForever(groupAction) 251 | node.runAction(repeatAction) 252 | } else { 253 | node.runAction(groupAction) 254 | } 255 | } 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/Sphere.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Sphere.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | public struct Sphere: Object { 11 | public init(radius: CGFloat = 0.5) { 12 | self.radius = radius 13 | } 14 | 15 | public var object: Object { self } 16 | public var attributes = ObjectAttributes() 17 | 18 | private let radius: CGFloat 19 | } 20 | 21 | extension Sphere { 22 | public func renderScnNode() -> (SCNNode, [Attributes]) { 23 | let sphere = SCNSphere(radius: radius) 24 | var node = SCNNode(geometry: sphere) 25 | let changedAttributes = applyAttributes(to: &node) 26 | return (node, changedAttributes) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/Stack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // XStack.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | import SwiftUI 9 | import SceneKit 10 | 11 | public struct Stack: Object { 12 | public init(_ xyz: XYZ, spacing: Float? = nil, @ObjectBuilder content: () -> [Object]) { 13 | self.xyz = xyz 14 | self.spacing = spacing 15 | 16 | var objects: [Object] = [] 17 | for (index, i) in content().enumerated() { 18 | var object = i 19 | object.index = index 20 | objects.append(object) 21 | } 22 | 23 | self.content = objects 24 | } 25 | 26 | public var object: Object { self } 27 | public var attributes = ObjectAttributes() 28 | 29 | internal var content: [Object] 30 | internal var xyz: XYZ 31 | internal var spacing: Float? 32 | } 33 | 34 | extension Stack { 35 | func stackToNode(xyz: XYZ, spacing: Float?, color: Color?) -> SCNNode { 36 | let parentNode = SCNNode() 37 | 38 | for (index, object) in content.enumerated() { 39 | let childNode = object.scnNode 40 | let spacing = spacing ?? 0 41 | let xTranslation = index == 0 ? 0 : parentNode.boundingBox.max.x + spacing + childNode.boundingBox.max.x 42 | let yTranslation = index == 0 ? 0 : parentNode.boundingBox.max.y + spacing + childNode.boundingBox.max.y 43 | let zTranslation = index == 0 ? 0 : parentNode.boundingBox.max.z + spacing + childNode.boundingBox.max.z 44 | switch xyz { 45 | case .x: 46 | childNode.position.x += xTranslation 47 | case .y: 48 | childNode.position.y += yTranslation 49 | case .z: 50 | childNode.position.z += zTranslation 51 | } 52 | parentNode.addChildNode(childNode) 53 | } 54 | return parentNode 55 | } 56 | } 57 | 58 | extension Stack { 59 | public func renderScnNode() -> (SCNNode, [Attributes]) { 60 | var node = stackToNode(xyz: xyz, spacing: spacing, color: color) 61 | let changedAttributes = applyAttributes(to: &node) 62 | return (node, changedAttributes) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/State3D.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ARState.swift 3 | // testingmy3dthing 4 | // 5 | // Created by Christian Privitelli on 4/4/21. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @propertyWrapper public class State3D: DynamicProperty3D { 11 | 12 | /// Set the initial default value. 13 | public init(wrappedValue value: Value) { 14 | self.storage.internalValue = value 15 | } 16 | 17 | /// This is the value that is accessed by default. Eg `@CustomState var test = 5` access this wrapped value with just `test`. 18 | public var wrappedValue: Value { 19 | /// Get the wrappedValue from the internal value of the model. 20 | get { self.storage.internalValue! } 21 | 22 | /// When a user changes the wrappedValue, it will change the internalValue meaning that with @CustomState, they can mutate and access a value without storing it in the struct/classes self. 23 | set { 24 | self.storage.internalValue = newValue 25 | self.update() 26 | } 27 | } 28 | 29 | public var update: () -> Void = { } 30 | 31 | private var storage = InternalStorage() 32 | 33 | private class InternalStorage { 34 | var internalValue: Value? 35 | /// Be nil by default, but this doesn't matter as when a @CustomState is created, the internalValue is set to the default it is created with. 36 | internal init() { self.internalValue = nil } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/Torus.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Torus.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 17/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | public struct Torus: Object { 11 | public init(ringRadius: CGFloat = 0.5, pipeRadius: CGFloat = 0.2) { 12 | self.ringRadius = ringRadius 13 | self.pipeRadius = pipeRadius 14 | } 15 | 16 | public var object: Object { self } 17 | public var attributes = ObjectAttributes() 18 | 19 | private let ringRadius: CGFloat 20 | private let pipeRadius: CGFloat 21 | } 22 | 23 | extension Torus { 24 | public func renderScnNode() -> (SCNNode, [Attributes]) { 25 | let torus = SCNTorus(ringRadius: ringRadius, pipeRadius: pipeRadius) 26 | var node = SCNNode(geometry: torus) 27 | let changedAttributes = applyAttributes(to: &node) 28 | return (node, changedAttributes) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/Tube.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tube.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 17/4/21. 6 | // 7 | 8 | import SceneKit 9 | 10 | public struct Tube: Object { 11 | public init(outerRadius: CGFloat = 0.5, innerRadius: CGFloat = 0.1, height: CGFloat = 1) { 12 | self.outerRadius = outerRadius 13 | self.innerRadius = innerRadius 14 | self.height = height 15 | } 16 | 17 | public var object: Object { self } 18 | public var attributes = ObjectAttributes() 19 | 20 | private let outerRadius: CGFloat 21 | private let innerRadius: CGFloat 22 | private let height: CGFloat 23 | } 24 | 25 | extension Tube { 26 | public func renderScnNode() -> (SCNNode, [Attributes]) { 27 | let torus = SCNTube(innerRadius: innerRadius, outerRadius: outerRadius, height: height) 28 | var node = SCNNode(geometry: torus) 29 | let changedAttributes = applyAttributes(to: &node) 30 | return (node, changedAttributes) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Swift3D.playgroundbook/Contents/UserModules/Swift3D.playgroundmodule/Sources/ViewPlane.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewPlane.swift 3 | // blank 4 | // 5 | // Created by Christian Privitelli on 15/4/21. 6 | // 7 | 8 | import SceneKit 9 | import SwiftUI 10 | 11 | public struct ViewPlane: Object where Content: View { 12 | public init(size: CGSize = .init(width: 10, height: 10), doubleSided: Bool = true, vertical: Bool = false, cornerRadius: CGFloat = 0, @ViewBuilder body: () -> Content) { 13 | self.size = size 14 | self.doubleSided = doubleSided 15 | self.vertical = vertical 16 | self.cornerRadius = cornerRadius 17 | self.body = body() 18 | } 19 | 20 | public init(size: CGSize = .init(width: 10, height: 10), doubleSided: Bool = true, vertical: Bool = false, cornerRadius: CGFloat = 0, body: Content) { 21 | self.size = size 22 | self.doubleSided = doubleSided 23 | self.vertical = vertical 24 | self.cornerRadius = cornerRadius 25 | self.body = body 26 | } 27 | 28 | public var object: Object { self } 29 | public var attributes = ObjectAttributes() 30 | 31 | private let size: CGSize 32 | private var doubleSided: Bool 33 | private var vertical: Bool 34 | private var cornerRadius: CGFloat 35 | private var body: Content 36 | } 37 | 38 | extension ViewPlane { 39 | public func renderScnNode() -> (SCNNode, [Attributes]) { 40 | let plane = SCNPlane(width: size.width, height: size.height) 41 | 42 | plane.cornerRadius = cornerRadius 43 | var node = SCNNode(geometry: plane) 44 | if !vertical { 45 | node.transform = SCNMatrix4MakeRotation(-Float.pi / 2, 1, 0, 0) 46 | } 47 | 48 | let changedAttributes = applyAttributes(to: &node) 49 | 50 | DispatchQueue.main.async { 51 | let materialView = UIHostingController(rootView: body).view! 52 | materialView.isOpaque = false 53 | materialView.backgroundColor = UIColor(color ?? .clear) 54 | materialView.frame = CGRect(x: 0, y: 0, width: plane.width*50, height: plane.height*50) 55 | 56 | let material = SCNMaterial() 57 | material.diffuse.contents = viewToImage(view: materialView) 58 | node.geometry?.materials = [material] 59 | node.geometry?.materials.first?.isDoubleSided = doubleSided 60 | } 61 | 62 | let containerNode = SCNNode() 63 | containerNode.addChildNode(node) 64 | 65 | return (containerNode, changedAttributes) 66 | } 67 | 68 | private func viewToImage(view: UIView) -> UIImage { 69 | let renderer = UIGraphicsImageRenderer(size: view.bounds.size) 70 | let image = renderer.image { ctx in 71 | view.drawHierarchy(in: view.bounds, afterScreenUpdates: true) 72 | } 73 | return image 74 | } 75 | } 76 | 77 | extension ViewPlane { 78 | public func doubleSided(_ bool: Bool) -> ViewPlane { 79 | var me = self 80 | me.doubleSided = bool 81 | return me 82 | } 83 | } 84 | 85 | extension ViewPlane { 86 | public func cornerRadius(_ cornerRadius: CGFloat) -> ViewPlane { 87 | var me = self 88 | me.cornerRadius = cornerRadius 89 | return me 90 | } 91 | } 92 | --------------------------------------------------------------------------------