├── .gitignore ├── LICENSE ├── README.md ├── SCNHighlight.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ └── SCNHighlight.xcscheme └── xcuserdata │ └── alberto.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── SCNHighlight ├── AppDelegate.swift ├── Info.plist ├── assets │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── spaceship-1024px-@1x.png │ │ │ ├── spaceship-20.0px-@1x.png │ │ │ ├── spaceship-20.0px-@2x-1.png │ │ │ ├── spaceship-20.0px-@2x.png │ │ │ ├── spaceship-20.0px-@3x.png │ │ │ ├── spaceship-29.0px-@1x-1.png │ │ │ ├── spaceship-29.0px-@1x.png │ │ │ ├── spaceship-29.0px-@2x-1.png │ │ │ ├── spaceship-29.0px-@2x.png │ │ │ ├── spaceship-29.0px-@3x.png │ │ │ ├── spaceship-40.0px-@1x.png │ │ │ ├── spaceship-40.0px-@2x-1.png │ │ │ ├── spaceship-40.0px-@2x.png │ │ │ ├── spaceship-40.0px-@3x.png │ │ │ ├── spaceship-60.0px-@2x.png │ │ │ ├── spaceship-60.0px-@3x.png │ │ │ ├── spaceship-76.0px-@1x.png │ │ │ ├── spaceship-76.0px-@2x.png │ │ │ └── spaceship-83.5px-@2x.png │ │ └── Contents.json │ ├── art.scnassets │ │ ├── ship.scn │ │ └── texture.png │ └── scene_techniques │ │ ├── RenderOutlineShaders.metal │ │ └── RenderOutlineTechnique.plist ├── controllers │ └── GameViewController.swift ├── extensions │ ├── Dictionary+Extensions.swift │ └── SCNNode+Extensions.swift └── views │ └── Base.lproj │ └── LaunchScreen.storyboard └── data └── scnhighlight_demo.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | # 51 | # Add this line if you want to avoid checking in source code from the Xcode workspace 52 | # *.xcworkspace 53 | 54 | # Carthage 55 | # 56 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 57 | # Carthage/Checkouts 58 | 59 | Carthage/Build 60 | 61 | # Accio dependency management 62 | Dependencies/ 63 | .accio/ 64 | 65 | # fastlane 66 | # 67 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 68 | # screenshots whenever they are needed. 69 | # For more information about the recommended setup visit: 70 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 71 | 72 | fastlane/report.xml 73 | fastlane/Preview.html 74 | fastlane/screenshots/**/*.png 75 | fastlane/test_output 76 | 77 | # Code Injection 78 | # 79 | # After new code Injection tools there's a generated folder /iOSInjectionProject 80 | # https://github.com/johnno1962/injectionforxcode 81 | 82 | iOSInjectionProject/ 83 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alberto Taiuti 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SCNHighlight 2 | 3 | Scale-invariant highlight effect for SCNNodes 4 | 5 | ![example_1](data/scnhighlight_demo.gif) 6 | 7 | ## Description 8 | 9 | This project can be used as inspiration (or it can brutally copy-pasted) 10 | to achieve a scale-invariant highlight effect on your SCNNodes. 11 | 12 | "Scale-invariant" means that the highlight border maintains the same 13 | thickness no matter how far from the camera the SCNNode is. 14 | This is in contrast with the other main techniques to show a border around 15 | objects which, although work when you're at close range from the highlighted 16 | object, they "thin out" the highlight as the object is far away. 17 | 18 | SCNHighlight can be particularly useful to show a highlight around SCNNodes 19 | in an ARKit app, where selected objects might be far from the camera. 20 | 21 | ## Prerequisites 22 | 23 | * XCode >= 10 24 | * iOS >= 12.1 25 | 26 | ## Usage 27 | 28 | * Clone the repo 29 | * Build 30 | 31 | ## References 32 | 33 | * Inspired and adapted from [SCNTechniqueGlow](https://github.com/laanlabs/SCNTechniqueGlow) 34 | 35 | ## Authors 36 | 37 | * [Alberto Taiuti](https://albertotaiuti.com) 38 | -------------------------------------------------------------------------------- /SCNHighlight.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | D3266A5322ABAD2100986613 /* RenderOutlineTechnique.plist in Resources */ = {isa = PBXBuildFile; fileRef = D3266A5122ABAD2100986613 /* RenderOutlineTechnique.plist */; }; 11 | D3266A5422ABAD2100986613 /* RenderOutlineShaders.metal in Sources */ = {isa = PBXBuildFile; fileRef = D3266A5222ABAD2100986613 /* RenderOutlineShaders.metal */; }; 12 | D3266A5722ABAEC200986613 /* Dictionary+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3266A5622ABAEC200986613 /* Dictionary+Extensions.swift */; }; 13 | D3266A5922ABB2BE00986613 /* SCNNode+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3266A5822ABB2BE00986613 /* SCNNode+Extensions.swift */; }; 14 | D3D93CCF22AB185800498A08 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3D93CCE22AB185800498A08 /* AppDelegate.swift */; }; 15 | D3D93CD122AB185800498A08 /* art.scnassets in Resources */ = {isa = PBXBuildFile; fileRef = D3D93CD022AB185800498A08 /* art.scnassets */; }; 16 | D3D93CD322AB185800498A08 /* GameViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3D93CD222AB185800498A08 /* GameViewController.swift */; }; 17 | D3D93CD822AB185900498A08 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D3D93CD722AB185900498A08 /* Assets.xcassets */; }; 18 | D3D93CDB22AB185900498A08 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = D3D93CD922AB185900498A08 /* LaunchScreen.storyboard */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | D3266A5122ABAD2100986613 /* RenderOutlineTechnique.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = RenderOutlineTechnique.plist; sourceTree = ""; }; 23 | D3266A5222ABAD2100986613 /* RenderOutlineShaders.metal */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.metal; path = RenderOutlineShaders.metal; sourceTree = ""; }; 24 | D3266A5622ABAEC200986613 /* Dictionary+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Dictionary+Extensions.swift"; sourceTree = ""; }; 25 | D3266A5822ABB2BE00986613 /* SCNNode+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SCNNode+Extensions.swift"; sourceTree = ""; }; 26 | D3D93CCB22AB185700498A08 /* SCNHighlight.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SCNHighlight.app; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | D3D93CCE22AB185800498A08 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 28 | D3D93CD022AB185800498A08 /* art.scnassets */ = {isa = PBXFileReference; lastKnownFileType = wrapper.scnassets; path = art.scnassets; sourceTree = ""; }; 29 | D3D93CD222AB185800498A08 /* GameViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameViewController.swift; sourceTree = ""; }; 30 | D3D93CD722AB185900498A08 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 31 | D3D93CDA22AB185900498A08 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 32 | D3D93CDC22AB185900498A08 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 33 | /* End PBXFileReference section */ 34 | 35 | /* Begin PBXFrameworksBuildPhase section */ 36 | D3D93CC822AB185700498A08 /* Frameworks */ = { 37 | isa = PBXFrameworksBuildPhase; 38 | buildActionMask = 2147483647; 39 | files = ( 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXFrameworksBuildPhase section */ 44 | 45 | /* Begin PBXGroup section */ 46 | D3266A4D22ABAC8000986613 /* assets */ = { 47 | isa = PBXGroup; 48 | children = ( 49 | D3266A5022ABACF100986613 /* scene_techniques */, 50 | D3D93CD722AB185900498A08 /* Assets.xcassets */, 51 | D3D93CD022AB185800498A08 /* art.scnassets */, 52 | ); 53 | path = assets; 54 | sourceTree = ""; 55 | }; 56 | D3266A4E22ABACD300986613 /* views */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | D3D93CD922AB185900498A08 /* LaunchScreen.storyboard */, 60 | ); 61 | path = views; 62 | sourceTree = ""; 63 | }; 64 | D3266A4F22ABACE500986613 /* controllers */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | D3D93CD222AB185800498A08 /* GameViewController.swift */, 68 | ); 69 | path = controllers; 70 | sourceTree = ""; 71 | }; 72 | D3266A5022ABACF100986613 /* scene_techniques */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | D3266A5222ABAD2100986613 /* RenderOutlineShaders.metal */, 76 | D3266A5122ABAD2100986613 /* RenderOutlineTechnique.plist */, 77 | ); 78 | path = scene_techniques; 79 | sourceTree = ""; 80 | }; 81 | D3266A5522ABAEAF00986613 /* extensions */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | D3266A5622ABAEC200986613 /* Dictionary+Extensions.swift */, 85 | D3266A5822ABB2BE00986613 /* SCNNode+Extensions.swift */, 86 | ); 87 | path = extensions; 88 | sourceTree = ""; 89 | }; 90 | D3D93CC222AB185700498A08 = { 91 | isa = PBXGroup; 92 | children = ( 93 | D3D93CCD22AB185700498A08 /* SCNHighlight */, 94 | D3D93CCC22AB185700498A08 /* Products */, 95 | ); 96 | sourceTree = ""; 97 | }; 98 | D3D93CCC22AB185700498A08 /* Products */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | D3D93CCB22AB185700498A08 /* SCNHighlight.app */, 102 | ); 103 | name = Products; 104 | sourceTree = ""; 105 | }; 106 | D3D93CCD22AB185700498A08 /* SCNHighlight */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | D3266A5522ABAEAF00986613 /* extensions */, 110 | D3266A4F22ABACE500986613 /* controllers */, 111 | D3266A4E22ABACD300986613 /* views */, 112 | D3266A4D22ABAC8000986613 /* assets */, 113 | D3D93CCE22AB185800498A08 /* AppDelegate.swift */, 114 | D3D93CDC22AB185900498A08 /* Info.plist */, 115 | ); 116 | path = SCNHighlight; 117 | sourceTree = ""; 118 | }; 119 | /* End PBXGroup section */ 120 | 121 | /* Begin PBXNativeTarget section */ 122 | D3D93CCA22AB185700498A08 /* SCNHighlight */ = { 123 | isa = PBXNativeTarget; 124 | buildConfigurationList = D3D93CDF22AB185900498A08 /* Build configuration list for PBXNativeTarget "SCNHighlight" */; 125 | buildPhases = ( 126 | D3D93CC722AB185700498A08 /* Sources */, 127 | D3D93CC822AB185700498A08 /* Frameworks */, 128 | D3D93CC922AB185700498A08 /* Resources */, 129 | ); 130 | buildRules = ( 131 | ); 132 | dependencies = ( 133 | ); 134 | name = SCNHighlight; 135 | productName = SCNHighlight; 136 | productReference = D3D93CCB22AB185700498A08 /* SCNHighlight.app */; 137 | productType = "com.apple.product-type.application"; 138 | }; 139 | /* End PBXNativeTarget section */ 140 | 141 | /* Begin PBXProject section */ 142 | D3D93CC322AB185700498A08 /* Project object */ = { 143 | isa = PBXProject; 144 | attributes = { 145 | LastSwiftUpdateCheck = 1020; 146 | LastUpgradeCheck = 1020; 147 | ORGANIZATIONNAME = "Alberto Taiuti"; 148 | TargetAttributes = { 149 | D3D93CCA22AB185700498A08 = { 150 | CreatedOnToolsVersion = 10.2.1; 151 | }; 152 | }; 153 | }; 154 | buildConfigurationList = D3D93CC622AB185700498A08 /* Build configuration list for PBXProject "SCNHighlight" */; 155 | compatibilityVersion = "Xcode 9.3"; 156 | developmentRegion = en; 157 | hasScannedForEncodings = 0; 158 | knownRegions = ( 159 | en, 160 | Base, 161 | ); 162 | mainGroup = D3D93CC222AB185700498A08; 163 | productRefGroup = D3D93CCC22AB185700498A08 /* Products */; 164 | projectDirPath = ""; 165 | projectRoot = ""; 166 | targets = ( 167 | D3D93CCA22AB185700498A08 /* SCNHighlight */, 168 | ); 169 | }; 170 | /* End PBXProject section */ 171 | 172 | /* Begin PBXResourcesBuildPhase section */ 173 | D3D93CC922AB185700498A08 /* Resources */ = { 174 | isa = PBXResourcesBuildPhase; 175 | buildActionMask = 2147483647; 176 | files = ( 177 | D3266A5322ABAD2100986613 /* RenderOutlineTechnique.plist in Resources */, 178 | D3D93CD122AB185800498A08 /* art.scnassets in Resources */, 179 | D3D93CDB22AB185900498A08 /* LaunchScreen.storyboard in Resources */, 180 | D3D93CD822AB185900498A08 /* Assets.xcassets in Resources */, 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | }; 184 | /* End PBXResourcesBuildPhase section */ 185 | 186 | /* Begin PBXSourcesBuildPhase section */ 187 | D3D93CC722AB185700498A08 /* Sources */ = { 188 | isa = PBXSourcesBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | D3D93CD322AB185800498A08 /* GameViewController.swift in Sources */, 192 | D3266A5422ABAD2100986613 /* RenderOutlineShaders.metal in Sources */, 193 | D3D93CCF22AB185800498A08 /* AppDelegate.swift in Sources */, 194 | D3266A5922ABB2BE00986613 /* SCNNode+Extensions.swift in Sources */, 195 | D3266A5722ABAEC200986613 /* Dictionary+Extensions.swift in Sources */, 196 | ); 197 | runOnlyForDeploymentPostprocessing = 0; 198 | }; 199 | /* End PBXSourcesBuildPhase section */ 200 | 201 | /* Begin PBXVariantGroup section */ 202 | D3D93CD922AB185900498A08 /* LaunchScreen.storyboard */ = { 203 | isa = PBXVariantGroup; 204 | children = ( 205 | D3D93CDA22AB185900498A08 /* Base */, 206 | ); 207 | name = LaunchScreen.storyboard; 208 | sourceTree = ""; 209 | }; 210 | /* End PBXVariantGroup section */ 211 | 212 | /* Begin XCBuildConfiguration section */ 213 | D3D93CDD22AB185900498A08 /* Debug */ = { 214 | isa = XCBuildConfiguration; 215 | buildSettings = { 216 | ALWAYS_SEARCH_USER_PATHS = NO; 217 | CLANG_ANALYZER_NONNULL = YES; 218 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 219 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 220 | CLANG_CXX_LIBRARY = "libc++"; 221 | CLANG_ENABLE_MODULES = YES; 222 | CLANG_ENABLE_OBJC_ARC = YES; 223 | CLANG_ENABLE_OBJC_WEAK = YES; 224 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 225 | CLANG_WARN_BOOL_CONVERSION = YES; 226 | CLANG_WARN_COMMA = YES; 227 | CLANG_WARN_CONSTANT_CONVERSION = YES; 228 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 229 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 230 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 231 | CLANG_WARN_EMPTY_BODY = YES; 232 | CLANG_WARN_ENUM_CONVERSION = YES; 233 | CLANG_WARN_INFINITE_RECURSION = YES; 234 | CLANG_WARN_INT_CONVERSION = YES; 235 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 236 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 237 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 238 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 239 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 240 | CLANG_WARN_STRICT_PROTOTYPES = YES; 241 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 242 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 243 | CLANG_WARN_UNREACHABLE_CODE = YES; 244 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 245 | CODE_SIGN_IDENTITY = "iPhone Developer"; 246 | COPY_PHASE_STRIP = NO; 247 | DEBUG_INFORMATION_FORMAT = dwarf; 248 | ENABLE_STRICT_OBJC_MSGSEND = YES; 249 | ENABLE_TESTABILITY = YES; 250 | GCC_C_LANGUAGE_STANDARD = gnu11; 251 | GCC_DYNAMIC_NO_PIC = NO; 252 | GCC_NO_COMMON_BLOCKS = YES; 253 | GCC_OPTIMIZATION_LEVEL = 0; 254 | GCC_PREPROCESSOR_DEFINITIONS = ( 255 | "DEBUG=1", 256 | "$(inherited)", 257 | ); 258 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 259 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 260 | GCC_WARN_UNDECLARED_SELECTOR = YES; 261 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 262 | GCC_WARN_UNUSED_FUNCTION = YES; 263 | GCC_WARN_UNUSED_VARIABLE = YES; 264 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 265 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 266 | MTL_FAST_MATH = YES; 267 | ONLY_ACTIVE_ARCH = YES; 268 | SDKROOT = iphoneos; 269 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 270 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 271 | }; 272 | name = Debug; 273 | }; 274 | D3D93CDE22AB185900498A08 /* Release */ = { 275 | isa = XCBuildConfiguration; 276 | buildSettings = { 277 | ALWAYS_SEARCH_USER_PATHS = NO; 278 | CLANG_ANALYZER_NONNULL = YES; 279 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 280 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 281 | CLANG_CXX_LIBRARY = "libc++"; 282 | CLANG_ENABLE_MODULES = YES; 283 | CLANG_ENABLE_OBJC_ARC = YES; 284 | CLANG_ENABLE_OBJC_WEAK = YES; 285 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 286 | CLANG_WARN_BOOL_CONVERSION = YES; 287 | CLANG_WARN_COMMA = YES; 288 | CLANG_WARN_CONSTANT_CONVERSION = YES; 289 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 290 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 291 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 292 | CLANG_WARN_EMPTY_BODY = YES; 293 | CLANG_WARN_ENUM_CONVERSION = YES; 294 | CLANG_WARN_INFINITE_RECURSION = YES; 295 | CLANG_WARN_INT_CONVERSION = YES; 296 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 297 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 298 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 299 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 300 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 301 | CLANG_WARN_STRICT_PROTOTYPES = YES; 302 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 303 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 304 | CLANG_WARN_UNREACHABLE_CODE = YES; 305 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 306 | CODE_SIGN_IDENTITY = "iPhone Developer"; 307 | COPY_PHASE_STRIP = NO; 308 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 309 | ENABLE_NS_ASSERTIONS = NO; 310 | ENABLE_STRICT_OBJC_MSGSEND = YES; 311 | GCC_C_LANGUAGE_STANDARD = gnu11; 312 | GCC_NO_COMMON_BLOCKS = YES; 313 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 314 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 315 | GCC_WARN_UNDECLARED_SELECTOR = YES; 316 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 317 | GCC_WARN_UNUSED_FUNCTION = YES; 318 | GCC_WARN_UNUSED_VARIABLE = YES; 319 | IPHONEOS_DEPLOYMENT_TARGET = 12.2; 320 | MTL_ENABLE_DEBUG_INFO = NO; 321 | MTL_FAST_MATH = YES; 322 | SDKROOT = iphoneos; 323 | SWIFT_COMPILATION_MODE = wholemodule; 324 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 325 | VALIDATE_PRODUCT = YES; 326 | }; 327 | name = Release; 328 | }; 329 | D3D93CE022AB185900498A08 /* Debug */ = { 330 | isa = XCBuildConfiguration; 331 | buildSettings = { 332 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 333 | CODE_SIGN_STYLE = Automatic; 334 | DEVELOPMENT_TEAM = E4Z63835UE; 335 | INFOPLIST_FILE = SCNHighlight/Info.plist; 336 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 337 | LD_RUNPATH_SEARCH_PATHS = ( 338 | "$(inherited)", 339 | "@executable_path/Frameworks", 340 | ); 341 | PRODUCT_BUNDLE_IDENTIFIER = albertotaiuti.SCNHighlight; 342 | PRODUCT_NAME = "$(TARGET_NAME)"; 343 | SWIFT_VERSION = 5.0; 344 | TARGETED_DEVICE_FAMILY = "1,2"; 345 | }; 346 | name = Debug; 347 | }; 348 | D3D93CE122AB185900498A08 /* Release */ = { 349 | isa = XCBuildConfiguration; 350 | buildSettings = { 351 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 352 | CODE_SIGN_STYLE = Automatic; 353 | DEVELOPMENT_TEAM = E4Z63835UE; 354 | INFOPLIST_FILE = SCNHighlight/Info.plist; 355 | IPHONEOS_DEPLOYMENT_TARGET = 12.1; 356 | LD_RUNPATH_SEARCH_PATHS = ( 357 | "$(inherited)", 358 | "@executable_path/Frameworks", 359 | ); 360 | PRODUCT_BUNDLE_IDENTIFIER = albertotaiuti.SCNHighlight; 361 | PRODUCT_NAME = "$(TARGET_NAME)"; 362 | SWIFT_VERSION = 5.0; 363 | TARGETED_DEVICE_FAMILY = "1,2"; 364 | }; 365 | name = Release; 366 | }; 367 | /* End XCBuildConfiguration section */ 368 | 369 | /* Begin XCConfigurationList section */ 370 | D3D93CC622AB185700498A08 /* Build configuration list for PBXProject "SCNHighlight" */ = { 371 | isa = XCConfigurationList; 372 | buildConfigurations = ( 373 | D3D93CDD22AB185900498A08 /* Debug */, 374 | D3D93CDE22AB185900498A08 /* Release */, 375 | ); 376 | defaultConfigurationIsVisible = 0; 377 | defaultConfigurationName = Release; 378 | }; 379 | D3D93CDF22AB185900498A08 /* Build configuration list for PBXNativeTarget "SCNHighlight" */ = { 380 | isa = XCConfigurationList; 381 | buildConfigurations = ( 382 | D3D93CE022AB185900498A08 /* Debug */, 383 | D3D93CE122AB185900498A08 /* Release */, 384 | ); 385 | defaultConfigurationIsVisible = 0; 386 | defaultConfigurationName = Release; 387 | }; 388 | /* End XCConfigurationList section */ 389 | }; 390 | rootObject = D3D93CC322AB185700498A08 /* Project object */; 391 | } 392 | -------------------------------------------------------------------------------- /SCNHighlight.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SCNHighlight.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SCNHighlight.xcodeproj/xcshareddata/xcschemes/SCNHighlight.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 55 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 74 | 76 | 82 | 83 | 84 | 85 | 87 | 88 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /SCNHighlight.xcodeproj/xcuserdata/alberto.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SCNHighlight.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | D3D93CCA22AB185700498A08 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /SCNHighlight/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SCNHighlight 4 | // 5 | // Created by Alberto Taiuti on 07/06/2019. 6 | // Copyright © 2019 Alberto Taiuti. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 18 | 19 | // Programmatically launch the first viewcontroller 20 | let vc = GameViewController() 21 | window = UIWindow(frame: UIScreen.main.bounds) 22 | window!.rootViewController = vc 23 | window!.makeKeyAndVisible() 24 | 25 | return true 26 | } 27 | 28 | func applicationWillResignActive(_ application: UIApplication) { 29 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 30 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 31 | } 32 | 33 | func applicationDidEnterBackground(_ application: UIApplication) { 34 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 35 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 36 | } 37 | 38 | func applicationWillEnterForeground(_ application: UIApplication) { 39 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 40 | } 41 | 42 | func applicationDidBecomeActive(_ application: UIApplication) { 43 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 44 | } 45 | 46 | func applicationWillTerminate(_ application: UIApplication) { 47 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /SCNHighlight/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 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UIStatusBarHidden 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeRight 35 | UIInterfaceOrientationLandscapeLeft 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "spaceship-20.0px-@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "spaceship-20.0px-@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "spaceship-29.0px-@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "spaceship-29.0px-@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "spaceship-29.0px-@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "spaceship-40.0px-@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "spaceship-40.0px-@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "spaceship-60.0px-@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "spaceship-60.0px-@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "spaceship-20.0px-@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "spaceship-20.0px-@2x-1.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "spaceship-29.0px-@1x-1.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "spaceship-29.0px-@2x-1.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "spaceship-40.0px-@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "spaceship-40.0px-@2x-1.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "spaceship-76.0px-@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "spaceship-76.0px-@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "spaceship-83.5px-@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "spaceship-1024px-@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-1024px-@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-1024px-@1x.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-20.0px-@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-20.0px-@1x.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-20.0px-@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-20.0px-@2x-1.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-20.0px-@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-20.0px-@2x.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-20.0px-@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-20.0px-@3x.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-29.0px-@1x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-29.0px-@1x-1.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-29.0px-@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-29.0px-@1x.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-29.0px-@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-29.0px-@2x-1.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-29.0px-@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-29.0px-@2x.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-29.0px-@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-29.0px-@3x.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-40.0px-@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-40.0px-@1x.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-40.0px-@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-40.0px-@2x-1.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-40.0px-@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-40.0px-@2x.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-40.0px-@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-40.0px-@3x.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-60.0px-@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-60.0px-@2x.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-60.0px-@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-60.0px-@3x.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-76.0px-@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-76.0px-@1x.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-76.0px-@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-76.0px-@2x.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-83.5px-@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/Assets.xcassets/AppIcon.appiconset/spaceship-83.5px-@2x.png -------------------------------------------------------------------------------- /SCNHighlight/assets/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SCNHighlight/assets/art.scnassets/ship.scn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/art.scnassets/ship.scn -------------------------------------------------------------------------------- /SCNHighlight/assets/art.scnassets/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/SCNHighlight/assets/art.scnassets/texture.png -------------------------------------------------------------------------------- /SCNHighlight/assets/scene_techniques/RenderOutlineShaders.metal: -------------------------------------------------------------------------------- 1 | // 2 | // RenderOutlineShades.metal 3 | // scape-ar-project 4 | // 5 | // Created by Alberto Taiuti on 16/05/2019. 6 | // Copyright © 2019 Alberto Taiuti. All rights reserved. 7 | // 8 | 9 | #include 10 | using namespace metal; 11 | #include 12 | 13 | struct custom_node_t3 { 14 | float4x4 modelTransform; 15 | float4x4 modelViewTransform; 16 | float4x4 normalTransform; 17 | float4x4 modelViewProjectionTransform; 18 | }; 19 | 20 | struct custom_vertex_t 21 | { 22 | float4 position [[attribute(SCNVertexSemanticPosition)]]; 23 | float4 normal [[attribute(SCNVertexSemanticNormal)]]; 24 | }; 25 | 26 | struct out_vertex_t 27 | { 28 | float4 position [[position]]; 29 | float2 uv; 30 | }; 31 | 32 | 33 | vertex out_vertex_t mask_vertex(custom_vertex_t in [[stage_in]], 34 | constant custom_node_t3& scn_node [[buffer(0)]]) 35 | { 36 | out_vertex_t out; 37 | out.position = scn_node.modelViewProjectionTransform * float4(in.position.xyz, 1.0); 38 | return out; 39 | }; 40 | 41 | fragment half4 mask_fragment(out_vertex_t in [[stage_in]]) 42 | { 43 | return half4(1.0); 44 | }; 45 | 46 | 47 | constexpr sampler s = sampler(coord::normalized, 48 | r_address::clamp_to_edge, 49 | t_address::clamp_to_edge, 50 | filter::linear); 51 | 52 | vertex out_vertex_t combine_vertex(custom_vertex_t in [[stage_in]]) 53 | { 54 | out_vertex_t out; 55 | out.position = in.position; 56 | out.uv = float2( (in.position.x + 1.0) * 0.5, 1.0 - (in.position.y + 1.0) * 0.5 ); 57 | return out; 58 | }; 59 | 60 | 61 | fragment half4 combine_fragment(out_vertex_t vert [[stage_in]], 62 | texture2d colorSampler [[texture(0)]], 63 | texture2d maskSampler [[texture(1)]]) 64 | { 65 | 66 | float4 FragmentColor = colorSampler.sample( s, vert.uv); 67 | float4 maskColor = maskSampler.sample(s, vert.uv); 68 | 69 | if ( maskColor.g >= 1.0 ) { 70 | return half4(FragmentColor); 71 | } 72 | 73 | float3 glowColor = float3(1.0, 1.0, 0.0); 74 | 75 | float alpha = maskColor.r; 76 | if (alpha > 0.0) { 77 | alpha = 1; 78 | } 79 | float3 out = FragmentColor.rgb * ( 1.0 - alpha ) + alpha * glowColor; 80 | return half4( float4(out.rgb, 1.0) ); 81 | 82 | } 83 | 84 | ///// Blur ////// 85 | 86 | vertex out_vertex_t blur_vertex(custom_vertex_t in [[stage_in]]) 87 | { 88 | out_vertex_t out; 89 | out.position = in.position; 90 | out.uv = float2( (in.position.x + 1.0) * 0.5, 1.0 - (in.position.y + 1.0) * 0.5 ); 91 | return out; 92 | }; 93 | 94 | // http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/ 95 | constant float offset[] = { 0.0, 1.0, 2.0, 3.0, 4.0 }; 96 | constant float weight[] = { 0.2270270270, 0.1945945946, 0.1216216216, 0.0540540541, 0.0162162162 }; 97 | constant float bufferSize = 512.0; 98 | 99 | fragment half4 blur_fragment_h(out_vertex_t vert [[stage_in]], 100 | texture2d maskSampler [[texture(0)]]) 101 | { 102 | 103 | float4 FragmentColor = maskSampler.sample( s, vert.uv); 104 | float FragmentR = FragmentColor.r * weight[0]; 105 | 106 | 107 | for (int i=1; i<5; i++) { 108 | FragmentR += maskSampler.sample( s, ( vert.uv + float2(offset[i], 0.0)/bufferSize ) ).r * weight[i]; 109 | FragmentR += maskSampler.sample( s, ( vert.uv - float2(offset[i], 0.0)/bufferSize ) ).r * weight[i]; 110 | } 111 | return half4(FragmentR, FragmentColor.g, FragmentColor.b, 1.0); 112 | } 113 | 114 | fragment half4 blur_fragment_v(out_vertex_t vert [[stage_in]], 115 | texture2d maskSampler [[texture(0)]]) 116 | { 117 | 118 | float4 FragmentColor = maskSampler.sample( s, vert.uv); 119 | float FragmentR = FragmentColor.r * weight[0]; 120 | 121 | for (int i=1; i<5; i++) { 122 | FragmentR += maskSampler.sample( s, ( vert.uv + float2(0.0, offset[i])/bufferSize ) ).r * weight[i]; 123 | FragmentR += maskSampler.sample( s, ( vert.uv - float2(0.0, offset[i])/bufferSize ) ).r * weight[i]; 124 | } 125 | 126 | return half4(FragmentR, FragmentColor.g, FragmentColor.b, 1.0); 127 | 128 | }; 129 | -------------------------------------------------------------------------------- /SCNHighlight/assets/scene_techniques/RenderOutlineTechnique.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | passes 6 | 7 | pass_draw_masks 8 | 9 | draw 10 | DRAW_SCENE 11 | program 12 | doesntexist 13 | metalVertexShader 14 | mask_vertex 15 | metalFragmentShader 16 | mask_fragment 17 | includeCategoryMask 18 | 2 19 | inputs 20 | 21 | colorSampler 22 | COLOR 23 | aPos 24 | vertexSymbol 25 | 26 | outputs 27 | 28 | color 29 | MASK 30 | 31 | 32 | pass_combine 33 | 34 | colorStates 35 | 36 | clear 37 | 38 | 39 | inputs 40 | 41 | aPos 42 | vertexSymbol 43 | colorSampler 44 | COLOR 45 | maskSampler 46 | MASK 47 | 48 | outputs 49 | 50 | color 51 | COLOR 52 | 53 | metalFragmentShader 54 | combine_fragment 55 | metalVertexShader 56 | combine_vertex 57 | program 58 | doesntexist 59 | draw 60 | DRAW_QUAD 61 | 62 | pass_blur_h 63 | 64 | colorStates 65 | 66 | clear 67 | 68 | 69 | inputs 70 | 71 | aPos 72 | vertexSymbol 73 | maskSampler 74 | MASK 75 | 76 | outputs 77 | 78 | color 79 | MASK 80 | 81 | metalFragmentShader 82 | blur_fragment_h 83 | metalVertexShader 84 | blur_vertex 85 | program 86 | doesntexist 87 | draw 88 | DRAW_QUAD 89 | 90 | pass_blur_v 91 | 92 | colorStates 93 | 94 | clear 95 | 96 | 97 | inputs 98 | 99 | aPos 100 | vertexSymbol 101 | maskSampler 102 | MASK 103 | 104 | outputs 105 | 106 | color 107 | MASK 108 | 109 | metalFragmentShader 110 | blur_fragment_v 111 | metalVertexShader 112 | blur_vertex 113 | program 114 | doesntexist 115 | draw 116 | DRAW_QUAD 117 | 118 | 119 | sequence 120 | 121 | pass_draw_masks 122 | pass_blur_h 123 | pass_blur_v 124 | pass_combine 125 | 126 | symbols 127 | 128 | vertexSymbol 129 | 130 | semantic 131 | vertex 132 | 133 | 134 | targets 135 | 136 | MASK 137 | 138 | type 139 | color 140 | format 141 | rgb 142 | size 143 | 512x512 144 | scaleFactor 145 | 1 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /SCNHighlight/controllers/GameViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GameViewController.swift 3 | // SCNHighlight 4 | // 5 | // Created by Alberto Taiuti on 07/06/2019. 6 | // Copyright © 2019 Alberto Taiuti. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import QuartzCore 11 | import SceneKit 12 | 13 | fileprivate let highlightMaskValue: Int = 2 14 | fileprivate let normalMaskValue: Int = 1 15 | 16 | class GameViewController: UIViewController { 17 | 18 | private let scnView = SCNView() 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | 23 | // Generic stuff 24 | setupHierarchy() 25 | setupSCNScene() 26 | 27 | // Here we load the technique we'll use to achieve a highlight effect around 28 | // selected nodes 29 | if let fileUrl = Bundle.main.url(forResource: "RenderOutlineTechnique", withExtension: "plist"), let data = try? Data(contentsOf: fileUrl) { 30 | if var result = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] { // [String: Any] which ever it is 31 | 32 | // Here we update the size and scale factor in the original technique file 33 | // to whichever size and scale factor the current device is so that 34 | // we avoid crazy aliasing 35 | let nativePoints = UIScreen.main.bounds 36 | let nativeScale = UIScreen.main.nativeScale 37 | result[keyPath: "targets.MASK.size"] = "\(nativePoints.width)x\(nativePoints.height)" 38 | result[keyPath: "targets.MASK.scaleFactor"] = nativeScale 39 | 40 | guard let technique = SCNTechnique(dictionary: result) else { 41 | fatalError("This shouldn't be happening! 🤔") 42 | } 43 | 44 | scnView.technique = technique 45 | } 46 | } 47 | else { 48 | fatalError("This shouldn't be happening! Has someone been naughty and deleted the file? 🤔") 49 | } 50 | } 51 | 52 | // Tap to set/unset the highlight 53 | @objc private func handleTap(_ gestureRecognize: UIGestureRecognizer) { 54 | // check what nodes are tapped 55 | let p = gestureRecognize.location(in: scnView) 56 | let hitResults = scnView.hitTest(p, options: [:]) 57 | 58 | // check that we clicked on at least one object 59 | guard let first = hitResults.first else { 60 | return 61 | } 62 | 63 | // Highlight it / remove highlight 64 | if first.node.categoryBitMask == highlightMaskValue { 65 | first.node.setCategoryBitMaskForAllHierarchy(normalMaskValue) 66 | } 67 | else if first.node.categoryBitMask == normalMaskValue { 68 | first.node.setCategoryBitMaskForAllHierarchy(highlightMaskValue) 69 | } 70 | else { 71 | fatalError("Unsupported category bit mask value") 72 | } 73 | } 74 | 75 | override var shouldAutorotate: Bool { 76 | return true 77 | } 78 | 79 | override var prefersStatusBarHidden: Bool { 80 | return true 81 | } 82 | 83 | override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 84 | if UIDevice.current.userInterfaceIdiom == .phone { 85 | return .allButUpsideDown 86 | } else { 87 | return .all 88 | } 89 | } 90 | 91 | } 92 | 93 | extension GameViewController { 94 | 95 | private func setupHierarchy() { 96 | dispatchPrecondition(condition: .onQueue(.main)) 97 | 98 | scnView.translatesAutoresizingMaskIntoConstraints = false 99 | view.addSubview(scnView) 100 | scnView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true 101 | scnView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true 102 | scnView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true 103 | scnView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true 104 | 105 | // add a tap gesture recognizer 106 | let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) 107 | scnView.addGestureRecognizer(tapGesture) 108 | } 109 | 110 | private func setupSCNScene() { 111 | // create a new scene 112 | let scene = SCNScene(named: "art.scnassets/ship.scn")! 113 | 114 | // create and add a camera to the scene 115 | let cameraNode = SCNNode() 116 | cameraNode.camera = SCNCamera() 117 | scene.rootNode.addChildNode(cameraNode) 118 | 119 | // place the camera 120 | cameraNode.simdPosition.z = 15 121 | 122 | // create and add a light to the scene 123 | let lightNode = SCNNode() 124 | lightNode.light = SCNLight() 125 | lightNode.light!.type = .omni 126 | lightNode.simdPosition = simd_float3(0, 10, 10) 127 | scene.rootNode.addChildNode(lightNode) 128 | 129 | // create and add an ambient light to the scene 130 | let ambientLightNode = SCNNode() 131 | ambientLightNode.light = SCNLight() 132 | ambientLightNode.light!.type = .ambient 133 | ambientLightNode.light!.color = UIColor.darkGray 134 | scene.rootNode.addChildNode(ambientLightNode) 135 | 136 | // retrieve the ship node 137 | let ship = scene.rootNode.childNode(withName: "ship", recursively: true)! 138 | 139 | // animate the 3d object 140 | ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1))) 141 | 142 | // Set its highlight 143 | ship.setCategoryBitMaskForAllHierarchy(highlightMaskValue) 144 | 145 | // set the scene to the view 146 | scnView.scene = scene 147 | 148 | // allows the user to manipulate the camera 149 | scnView.allowsCameraControl = true 150 | 151 | // show statistics such as fps and timing information 152 | scnView.showsStatistics = true 153 | 154 | // configure the view 155 | scnView.backgroundColor = UIColor.black 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /SCNHighlight/extensions/Dictionary+Extensions.swift: -------------------------------------------------------------------------------- 1 | //// 2 | // Dictionary+Extension.swift 3 | // SCNTechniqueGlow 4 | // 5 | // Created by Alberto Taiuti on 15/05/2019. 6 | // Copyright © 2019 Alberto Taiuti. All rights reserved. 7 | // 8 | // From https://oleb.net/blog/2017/01/dictionary-key-paths/ 9 | 10 | import Foundation 11 | 12 | struct KeyPath { 13 | var segments: [String] 14 | 15 | var isEmpty: Bool { return segments.isEmpty } 16 | var path: String { 17 | return segments.joined(separator: ".") 18 | } 19 | 20 | /// Strips off the first segment and returns a pair 21 | /// consisting of the first segment and the remaining key path. 22 | /// Returns nil if the key path has no segments. 23 | func headAndTail() -> (head: String, tail: KeyPath)? { 24 | guard !isEmpty else { return nil } 25 | var tail = segments 26 | let head = tail.removeFirst() 27 | return (head, KeyPath(segments: tail)) 28 | } 29 | } 30 | 31 | 32 | /// Initializes a KeyPath with a string of the form "this.is.a.keypath" 33 | extension KeyPath { 34 | init(_ string: String) { 35 | segments = string.components(separatedBy: ".") 36 | } 37 | } 38 | 39 | extension KeyPath: ExpressibleByStringLiteral { 40 | init(stringLiteral value: String) { 41 | self.init(value) 42 | } 43 | init(unicodeScalarLiteral value: String) { 44 | self.init(value) 45 | } 46 | init(extendedGraphemeClusterLiteral value: String) { 47 | self.init(value) 48 | } 49 | } 50 | 51 | 52 | // Needed because Swift 3.0 doesn't support extensions with concrete 53 | // same-type requirements (extension Dictionary where Key == String). 54 | protocol StringProtocol { 55 | init(string s: String) 56 | } 57 | 58 | extension String: StringProtocol { 59 | init(string s: String) { 60 | self = s 61 | } 62 | } 63 | 64 | extension Dictionary where Key: StringProtocol { 65 | subscript(keyPath keyPath: KeyPath) -> Any? { 66 | get { 67 | switch keyPath.headAndTail() { 68 | case nil: 69 | // key path is empty. 70 | return nil 71 | case let (head, remainingKeyPath)? where remainingKeyPath.isEmpty: 72 | // Reached the end of the key path. 73 | let key = Key(string: head) 74 | return self[key] 75 | case let (head, remainingKeyPath)?: 76 | // Key path has a tail we need to traverse. 77 | let key = Key(string: head) 78 | switch self[key] { 79 | case let nestedDict as [Key: Any]: 80 | // Next nest level is a dictionary. 81 | // Start over with remaining key path. 82 | return nestedDict[keyPath: remainingKeyPath] 83 | default: 84 | // Next nest level isn't a dictionary. 85 | // Invalid key path, abort. 86 | return nil 87 | } 88 | } 89 | } 90 | set { 91 | switch keyPath.headAndTail() { 92 | case nil: 93 | // key path is empty. 94 | return 95 | case let (head, remainingKeyPath)? where remainingKeyPath.isEmpty: 96 | // Reached the end of the key path. 97 | let key = Key(string: head) 98 | self[key] = newValue as? Value 99 | case let (head, remainingKeyPath)?: 100 | let key = Key(string: head) 101 | let value = self[key] 102 | switch value { 103 | case var nestedDict as [Key: Any]: 104 | // Key path has a tail we need to traverse 105 | nestedDict[keyPath: remainingKeyPath] = newValue 106 | self[key] = nestedDict as? Value 107 | default: 108 | // Invalid keyPath 109 | return 110 | } 111 | } 112 | } 113 | } 114 | } 115 | 116 | extension Dictionary where Key: StringProtocol { 117 | subscript(string keyPath: KeyPath) -> String? { 118 | get { return self[keyPath: keyPath] as? String } 119 | set { self[keyPath: keyPath] = newValue } 120 | } 121 | 122 | subscript(dict keyPath: KeyPath) -> [Key: Any]? { 123 | get { return self[keyPath: keyPath] as? [Key: Any] } 124 | set { self[keyPath: keyPath] = newValue } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /SCNHighlight/extensions/SCNNode+Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SCNNode+Extensions.swift 3 | // SCNHighlight 4 | // 5 | // Created by Alberto Taiuti on 08/06/2019. 6 | // Copyright © 2019 Alberto Taiuti. All rights reserved. 7 | // 8 | 9 | import SceneKit 10 | 11 | extension SCNNode { 12 | func setCategoryBitMaskForAllHierarchy(_ highlightedBitMask: Int = 2, 13 | nodesToExclude: Set = Set()) { 14 | if let selfName = name { 15 | if !nodesToExclude.contains(selfName) { 16 | categoryBitMask = highlightedBitMask 17 | } 18 | } 19 | else { 20 | categoryBitMask = highlightedBitMask 21 | } 22 | 23 | for child in self.childNodes { 24 | child.setCategoryBitMaskForAllHierarchy(highlightedBitMask, 25 | nodesToExclude: nodesToExclude) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /SCNHighlight/views/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /data/scnhighlight_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzurfer/SCNHighlight/e68884561338bbe38f47a379bb030418b520d43b/data/scnhighlight_demo.gif --------------------------------------------------------------------------------