├── .gitattributes ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── Resources └── ErrorSymbol.scn ├── examples ├── brainstem.gif └── scifihelmet.gif ├── glTF-quicklook.xcodeproj ├── project.pbxproj └── xcshareddata │ └── xcschemes │ └── glTF-qucklook.xcscheme └── glTF-quicklook ├── GeneratePreviewForURL.m ├── GenerateThumbnailForURL.m ├── Info.plist ├── SCNScene+BoundingBox.h ├── SCNScene+BoundingBox.m ├── SceneGenerator.h ├── SceneGenerator.m ├── ThumbnailGenerator.h ├── ThumbnailGenerator.m ├── TinyGLTFSCN ├── GLTFSCNAnimationTargetPair.h ├── GLTFSCNAnimationTargetPair.m ├── TinyGLTFSCN.h └── TinyGLTFSCN.mm └── main.c /.gitattributes: -------------------------------------------------------------------------------- 1 | tiny_gltf/* linguist-vendored 2 | glTF-quicklook/main.c linguist-vendored 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | glTF-qucklook.xcodeproj/xcuserdata/* 2 | 3 | # Xcode 4 | ## User settings 5 | xcuserdata/ 6 | 7 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 8 | *.xcscmblueprint 9 | *.xccheckout 10 | 11 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 12 | build/ 13 | DerivedData/ 14 | *.moved-aside 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | 24 | ### Xcode Patch ### 25 | *.xcodeproj/* 26 | !*.xcodeproj/project.pbxproj 27 | !*.xcodeproj/xcshareddata/ 28 | !*.xcworkspace/contents.xcworkspacedata 29 | /*.gcno 30 | **/xcshareddata/WorkspaceSettings.xcsettings 31 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tinygltf"] 2 | path = tinygltf 3 | url = https://github.com/syoyo/tinygltf.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Anton Klochkov 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 | # glTF-quicklook 2 | 3 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/eecaeefcb3854e6181403bea06e0dbcd)](https://www.codacy.com/app/toshiks/glTF-quicklook?utm_source=github.com&utm_medium=referral&utm_content=toshiks/glTF-quicklook&utm_campaign=Badge_Grade) 4 | ![status](https://img.shields.io/badge/glTF-2%2E0-green.svg?style=flat) 5 | [![License](http://img.shields.io/:license-mit-blue.svg)](https://github.com/toshiks/glTF-quicklook/blob/master/LICENSE) 6 | 7 | Simple QuickLook plugin for previewing gltf-files on macOS. 8 | 9 | ## Status 10 | 11 | v1.1 release(14 Sep, 2019) 12 | 13 | ## Features 14 | * glTF specification v2.0.0 15 | * Draco compression format 16 | * Animations (not for Draco compression format) 17 | * Textures 18 | 19 | ## Examples 20 | 21 | ![](examples/brainstem.gif) 22 | ![](examples/scifihelmet.gif) 23 | 24 | ## System Requirements 25 | 26 | - macOS 10.13 (High Sierra) or later 27 | - installed [Draco compression library](https://github.com/google/draco) 28 | 29 | ## Install 30 | 31 | ### Manually 32 | 33 | 1. In terminal run command: ```brew install draco``` 34 | 2. Download **glTF-qucklook_vX.X.zip** from [Releases](https://github.com/toshiks/glTF-quicklook/releases/latest). 35 | 3. Put **glTF-qucklook.qlgenerator** from zip file into 36 | 1. `/Library/QuickLook` - for all users; 37 | 2. `~/Library/QuickLook` - only for the logged-in user. 38 | 4. Run `qlmanage -r` command to reload QuickLook plugins. 39 | 40 | 41 | ## Licenses 42 | 43 | glTF-qucklook is licensed under MIT license. 44 | 45 | ## Third party licenses 46 | * [tiny-gltf](https://github.com/syoyo/tinygltf) - Copyright (c) 2017 Syoyo Fujita, Aurélien Chatelain 47 | * [GLTFKIT](https://github.com/warrenm/GLTFKit) - Copyright (c) 2017 Warren Moore -------------------------------------------------------------------------------- /Resources/ErrorSymbol.scn: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toshiks/glTF-quicklook/705d680bf359753894771b5e8e3a50021e16f146/Resources/ErrorSymbol.scn -------------------------------------------------------------------------------- /examples/brainstem.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toshiks/glTF-quicklook/705d680bf359753894771b5e8e3a50021e16f146/examples/brainstem.gif -------------------------------------------------------------------------------- /examples/scifihelmet.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toshiks/glTF-quicklook/705d680bf359753894771b5e8e3a50021e16f146/examples/scifihelmet.gif -------------------------------------------------------------------------------- /glTF-quicklook.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 27077F9121B57DD00081C27D /* ErrorSymbol.scn in Resources */ = {isa = PBXBuildFile; fileRef = 27077F9021B57DC60081C27D /* ErrorSymbol.scn */; }; 11 | 27077FCD21B5A20C0081C27D /* SceneGenerator.h in Headers */ = {isa = PBXBuildFile; fileRef = 27077F9321B58C0D0081C27D /* SceneGenerator.h */; }; 12 | 27077FCE21B5A20C0081C27D /* SceneGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 27077F9421B58C0D0081C27D /* SceneGenerator.m */; }; 13 | 27077FD121B5AE330081C27D /* ThumbnailGenerator.h in Headers */ = {isa = PBXBuildFile; fileRef = 27077FCF21B5AE330081C27D /* ThumbnailGenerator.h */; }; 14 | 27077FD221B5AE330081C27D /* ThumbnailGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 27077FD021B5AE330081C27D /* ThumbnailGenerator.m */; }; 15 | 27077FD521B5B24B0081C27D /* SCNScene+BoundingBox.h in Headers */ = {isa = PBXBuildFile; fileRef = 27077FD321B5B24B0081C27D /* SCNScene+BoundingBox.h */; }; 16 | 27077FD621B5B24B0081C27D /* SCNScene+BoundingBox.m in Sources */ = {isa = PBXBuildFile; fileRef = 27077FD421B5B24B0081C27D /* SCNScene+BoundingBox.m */; }; 17 | 2707800821B5C9140081C27D /* json.hpp in Frameworks */ = {isa = PBXBuildFile; fileRef = 2707800421B5C9140081C27D /* json.hpp */; }; 18 | 2707800921B5C9140081C27D /* stb_image.h in Frameworks */ = {isa = PBXBuildFile; fileRef = 2707800521B5C9140081C27D /* stb_image.h */; }; 19 | 2707800A21B5C9140081C27D /* stb_image_write.h in Frameworks */ = {isa = PBXBuildFile; fileRef = 2707800621B5C9140081C27D /* stb_image_write.h */; }; 20 | 2707800B21B5C9140081C27D /* tiny_gltf.h in Frameworks */ = {isa = PBXBuildFile; fileRef = 2707800721B5C9140081C27D /* tiny_gltf.h */; }; 21 | 2744673121B1F39100E8338E /* GenerateThumbnailForURL.m in Sources */ = {isa = PBXBuildFile; fileRef = 2744673021B1F39100E8338E /* GenerateThumbnailForURL.m */; }; 22 | 2744673321B1F39100E8338E /* GeneratePreviewForURL.m in Sources */ = {isa = PBXBuildFile; fileRef = 2744673221B1F39100E8338E /* GeneratePreviewForURL.m */; }; 23 | 2744673521B1F39100E8338E /* main.c in Sources */ = {isa = PBXBuildFile; fileRef = 2744673421B1F39100E8338E /* main.c */; }; 24 | 27BDD5C7232D26F400D0A2F1 /* TinyGLTFSCN.h in Headers */ = {isa = PBXBuildFile; fileRef = 27BDD5C5232D26F400D0A2F1 /* TinyGLTFSCN.h */; }; 25 | 27BDD5C8232D26F400D0A2F1 /* TinyGLTFSCN.mm in Sources */ = {isa = PBXBuildFile; fileRef = 27BDD5C6232D26F400D0A2F1 /* TinyGLTFSCN.mm */; }; 26 | 27BDD5CC232D292800D0A2F1 /* libdracodec.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27BDD5C9232D292700D0A2F1 /* libdracodec.a */; }; 27 | 27BDD5CD232D292800D0A2F1 /* libdraco.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27BDD5CA232D292700D0A2F1 /* libdraco.a */; }; 28 | 27BDD5CE232D292800D0A2F1 /* libdracoenc.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 27BDD5CB232D292800D0A2F1 /* libdracoenc.a */; }; 29 | 27BDD5D1232D31E000D0A2F1 /* GLTFSCNAnimationTargetPair.h in Headers */ = {isa = PBXBuildFile; fileRef = 27BDD5CF232D31E000D0A2F1 /* GLTFSCNAnimationTargetPair.h */; }; 30 | 27BDD5D4232D6EB900D0A2F1 /* GLTFSCNAnimationTargetPair.m in Sources */ = {isa = PBXBuildFile; fileRef = 27BDD5D3232D6EB900D0A2F1 /* GLTFSCNAnimationTargetPair.m */; }; 31 | /* End PBXBuildFile section */ 32 | 33 | /* Begin PBXCopyFilesBuildPhase section */ 34 | 2744674E21B1FA1100E8338E /* CopyFiles */ = { 35 | isa = PBXCopyFilesBuildPhase; 36 | buildActionMask = 2147483647; 37 | dstPath = ""; 38 | dstSubfolderSpec = 10; 39 | files = ( 40 | ); 41 | runOnlyForDeploymentPostprocessing = 0; 42 | }; 43 | /* End PBXCopyFilesBuildPhase section */ 44 | 45 | /* Begin PBXFileReference section */ 46 | 27077F9021B57DC60081C27D /* ErrorSymbol.scn */ = {isa = PBXFileReference; lastKnownFileType = file.bplist; name = ErrorSymbol.scn; path = Resources/ErrorSymbol.scn; sourceTree = ""; }; 47 | 27077F9321B58C0D0081C27D /* SceneGenerator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SceneGenerator.h; sourceTree = ""; }; 48 | 27077F9421B58C0D0081C27D /* SceneGenerator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SceneGenerator.m; sourceTree = ""; }; 49 | 27077FCF21B5AE330081C27D /* ThumbnailGenerator.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ThumbnailGenerator.h; sourceTree = ""; }; 50 | 27077FD021B5AE330081C27D /* ThumbnailGenerator.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ThumbnailGenerator.m; sourceTree = ""; }; 51 | 27077FD321B5B24B0081C27D /* SCNScene+BoundingBox.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SCNScene+BoundingBox.h"; sourceTree = ""; }; 52 | 27077FD421B5B24B0081C27D /* SCNScene+BoundingBox.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SCNScene+BoundingBox.m"; sourceTree = ""; }; 53 | 2707800421B5C9140081C27D /* json.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = json.hpp; path = tinygltf/json.hpp; sourceTree = ""; }; 54 | 2707800521B5C9140081C27D /* stb_image.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = stb_image.h; path = tinygltf/stb_image.h; sourceTree = ""; }; 55 | 2707800621B5C9140081C27D /* stb_image_write.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = stb_image_write.h; path = tinygltf/stb_image_write.h; sourceTree = ""; }; 56 | 2707800721B5C9140081C27D /* tiny_gltf.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = tiny_gltf.h; path = tinygltf/tiny_gltf.h; sourceTree = ""; }; 57 | 2744672D21B1F39100E8338E /* glTF-quicklook.qlgenerator */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "glTF-quicklook.qlgenerator"; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | 2744673021B1F39100E8338E /* GenerateThumbnailForURL.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GenerateThumbnailForURL.m; sourceTree = ""; }; 59 | 2744673221B1F39100E8338E /* GeneratePreviewForURL.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GeneratePreviewForURL.m; sourceTree = ""; }; 60 | 2744673421B1F39100E8338E /* main.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = main.c; sourceTree = ""; }; 61 | 2744673621B1F39100E8338E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 62 | 27BDD5C5232D26F400D0A2F1 /* TinyGLTFSCN.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TinyGLTFSCN.h; sourceTree = ""; }; 63 | 27BDD5C6232D26F400D0A2F1 /* TinyGLTFSCN.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = TinyGLTFSCN.mm; sourceTree = ""; }; 64 | 27BDD5C9232D292700D0A2F1 /* libdracodec.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libdracodec.a; path = ../../../../usr/local/Cellar/draco/1.3.5/lib/libdracodec.a; sourceTree = ""; }; 65 | 27BDD5CA232D292700D0A2F1 /* libdraco.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libdraco.a; path = ../../../../usr/local/Cellar/draco/1.3.5/lib/libdraco.a; sourceTree = ""; }; 66 | 27BDD5CB232D292800D0A2F1 /* libdracoenc.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libdracoenc.a; path = ../../../../usr/local/Cellar/draco/1.3.5/lib/libdracoenc.a; sourceTree = ""; }; 67 | 27BDD5CF232D31E000D0A2F1 /* GLTFSCNAnimationTargetPair.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GLTFSCNAnimationTargetPair.h; sourceTree = ""; }; 68 | 27BDD5D3232D6EB900D0A2F1 /* GLTFSCNAnimationTargetPair.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GLTFSCNAnimationTargetPair.m; sourceTree = ""; }; 69 | /* End PBXFileReference section */ 70 | 71 | /* Begin PBXFrameworksBuildPhase section */ 72 | 2744672A21B1F39100E8338E /* Frameworks */ = { 73 | isa = PBXFrameworksBuildPhase; 74 | buildActionMask = 2147483647; 75 | files = ( 76 | 27BDD5CC232D292800D0A2F1 /* libdracodec.a in Frameworks */, 77 | 27BDD5CD232D292800D0A2F1 /* libdraco.a in Frameworks */, 78 | 27BDD5CE232D292800D0A2F1 /* libdracoenc.a in Frameworks */, 79 | 2707800821B5C9140081C27D /* json.hpp in Frameworks */, 80 | 2707800921B5C9140081C27D /* stb_image.h in Frameworks */, 81 | 2707800A21B5C9140081C27D /* stb_image_write.h in Frameworks */, 82 | 2707800B21B5C9140081C27D /* tiny_gltf.h in Frameworks */, 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | /* End PBXFrameworksBuildPhase section */ 87 | 88 | /* Begin PBXGroup section */ 89 | 27077F9221B58AC50081C27D /* Resources */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 27077F9021B57DC60081C27D /* ErrorSymbol.scn */, 93 | ); 94 | name = Resources; 95 | sourceTree = ""; 96 | }; 97 | 2744672321B1F39100E8338E = { 98 | isa = PBXGroup; 99 | children = ( 100 | 27077F9221B58AC50081C27D /* Resources */, 101 | 2744672F21B1F39100E8338E /* glTF-quicklook */, 102 | 2744672E21B1F39100E8338E /* Products */, 103 | 2744673F21B1F94300E8338E /* Frameworks */, 104 | ); 105 | sourceTree = ""; 106 | }; 107 | 2744672E21B1F39100E8338E /* Products */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 2744672D21B1F39100E8338E /* glTF-quicklook.qlgenerator */, 111 | ); 112 | name = Products; 113 | sourceTree = ""; 114 | }; 115 | 2744672F21B1F39100E8338E /* glTF-quicklook */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 27BDD5C2232D26AA00D0A2F1 /* TinyGLTFSCN */, 119 | 2744673021B1F39100E8338E /* GenerateThumbnailForURL.m */, 120 | 2744673221B1F39100E8338E /* GeneratePreviewForURL.m */, 121 | 2744673421B1F39100E8338E /* main.c */, 122 | 2744673621B1F39100E8338E /* Info.plist */, 123 | 27077F9321B58C0D0081C27D /* SceneGenerator.h */, 124 | 27077F9421B58C0D0081C27D /* SceneGenerator.m */, 125 | 27077FCF21B5AE330081C27D /* ThumbnailGenerator.h */, 126 | 27077FD021B5AE330081C27D /* ThumbnailGenerator.m */, 127 | 27077FD321B5B24B0081C27D /* SCNScene+BoundingBox.h */, 128 | 27077FD421B5B24B0081C27D /* SCNScene+BoundingBox.m */, 129 | ); 130 | path = "glTF-quicklook"; 131 | sourceTree = ""; 132 | }; 133 | 2744673F21B1F94300E8338E /* Frameworks */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 27BDD5CA232D292700D0A2F1 /* libdraco.a */, 137 | 27BDD5C9232D292700D0A2F1 /* libdracodec.a */, 138 | 27BDD5CB232D292800D0A2F1 /* libdracoenc.a */, 139 | 2707800421B5C9140081C27D /* json.hpp */, 140 | 2707800621B5C9140081C27D /* stb_image_write.h */, 141 | 2707800521B5C9140081C27D /* stb_image.h */, 142 | 2707800721B5C9140081C27D /* tiny_gltf.h */, 143 | ); 144 | name = Frameworks; 145 | sourceTree = ""; 146 | }; 147 | 27BDD5C2232D26AA00D0A2F1 /* TinyGLTFSCN */ = { 148 | isa = PBXGroup; 149 | children = ( 150 | 27BDD5C5232D26F400D0A2F1 /* TinyGLTFSCN.h */, 151 | 27BDD5C6232D26F400D0A2F1 /* TinyGLTFSCN.mm */, 152 | 27BDD5CF232D31E000D0A2F1 /* GLTFSCNAnimationTargetPair.h */, 153 | 27BDD5D3232D6EB900D0A2F1 /* GLTFSCNAnimationTargetPair.m */, 154 | ); 155 | path = TinyGLTFSCN; 156 | sourceTree = ""; 157 | }; 158 | /* End PBXGroup section */ 159 | 160 | /* Begin PBXHeadersBuildPhase section */ 161 | 2744672821B1F39100E8338E /* Headers */ = { 162 | isa = PBXHeadersBuildPhase; 163 | buildActionMask = 2147483647; 164 | files = ( 165 | 27077FD121B5AE330081C27D /* ThumbnailGenerator.h in Headers */, 166 | 27BDD5D1232D31E000D0A2F1 /* GLTFSCNAnimationTargetPair.h in Headers */, 167 | 27077FCD21B5A20C0081C27D /* SceneGenerator.h in Headers */, 168 | 27BDD5C7232D26F400D0A2F1 /* TinyGLTFSCN.h in Headers */, 169 | 27077FD521B5B24B0081C27D /* SCNScene+BoundingBox.h in Headers */, 170 | ); 171 | runOnlyForDeploymentPostprocessing = 0; 172 | }; 173 | /* End PBXHeadersBuildPhase section */ 174 | 175 | /* Begin PBXNativeTarget section */ 176 | 2744672C21B1F39100E8338E /* glTF-quicklook */ = { 177 | isa = PBXNativeTarget; 178 | buildConfigurationList = 2744673921B1F39100E8338E /* Build configuration list for PBXNativeTarget "glTF-quicklook" */; 179 | buildPhases = ( 180 | 2744672821B1F39100E8338E /* Headers */, 181 | 2744672921B1F39100E8338E /* Sources */, 182 | 2744672A21B1F39100E8338E /* Frameworks */, 183 | 2744672B21B1F39100E8338E /* Resources */, 184 | 2744674E21B1FA1100E8338E /* CopyFiles */, 185 | ); 186 | buildRules = ( 187 | ); 188 | dependencies = ( 189 | ); 190 | name = "glTF-quicklook"; 191 | productName = "glTF-qucklook"; 192 | productReference = 2744672D21B1F39100E8338E /* glTF-quicklook.qlgenerator */; 193 | productType = "com.apple.product-type.bundle"; 194 | }; 195 | /* End PBXNativeTarget section */ 196 | 197 | /* Begin PBXProject section */ 198 | 2744672421B1F39100E8338E /* Project object */ = { 199 | isa = PBXProject; 200 | attributes = { 201 | LastUpgradeCheck = 1010; 202 | ORGANIZATIONNAME = "Klochkov Anton"; 203 | TargetAttributes = { 204 | 2744672C21B1F39100E8338E = { 205 | CreatedOnToolsVersion = 10.1; 206 | }; 207 | }; 208 | }; 209 | buildConfigurationList = 2744672721B1F39100E8338E /* Build configuration list for PBXProject "glTF-quicklook" */; 210 | compatibilityVersion = "Xcode 9.3"; 211 | developmentRegion = en; 212 | hasScannedForEncodings = 0; 213 | knownRegions = ( 214 | en, 215 | Base, 216 | ); 217 | mainGroup = 2744672321B1F39100E8338E; 218 | productRefGroup = 2744672E21B1F39100E8338E /* Products */; 219 | projectDirPath = ""; 220 | projectRoot = ""; 221 | targets = ( 222 | 2744672C21B1F39100E8338E /* glTF-quicklook */, 223 | ); 224 | }; 225 | /* End PBXProject section */ 226 | 227 | /* Begin PBXResourcesBuildPhase section */ 228 | 2744672B21B1F39100E8338E /* Resources */ = { 229 | isa = PBXResourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | 27077F9121B57DD00081C27D /* ErrorSymbol.scn in Resources */, 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | /* End PBXResourcesBuildPhase section */ 237 | 238 | /* Begin PBXSourcesBuildPhase section */ 239 | 2744672921B1F39100E8338E /* Sources */ = { 240 | isa = PBXSourcesBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | 27077FCE21B5A20C0081C27D /* SceneGenerator.m in Sources */, 244 | 2744673121B1F39100E8338E /* GenerateThumbnailForURL.m in Sources */, 245 | 27077FD221B5AE330081C27D /* ThumbnailGenerator.m in Sources */, 246 | 27BDD5D4232D6EB900D0A2F1 /* GLTFSCNAnimationTargetPair.m in Sources */, 247 | 2744673321B1F39100E8338E /* GeneratePreviewForURL.m in Sources */, 248 | 27077FD621B5B24B0081C27D /* SCNScene+BoundingBox.m in Sources */, 249 | 2744673521B1F39100E8338E /* main.c in Sources */, 250 | 27BDD5C8232D26F400D0A2F1 /* TinyGLTFSCN.mm in Sources */, 251 | ); 252 | runOnlyForDeploymentPostprocessing = 0; 253 | }; 254 | /* End PBXSourcesBuildPhase section */ 255 | 256 | /* Begin XCBuildConfiguration section */ 257 | 2744673721B1F39100E8338E /* Debug */ = { 258 | isa = XCBuildConfiguration; 259 | buildSettings = { 260 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 261 | ALWAYS_SEARCH_USER_PATHS = NO; 262 | CLANG_ANALYZER_NONNULL = YES; 263 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 264 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 265 | CLANG_CXX_LIBRARY = "libc++"; 266 | CLANG_ENABLE_MODULES = YES; 267 | CLANG_ENABLE_OBJC_ARC = YES; 268 | CLANG_ENABLE_OBJC_WEAK = YES; 269 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 270 | CLANG_WARN_BOOL_CONVERSION = YES; 271 | CLANG_WARN_COMMA = YES; 272 | CLANG_WARN_CONSTANT_CONVERSION = YES; 273 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 274 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 275 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 276 | CLANG_WARN_EMPTY_BODY = YES; 277 | CLANG_WARN_ENUM_CONVERSION = YES; 278 | CLANG_WARN_INFINITE_RECURSION = YES; 279 | CLANG_WARN_INT_CONVERSION = YES; 280 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 281 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 282 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 283 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 284 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 285 | CLANG_WARN_STRICT_PROTOTYPES = YES; 286 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 287 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 288 | CLANG_WARN_UNREACHABLE_CODE = YES; 289 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 290 | CODE_SIGN_IDENTITY = "Mac Developer"; 291 | COPY_PHASE_STRIP = NO; 292 | DEBUG_INFORMATION_FORMAT = dwarf; 293 | ENABLE_STRICT_OBJC_MSGSEND = YES; 294 | ENABLE_TESTABILITY = YES; 295 | GCC_C_LANGUAGE_STANDARD = gnu11; 296 | GCC_DYNAMIC_NO_PIC = NO; 297 | GCC_NO_COMMON_BLOCKS = YES; 298 | GCC_OPTIMIZATION_LEVEL = 0; 299 | GCC_PREPROCESSOR_DEFINITIONS = ( 300 | "DEBUG=1", 301 | "$(inherited)", 302 | ); 303 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 304 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 305 | GCC_WARN_UNDECLARED_SELECTOR = YES; 306 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 307 | GCC_WARN_UNUSED_FUNCTION = YES; 308 | GCC_WARN_UNUSED_VARIABLE = YES; 309 | MACOSX_DEPLOYMENT_TARGET = 10.14; 310 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 311 | MTL_FAST_MATH = YES; 312 | ONLY_ACTIVE_ARCH = YES; 313 | SDKROOT = macosx; 314 | }; 315 | name = Debug; 316 | }; 317 | 2744673821B1F39100E8338E /* Release */ = { 318 | isa = XCBuildConfiguration; 319 | buildSettings = { 320 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 321 | ALWAYS_SEARCH_USER_PATHS = NO; 322 | CLANG_ANALYZER_NONNULL = YES; 323 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 324 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 325 | CLANG_CXX_LIBRARY = "libc++"; 326 | CLANG_ENABLE_MODULES = YES; 327 | CLANG_ENABLE_OBJC_ARC = YES; 328 | CLANG_ENABLE_OBJC_WEAK = YES; 329 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 330 | CLANG_WARN_BOOL_CONVERSION = YES; 331 | CLANG_WARN_COMMA = YES; 332 | CLANG_WARN_CONSTANT_CONVERSION = YES; 333 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 334 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 335 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 336 | CLANG_WARN_EMPTY_BODY = YES; 337 | CLANG_WARN_ENUM_CONVERSION = YES; 338 | CLANG_WARN_INFINITE_RECURSION = YES; 339 | CLANG_WARN_INT_CONVERSION = YES; 340 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 341 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 342 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 343 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 344 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 345 | CLANG_WARN_STRICT_PROTOTYPES = YES; 346 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 347 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 348 | CLANG_WARN_UNREACHABLE_CODE = YES; 349 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 350 | CODE_SIGN_IDENTITY = "Mac Developer"; 351 | COPY_PHASE_STRIP = NO; 352 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 353 | ENABLE_NS_ASSERTIONS = NO; 354 | ENABLE_STRICT_OBJC_MSGSEND = YES; 355 | GCC_C_LANGUAGE_STANDARD = gnu11; 356 | GCC_NO_COMMON_BLOCKS = YES; 357 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 358 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 359 | GCC_WARN_UNDECLARED_SELECTOR = YES; 360 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 361 | GCC_WARN_UNUSED_FUNCTION = YES; 362 | GCC_WARN_UNUSED_VARIABLE = YES; 363 | MACOSX_DEPLOYMENT_TARGET = 10.14; 364 | MTL_ENABLE_DEBUG_INFO = NO; 365 | MTL_FAST_MATH = YES; 366 | SDKROOT = macosx; 367 | }; 368 | name = Release; 369 | }; 370 | 2744673A21B1F39100E8338E /* Debug */ = { 371 | isa = XCBuildConfiguration; 372 | buildSettings = { 373 | CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; 374 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 375 | CLANG_CXX_LIBRARY = "libc++"; 376 | CLANG_MODULES_DISABLE_PRIVATE_WARNING = YES; 377 | CODE_SIGN_STYLE = Automatic; 378 | COMBINE_HIDPI_IMAGES = YES; 379 | DEVELOPMENT_TEAM = 9K99AL2M82; 380 | EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = YES; 381 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 382 | GCC_INPUT_FILETYPE = automatic; 383 | HEADER_SEARCH_PATHS = /usr/local/Cellar/draco/1.3.5/include; 384 | INFOPLIST_FILE = "$(SRCROOT)/glTF-quicklook/Info.plist"; 385 | INSTALL_PATH = /Library/QuickLook; 386 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks/"; 387 | LIBRARY_SEARCH_PATHS = ( 388 | "$(inherited)", 389 | /usr/local/Cellar/draco/1.3.5/lib, 390 | ); 391 | ONLY_ACTIVE_ARCH = YES; 392 | OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; 393 | PRODUCT_BUNDLE_IDENTIFIER = "com.ITS.glTF-quicklook"; 394 | PRODUCT_NAME = "$(TARGET_NAME)"; 395 | WRAPPER_EXTENSION = qlgenerator; 396 | }; 397 | name = Debug; 398 | }; 399 | 2744673B21B1F39100E8338E /* Release */ = { 400 | isa = XCBuildConfiguration; 401 | buildSettings = { 402 | CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES = YES; 403 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 404 | CLANG_CXX_LIBRARY = "libc++"; 405 | CLANG_MODULES_DISABLE_PRIVATE_WARNING = YES; 406 | CODE_SIGN_STYLE = Automatic; 407 | COMBINE_HIDPI_IMAGES = YES; 408 | DEVELOPMENT_TEAM = 9K99AL2M82; 409 | EMBED_ASSET_PACKS_IN_PRODUCT_BUNDLE = YES; 410 | FRAMEWORK_SEARCH_PATHS = "$(inherited)"; 411 | GCC_INPUT_FILETYPE = automatic; 412 | HEADER_SEARCH_PATHS = /usr/local/Cellar/draco/1.3.5/include; 413 | INFOPLIST_FILE = "$(SRCROOT)/glTF-quicklook/Info.plist"; 414 | INSTALL_PATH = /Library/QuickLook; 415 | LD_RUNPATH_SEARCH_PATHS = "@loader_path/../Frameworks/"; 416 | LIBRARY_SEARCH_PATHS = ( 417 | "$(inherited)", 418 | /usr/local/Cellar/draco/1.3.5/lib, 419 | ); 420 | OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; 421 | PRODUCT_BUNDLE_IDENTIFIER = "com.ITS.glTF-quicklook"; 422 | PRODUCT_NAME = "$(TARGET_NAME)"; 423 | WRAPPER_EXTENSION = qlgenerator; 424 | }; 425 | name = Release; 426 | }; 427 | /* End XCBuildConfiguration section */ 428 | 429 | /* Begin XCConfigurationList section */ 430 | 2744672721B1F39100E8338E /* Build configuration list for PBXProject "glTF-quicklook" */ = { 431 | isa = XCConfigurationList; 432 | buildConfigurations = ( 433 | 2744673721B1F39100E8338E /* Debug */, 434 | 2744673821B1F39100E8338E /* Release */, 435 | ); 436 | defaultConfigurationIsVisible = 0; 437 | defaultConfigurationName = Release; 438 | }; 439 | 2744673921B1F39100E8338E /* Build configuration list for PBXNativeTarget "glTF-quicklook" */ = { 440 | isa = XCConfigurationList; 441 | buildConfigurations = ( 442 | 2744673A21B1F39100E8338E /* Debug */, 443 | 2744673B21B1F39100E8338E /* Release */, 444 | ); 445 | defaultConfigurationIsVisible = 0; 446 | defaultConfigurationName = Release; 447 | }; 448 | /* End XCConfigurationList section */ 449 | }; 450 | rootObject = 2744672421B1F39100E8338E /* Project object */; 451 | } 452 | -------------------------------------------------------------------------------- /glTF-quicklook.xcodeproj/xcshareddata/xcschemes/glTF-qucklook.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 70 | 71 | 72 | 73 | 75 | 76 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /glTF-quicklook/GeneratePreviewForURL.m: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "SceneGenerator.h" 6 | 7 | OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options); 8 | void CancelPreviewGeneration(void *thisInterface, QLPreviewRequestRef preview); 9 | 10 | /* ----------------------------------------------------------------------------- 11 | Generate a preview for file 12 | 13 | This function's job is to create preview for designated file 14 | ----------------------------------------------------------------------------- */ 15 | 16 | OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options) 17 | { 18 | @autoreleasepool { 19 | 20 | if(QLPreviewRequestIsCancelled(preview)){ 21 | return noErr; 22 | } 23 | 24 | QLPreviewRequestSetDataRepresentation(preview, [SceneGenerator sceneDataByURL:url], kUTType3DContent, options); 25 | } 26 | 27 | return noErr; 28 | } 29 | 30 | void CancelPreviewGeneration(void *thisInterface, QLPreviewRequestRef preview) 31 | { 32 | // Implement only if supported 33 | } 34 | -------------------------------------------------------------------------------- /glTF-quicklook/GenerateThumbnailForURL.m: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #import "ThumbnailGenerator.h" 6 | 7 | @import SceneKit; 8 | 9 | OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize); 10 | void CancelThumbnailGeneration(void *thisInterface, QLThumbnailRequestRef thumbnail); 11 | 12 | /* ----------------------------------------------------------------------------- 13 | Generate a thumbnail for file 14 | 15 | This function's job is to create thumbnail for designated file as fast as possible 16 | ----------------------------------------------------------------------------- */ 17 | 18 | OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize) 19 | { 20 | @autoreleasepool { 21 | NSRect rect = {0, 0, maxSize.width, maxSize.height}; 22 | NSImage *thumbnailImage = [ThumbnailGenerator thumbnailImageByURL:url rect:rect]; 23 | 24 | CGContextRef cgContext = QLThumbnailRequestCreateContext(thumbnail, rect.size, false, options); 25 | if(cgContext){ 26 | NSGraphicsContext *context = [NSGraphicsContext graphicsContextWithCGContext:cgContext flipped:true]; 27 | 28 | if(context){ 29 | [NSGraphicsContext saveGraphicsState]; 30 | [NSGraphicsContext setCurrentContext:context]; 31 | 32 | [thumbnailImage drawInRect:rect fromRect:rect operation:NSCompositingOperationSourceOver fraction:1.0]; 33 | [NSGraphicsContext restoreGraphicsState]; 34 | } 35 | QLThumbnailRequestFlushContext(thumbnail, cgContext); 36 | CFRelease(cgContext); 37 | } else { 38 | return 1; 39 | } 40 | } 41 | 42 | return noErr; 43 | } 44 | 45 | void CancelThumbnailGeneration(void *thisInterface, QLThumbnailRequestRef thumbnail) 46 | { 47 | // Implement only if supported 48 | } 49 | -------------------------------------------------------------------------------- /glTF-quicklook/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeRole 11 | QLGenerator 12 | LSItemContentTypes 13 | 14 | dyn.ah62d4rv4ge80s5dc 15 | dyn.ah62d4rv4ge80s5dyq2 16 | 17 | 18 | 19 | CFBundleExecutable 20 | $(EXECUTABLE_NAME) 21 | CFBundleIdentifier 22 | $(PRODUCT_BUNDLE_IDENTIFIER) 23 | CFBundleInfoDictionaryVersion 24 | 6.0 25 | CFBundleName 26 | $(PRODUCT_NAME) 27 | CFBundleShortVersionString 28 | 1.2 29 | CFBundleVersion 30 | 3 31 | CFPlugInDynamicRegisterFunction 32 | 33 | CFPlugInDynamicRegistration 34 | NO 35 | CFPlugInFactories 36 | 37 | 5767D92A-C12C-4308-AB4C-CFCE8AF12087 38 | QuickLookGeneratorPluginFactory 39 | 40 | CFPlugInTypes 41 | 42 | 5E2D9680-5022-40FA-B806-43349622E5B9 43 | 44 | 5767D92A-C12C-4308-AB4C-CFCE8AF12087 45 | 46 | 47 | CFPlugInUnloadFunction 48 | 49 | NSHumanReadableCopyright 50 | Copyright © 2019 Klochkov Anton. All rights reserved. 51 | QLNeedsToBeRunInMainThread 52 | 53 | QLPreviewHeight 54 | 600 55 | QLPreviewWidth 56 | 800 57 | QLSupportsConcurrentRequests 58 | 59 | QLThumbnailMinimumSize 60 | 17 61 | UTImportedTypeDeclarations 62 | 63 | 64 | UTTypeConformsTo 65 | 66 | public.3d-content 67 | 68 | UTTypeIdentifier 69 | dyn.ah62d4rv4ge80s5dc 70 | UTTypeTagSpecification 71 | 72 | public.filename-extension 73 | 74 | gld 75 | 76 | 77 | 78 | 79 | UTTypeConformsTo 80 | 81 | public.3d-content 82 | 83 | UTTypeIdentifier 84 | dyn.ah62d4rv4ge80s5dyq2 85 | UTTypeTagSpecification 86 | 87 | public.filename-extension 88 | 89 | gltf 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /glTF-quicklook/SCNScene+BoundingBox.h: -------------------------------------------------------------------------------- 1 | // 2 | // SCNScene+BoundingBox.h 3 | // glTF-qucklook 4 | // 5 | // Created by Klochkov Anton on 03/12/2018. 6 | // Copyright © 2018 Klochkov Anton. All rights reserved. 7 | // 8 | 9 | @import SceneKit; 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface SCNScene (BoundingBox) 14 | 15 | - (void) getBoundingBoxOfAllSceneMin:(nullable SCNVector3 *)min max:(nullable SCNVector3 *)max; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /glTF-quicklook/SCNScene+BoundingBox.m: -------------------------------------------------------------------------------- 1 | // 2 | // SCNScene+BoundingBox.m 3 | // glTF-qucklook 4 | // 5 | // Created by Klochkov Anton on 03/12/2018. 6 | // Copyright © 2018 Klochkov Anton. All rights reserved. 7 | // 8 | 9 | #import "SCNScene+BoundingBox.h" 10 | 11 | @implementation SCNScene (BoundingBox) 12 | 13 | - (void) getBoundingBoxOfAllSceneMin:(nullable SCNVector3 *)min max:(nullable SCNVector3 *)max { 14 | *min = SCNVector3Make(CGFLOAT_MAX, CGFLOAT_MAX, CGFLOAT_MAX); 15 | *max = SCNVector3Make(CGFLOAT_MIN, CGFLOAT_MIN, CGFLOAT_MIN); 16 | 17 | [self.rootNode enumerateChildNodesUsingBlock:^(SCNNode * _Nonnull child, BOOL * _Nonnull stop) { 18 | if(child.geometry != nil){ 19 | SCNVector3 childMin; 20 | SCNVector3 childMax; 21 | [child.geometry getBoundingBoxMin:&childMin max:&childMax]; 22 | 23 | min->x = MIN(min->x, childMin.x + child.worldPosition.x); 24 | min->y = MIN(min->y, childMin.y + child.worldPosition.y); 25 | min->z = MIN(min->z, childMin.z + child.worldPosition.z); 26 | 27 | max->x = MAX(max->x, childMax.x + child.worldPosition.x); 28 | max->y = MAX(max->y, childMax.y + child.worldPosition.y); 29 | max->z = MAX(max->z, childMax.z + child.worldPosition.z); 30 | } 31 | }]; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /glTF-quicklook/SceneGenerator.h: -------------------------------------------------------------------------------- 1 | // 2 | // SceneGenerator.h 3 | // glTF-qucklook 4 | // 5 | // Created by Klochkov Anton on 03/12/2018. 6 | // Copyright © 2018 Klochkov Anton. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @import SceneKit; 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @interface SceneGenerator : NSObject 16 | 17 | + (CFDataRef)sceneDataByURL:(CFURLRef)url; 18 | + (SCNScene *)sceneByURL:(CFURLRef)url; 19 | 20 | @end 21 | 22 | NS_ASSUME_NONNULL_END 23 | -------------------------------------------------------------------------------- /glTF-quicklook/SceneGenerator.m: -------------------------------------------------------------------------------- 1 | // 2 | // SceneGenerator.m 3 | // glTF-qucklook 4 | // 5 | // Created by Klochkov Anton on 03/12/2018. 6 | // Copyright © 2018 Klochkov Anton. All rights reserved. 7 | // 8 | 9 | #import "SceneGenerator.h" 10 | 11 | #import "TinyGLTFSCN/TinyGLTFSCN.h" 12 | #import "TinyGLTFSCN/GLTFSCNAnimationTargetPair.h" 13 | 14 | @implementation SceneGenerator 15 | 16 | + (CFDataRef)sceneDataByURL:(CFURLRef)url { 17 | return [SceneGenerator archivedScene:[SceneGenerator sceneByURL:url]]; 18 | } 19 | 20 | + (SCNScene *)sceneByURL:(CFURLRef)url { 21 | return [SceneGenerator sceneByNSURL:(__bridge NSURL*)url]; 22 | } 23 | 24 | + (SCNScene *)sceneByNSURL:(NSURL*)url { 25 | TinyGLTFSCN *loader = [[TinyGLTFSCN alloc] init]; 26 | 27 | if (![loader loadModel:url]) { 28 | return [SceneGenerator errorScene]; 29 | } 30 | 31 | SCNScene *scene = loader.scenes[0]; 32 | [SceneGenerator enableAnimationsToScene:loader.animations]; 33 | 34 | return scene; 35 | } 36 | 37 | + (CFDataRef) archivedScene: (SCNScene *) scene { 38 | return (__bridge CFDataRef)[NSKeyedArchiver archivedDataWithRootObject:scene requiringSecureCoding:NO error:nil]; 39 | } 40 | 41 | + (SCNScene *) errorScene { 42 | #if DEBUG 43 | NSLog(@"Error Scene"); 44 | #endif 45 | NSBundle *bundle = [NSBundle bundleWithIdentifier:@"com.ITS.glTF-quicklook"]; 46 | 47 | NSURL *urlFile = [bundle URLForResource:@"ErrorSymbol" withExtension:@"scn"]; 48 | 49 | return [SCNScene sceneWithURL:urlFile options:nil error:nil]; 50 | } 51 | 52 | + (void) enableAnimationsToScene:(NSDictionary *)animations { 53 | if (animations.count == 0) { 54 | return; 55 | } 56 | 57 | NSString *name = animations.allKeys.firstObject; 58 | #if DEBUG 59 | NSLog(@"Animation name: %@", name); 60 | #endif 61 | 62 | [animations[name] enumerateObjectsUsingBlock:^(GLTFSCNAnimationTargetPair *pair, NSUInteger index, BOOL *stop) { 63 | pair.animation.usesSceneTimeBase = NO; 64 | [pair.target addAnimation:pair.animation forKey:nil]; 65 | }]; 66 | } 67 | 68 | 69 | 70 | @end 71 | -------------------------------------------------------------------------------- /glTF-quicklook/ThumbnailGenerator.h: -------------------------------------------------------------------------------- 1 | // 2 | // ThumbnailGenerator.h 3 | // glTF-qucklook 4 | // 5 | // Created by Klochkov Anton on 03/12/2018. 6 | // Copyright © 2018 Klochkov Anton. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface ThumbnailGenerator : NSObject 14 | 15 | + (NSImage *) thumbnailImageByURL:(CFURLRef)url rect:(NSRect)rect; 16 | 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /glTF-quicklook/ThumbnailGenerator.m: -------------------------------------------------------------------------------- 1 | // 2 | // ThumbnailGenerator.m 3 | // glTF-qucklook 4 | // 5 | // Created by Klochkov Anton on 03/12/2018. 6 | // Copyright © 2018 Klochkov Anton. All rights reserved. 7 | // 8 | 9 | #import "ThumbnailGenerator.h" 10 | #import "SceneGenerator.h" 11 | #import "SCNScene+BoundingBox.h" 12 | 13 | #define MAX_SIZE 700 14 | 15 | @implementation ThumbnailGenerator 16 | 17 | + (NSImage *) thumbnailImageByURL:(CFURLRef)url rect:(NSRect)rect { 18 | SCNScene *scene = [SceneGenerator sceneByURL:url]; 19 | 20 | SCNVector3 sceneMin; 21 | SCNVector3 sceneMax; 22 | 23 | [scene getBoundingBoxOfAllSceneMin:&sceneMin max:&sceneMax]; 24 | 25 | SCNNode *cameraNode = [SCNNode node]; 26 | cameraNode.camera = [SCNCamera camera]; 27 | 28 | cameraNode.camera.automaticallyAdjustsZRange = true; 29 | 30 | CGFloat dz = MIN(MAX(sceneMax.x - sceneMin.x, sceneMax.y - sceneMin.y), MAX_SIZE); 31 | if(cameraNode.camera.zNear > dz){ 32 | cameraNode.camera.zNear = dz; 33 | } 34 | 35 | CGFloat wz = dz +sceneMax.z - sceneMin.z; 36 | if(cameraNode.camera.zFar < wz){ 37 | cameraNode.camera.zFar = wz; 38 | } 39 | 40 | cameraNode.position = SCNVector3Make((sceneMax.x + sceneMin.x) * 0.5, (sceneMax.y + sceneMin.y) * 0.5, sceneMax.z + dz); 41 | [scene.rootNode addChildNode:cameraNode]; 42 | 43 | SCNView *view = [[SCNView alloc] initWithFrame:rect options:nil]; 44 | view.autoenablesDefaultLighting = true; 45 | view.scene = scene; 46 | view.pointOfView = cameraNode; 47 | 48 | return [view snapshot]; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /glTF-quicklook/TinyGLTFSCN/GLTFSCNAnimationTargetPair.h: -------------------------------------------------------------------------------- 1 | // 2 | // GLTFSCNAnimationTargetPair.h 3 | // glTF-quicklook 4 | // 5 | // Created by Klochkov Anton on 14/09/2019. 6 | // Copyright © 2019 Klochkov Anton. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface GLTFSCNAnimationTargetPair : NSObject 15 | @property (nonatomic, strong) CAAnimation *animation; 16 | @property (nonatomic, strong) SCNNode *target; 17 | @end 18 | 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /glTF-quicklook/TinyGLTFSCN/GLTFSCNAnimationTargetPair.m: -------------------------------------------------------------------------------- 1 | // 2 | // GLTFSCNAnimationTargetPair.m 3 | // glTF-quicklook 4 | // 5 | // Created by Klochkov Anton on 14/09/2019. 6 | // Copyright © 2019 Klochkov Anton. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | @implementation GLTFSCNAnimationTargetPair 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /glTF-quicklook/TinyGLTFSCN/TinyGLTFSCN.h: -------------------------------------------------------------------------------- 1 | // 2 | // TinyGLTFSCN.h 3 | // glTF-quicklook 4 | // 5 | // Created by Klochkov Anton on 12/06/2019. 6 | // Copyright © 2019 Klochkov Anton. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface TinyGLTFSCN : NSObject 15 | 16 | - (BOOL) loadModel: (NSURL *) modelURL; 17 | 18 | @property (nonatomic, readonly) NSArray *scenes; 19 | @property (nonatomic, readonly) NSDictionary *animations; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /glTF-quicklook/TinyGLTFSCN/TinyGLTFSCN.mm: -------------------------------------------------------------------------------- 1 | // 2 | // TinyGLTFSCN.m 3 | // glTF-quicklook 4 | // 5 | // Created by Klochkov Anton on 12/06/2019. 6 | // Copyright © 2019 Klochkov Anton. All rights reserved. 7 | // 8 | 9 | #define TINYGLTF_IMPLEMENTATION 10 | #define STB_IMAGE_IMPLEMENTATION 11 | #define TINYGLTF_NO_STB_IMAGE_WRITE 12 | #define TINYGLTF_ENABLE_DRACO 13 | #define TINYGLTF_USE_CPP14 14 | #define TINYGLTF_NO_EXTERNAL_IMAGE 15 | 16 | #import "TinyGLTFSCN.h" 17 | #import "GLTFSCNAnimationTargetPair.h" 18 | #include "tiny_gltf.h" 19 | 20 | typedef struct __attribute__((packed)) { 21 | float x, y, z; 22 | } GLTFVector3; 23 | 24 | typedef struct __attribute__((packed)) { 25 | float x, y, z, w; 26 | } GLTFVector4; 27 | 28 | typedef struct __attribute__((packed)) { 29 | GLTFVector4 columns[4]; 30 | } GLTFMatrix4; 31 | 32 | static SCNMatrix4 GLTFSCNMatrix4FromFloat4x4(GLTFMatrix4 m) { 33 | SCNMatrix4 mOut = (SCNMatrix4) { 34 | m.columns[0].x, m.columns[0].y, m.columns[0].z, m.columns[0].w, 35 | m.columns[1].x, m.columns[1].y, m.columns[1].z, m.columns[1].w, 36 | m.columns[2].x, m.columns[2].y, m.columns[2].z, m.columns[2].w, 37 | m.columns[3].x, m.columns[3].y, m.columns[3].z, m.columns[3].w 38 | }; 39 | return mOut; 40 | } 41 | 42 | static simd_float4x4 simdFloat4x4FromVector16(const std::vector &matrix) { 43 | if (matrix.size() < 16) { 44 | return { 45 | simd_make_float4(1, 0, 0, 0), 46 | simd_make_float4(0, 1, 0, 0), 47 | simd_make_float4(0, 0, 1, 0), 48 | simd_make_float4(0, 0, 0, 1) 49 | }; 50 | } 51 | 52 | return { 53 | simd_make_float4(matrix[0], matrix[1], matrix[2], matrix[3]), 54 | simd_make_float4(matrix[4], matrix[5], matrix[6], matrix[7]), 55 | simd_make_float4(matrix[8], matrix[9], matrix[10], matrix[11]), 56 | simd_make_float4(matrix[12], matrix[13], matrix[14], matrix[15]) 57 | }; 58 | } 59 | 60 | const std::string GLTFAttributeSemanticPosition = "POSITION"; 61 | const std::string GLTFAttributeSemanticTangent = "TANGENT"; 62 | const std::string GLTFAttributeSemanticNormal = "NORMAL"; 63 | const std::string GLTFAttributeSemanticTexCoord0 = "TEXCOORD_0"; 64 | const std::string GLTFAttributeSemanticTexCoord1 = "TEXCOORD_1"; 65 | const std::string GLTFAttributeSemanticColor0 = "COLOR_0"; 66 | const std::string GLTFAttributeSemanticJoints0 = "JOINTS_0"; 67 | const std::string GLTFAttributeSemanticJoints1 = "JOINTS_1"; 68 | const std::string GLTFAttributeSemanticWeights0 = "WEIGHTS_0"; 69 | const std::string GLTFAttributeSemanticWeights1 = "WEIGHTS_1"; 70 | const std::string GLTFAttributeSemanticRoughness = "ROUGHNESS"; 71 | const std::string GLTFAttributeSemanticMetallic = "METALLIC"; 72 | 73 | typedef NS_ENUM(NSInteger, TinyImageChannel) { 74 | TinyImageChannelRed, 75 | TinyImageChannelGreen, 76 | TinyImageChannelBlue, 77 | TinyImageChannelAlpha, 78 | TinyImageChannelAll = 255 79 | }; 80 | 81 | 82 | typedef NS_ENUM(NSInteger, GLTF_TYPE) { 83 | GLTF_TYPE_GLTF, 84 | GLTF_TYPE_GLB 85 | }; 86 | 87 | 88 | static SCNGeometryPrimitiveType TinyGLTFSCNGeometryPrimitiveTypeForPrimitiveType(NSInteger primitiveType) { 89 | switch (primitiveType) { 90 | case TINYGLTF_MODE_POINTS: 91 | return SCNGeometryPrimitiveTypePoint; 92 | case TINYGLTF_MODE_LINE: 93 | return SCNGeometryPrimitiveTypeLine; 94 | case TINYGLTF_MODE_TRIANGLES: 95 | return SCNGeometryPrimitiveTypeTriangles; 96 | case TINYGLTF_MODE_TRIANGLE_STRIP: 97 | return SCNGeometryPrimitiveTypeTriangleStrip; 98 | default: 99 | // Unsupported: line loop, line strip, triangle fan 100 | return SCNGeometryPrimitiveTypePolygon; 101 | } 102 | } 103 | 104 | static NSInteger TinyGLTFPrimitiveCountForIndexCount(NSInteger indexCount, SCNGeometryPrimitiveType primitiveType) { 105 | switch (primitiveType) { 106 | case SCNGeometryPrimitiveTypePoint: 107 | return indexCount; 108 | case SCNGeometryPrimitiveTypeLine: 109 | return indexCount / 2; 110 | case SCNGeometryPrimitiveTypeTriangles: 111 | return indexCount / 3; 112 | case SCNGeometryPrimitiveTypeTriangleStrip: 113 | return indexCount - 2; 114 | case SCNGeometryPrimitiveTypePolygon: 115 | return 1; 116 | default: 117 | return 0; 118 | } 119 | } 120 | 121 | static SCNWrapMode GLTFSCNWrapModeForAddressMode(int mode) { 122 | switch (mode) { 123 | case TINYGLTF_TEXTURE_WRAP_CLAMP_TO_EDGE: 124 | return SCNWrapModeClamp; 125 | case TINYGLTF_TEXTURE_WRAP_MIRRORED_REPEAT: 126 | return SCNWrapModeMirror; 127 | case TINYGLTF_TEXTURE_WRAP_REPEAT: 128 | default: 129 | return SCNWrapModeRepeat; 130 | } 131 | } 132 | 133 | size_t TinyGLTFSizeOfComponentTypeWithDimension(NSInteger baseType, NSInteger dimension) 134 | { 135 | switch (baseType) { 136 | case TINYGLTF_COMPONENT_TYPE_BYTE: 137 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: 138 | switch (dimension) { 139 | case TINYGLTF_TYPE_VEC2: 140 | return 2; 141 | case TINYGLTF_TYPE_VEC3: 142 | return 3; 143 | case TINYGLTF_TYPE_VEC4: 144 | return 4; 145 | default: 146 | break; 147 | } 148 | case TINYGLTF_COMPONENT_TYPE_SHORT: 149 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: 150 | switch (dimension) { 151 | case TINYGLTF_TYPE_VEC2: 152 | return 4; 153 | case TINYGLTF_TYPE_VEC3: 154 | return 6; 155 | case TINYGLTF_TYPE_VEC4: 156 | return 8; 157 | default: 158 | break; 159 | } 160 | case TINYGLTF_COMPONENT_TYPE_INT: 161 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: 162 | case TINYGLTF_COMPONENT_TYPE_FLOAT: 163 | switch (dimension) { 164 | case TINYGLTF_TYPE_SCALAR: 165 | return 4; 166 | case TINYGLTF_TYPE_VEC2: 167 | return 8; 168 | case TINYGLTF_TYPE_VEC3: 169 | return 12; 170 | case TINYGLTF_TYPE_VEC4: 171 | case TINYGLTF_TYPE_MAT2: 172 | return 16; 173 | case TINYGLTF_TYPE_MAT3: 174 | return 36; 175 | case TINYGLTF_TYPE_MAT4: 176 | return 64; 177 | default: 178 | break; 179 | } 180 | default: 181 | break; 182 | } 183 | return 0; 184 | } 185 | 186 | NSInteger TinyGLTFComponentCountForDimension(NSInteger dimension) { 187 | switch (dimension) { 188 | case TINYGLTF_TYPE_SCALAR: 189 | return 1; 190 | case TINYGLTF_TYPE_VEC2: 191 | return 2; 192 | case TINYGLTF_TYPE_VEC3: 193 | return 3; 194 | case TINYGLTF_TYPE_VEC4: 195 | return 4; 196 | case TINYGLTF_TYPE_MAT2: 197 | return 4; 198 | case TINYGLTF_TYPE_MAT3: 199 | return 9; 200 | case TINYGLTF_TYPE_MAT4: 201 | return 16; 202 | default: 203 | return 0; 204 | } 205 | } 206 | 207 | 208 | @interface TinyGLTFSCN () 209 | @property (nonatomic, strong) NSMutableDictionary *scnNodesForTinyNodes; 210 | @property (nonatomic, strong) NSMutableDictionary *> *inverseBindMatricesForSkins; 211 | @property (nonatomic, strong) NSMutableDictionary *cgImagesForImagesAndChannels; 212 | @property (nonatomic, assign) NSInteger namelessAnimationIndex; 213 | @property (nonatomic, strong) NSURL *gltfPath; 214 | @end 215 | 216 | 217 | @implementation TinyGLTFSCN 218 | 219 | - (GLTF_TYPE) typeOfFileByPath: (NSURL *) url { 220 | return [[url pathExtension].lowercaseString isEqualToString:@"glb"] ? GLTF_TYPE_GLB : GLTF_TYPE_GLTF; 221 | } 222 | 223 | - (BOOL) loadModel: (NSURL *) modelURL { 224 | self.scnNodesForTinyNodes = [NSMutableDictionary dictionary]; 225 | self.inverseBindMatricesForSkins = [NSMutableDictionary dictionary]; 226 | self.cgImagesForImagesAndChannels = [NSMutableDictionary dictionary]; 227 | self.namelessAnimationIndex = 0; 228 | self.gltfPath = [modelURL URLByDeletingLastPathComponent]; 229 | 230 | GLTF_TYPE typeOfFile = [self typeOfFileByPath:modelURL]; 231 | 232 | tinygltf::Model model; 233 | tinygltf::TinyGLTF loader; 234 | std::string err; 235 | std::string warn; 236 | bool ret; 237 | 238 | if (typeOfFile == GLTF_TYPE_GLTF) { 239 | ret = loader.LoadASCIIFromFile(&model, &err, &warn, std::string(modelURL.path.UTF8String)); 240 | } else { 241 | ret = loader.LoadBinaryFromFile(&model, &err, &warn, std::string(modelURL.path.UTF8String)); 242 | } 243 | 244 | if (!warn.empty()) { 245 | #if DEBUG 246 | NSLog(@"Warn: %s\n", warn.c_str()); 247 | #endif 248 | } 249 | 250 | if (!err.empty()) { 251 | #if DEBUG 252 | NSLog(@"Err: %s\n", err.c_str()); 253 | #endif 254 | return NO; 255 | } 256 | 257 | if (!ret) { 258 | #if DEBUG 259 | NSLog(@"Failed to parse glTF\n"); 260 | #endif 261 | return NO; 262 | } 263 | 264 | [self loadScenes:model]; 265 | [self loadAnimations:model]; 266 | 267 | return YES; 268 | } 269 | 270 | - (void) loadScenes:(const tinygltf::Model &)model { 271 | NSMutableArray *scenes = [NSMutableArray array]; 272 | 273 | for (const auto& scene: model.scenes) { 274 | SCNScene *scnScene = [SCNScene scene]; 275 | 276 | for (const auto& node: scene.nodes) { 277 | [self recursiveAddNodeWithId:node toSCNNode:scnScene.rootNode fromModel:model]; 278 | } 279 | 280 | [scenes addObject:scnScene]; 281 | } 282 | 283 | _scenes = scenes; 284 | } 285 | 286 | - (void) loadAnimations:(const tinygltf::Model &)model { 287 | NSMutableDictionary *animations = [NSMutableDictionary dictionary]; 288 | for (const auto &animation: model.animations) { 289 | NSString *name = animation.name.size() != 0 ? [[NSString alloc] initWithFormat:@"%s", animation.name.c_str()]: [self _nextAnonymousAnimationName]; 290 | NSMutableArray *pairs = [NSMutableArray array]; 291 | for (size_t i = 0; i < animation.channels.size(); ++i) { 292 | const auto& channel = animation.channels[i]; 293 | const auto& sampler = animation.samplers[channel.sampler]; 294 | 295 | 296 | const auto& inputAccessor = model.accessors[sampler.input]; 297 | const auto& outputAccessor = model.accessors[sampler.output]; 298 | 299 | 300 | CAKeyframeAnimation *keyframeAnimation = nil; 301 | NSString *targetPath = [[NSString alloc] initWithFormat:@"%s", channel.target_path.c_str()]; 302 | 303 | if ([targetPath isEqualToString:@"rotation"]) { 304 | keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"orientation"]; 305 | keyframeAnimation.values = [self arrayFromQuaternionAccessor:outputAccessor fromModel:model]; 306 | } else if ([targetPath isEqualToString:@"translation"]) { 307 | keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"translation"]; 308 | keyframeAnimation.values = [self vectorArrayFromAccessor:outputAccessor fromModel:model]; 309 | } else if ([targetPath isEqualToString:@"scale"]) { 310 | keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"scale"]; 311 | keyframeAnimation.values = [self vectorArrayFromScalarAccessor:outputAccessor fromModel:model]; 312 | } else { 313 | continue; 314 | } 315 | 316 | NSTimeInterval startTime = [self startTime:inputAccessor fromModel:model]; 317 | NSTimeInterval endTime = [self endTime:inputAccessor fromModel:model keyFrameCount:(int)inputAccessor.count]; 318 | 319 | 320 | keyframeAnimation.keyTimes = [self normalizedArrayFromFloatAccessor:inputAccessor fromModel:model minimumValue:startTime maximumValue:endTime]; 321 | keyframeAnimation.beginTime = startTime; 322 | keyframeAnimation.duration = endTime - startTime; 323 | keyframeAnimation.repeatDuration = FLT_MAX; 324 | NSNumber *targetNodeID = [NSNumber numberWithInteger:channel.target_node]; 325 | 326 | SCNNode *scnNode = self.scnNodesForTinyNodes[targetNodeID]; 327 | if (scnNode != nil) { 328 | GLTFSCNAnimationTargetPair *pair = [GLTFSCNAnimationTargetPair new]; 329 | pair.animation = keyframeAnimation; 330 | pair.target = scnNode; 331 | [pairs addObject:pair]; 332 | } else { 333 | #if DEBUG 334 | NSLog(@"WARNING: Could not find node for channel target node identifier %@", targetNodeID); 335 | #endif 336 | } 337 | } 338 | 339 | animations[name] = [pairs copy]; 340 | } 341 | 342 | _animations = animations; 343 | 344 | } 345 | 346 | - (simd_float4x4)computeTransformForTinyNode: (const tinygltf::Node &)node { 347 | simd_quatf _rotationQuaternion = simd_quaternion(0.f, 0.f, 0.f, 1.f); 348 | simd_float3 _scale = vector3(1.0f, 1.0f, 1.0f); 349 | simd_float3 _translation = vector3(0.0f, 0.0f, 0.0f); 350 | 351 | const auto& rotation = node.rotation; 352 | const auto& scale = node.scale; 353 | const auto& translation = node.translation; 354 | const auto& matrix = simdFloat4x4FromVector16(node.matrix); 355 | 356 | if (rotation.size() == 4) { 357 | _rotationQuaternion = simd_quaternion(float(rotation[0]), float(rotation[1]), float(rotation[2]), float(rotation[3])); 358 | } 359 | 360 | if (scale.size() == 3) { 361 | _scale = vector3(float(scale[0]), float(scale[1]), float(scale[2])); 362 | } 363 | 364 | if (translation.size() == 3) { 365 | _translation = vector3(float(translation[0]), float(translation[1]), float(translation[2])); 366 | } 367 | 368 | simd_float4x4 translationMatrix = matrix_identity_float4x4; 369 | translationMatrix.columns[3][0] = _translation[0]; 370 | translationMatrix.columns[3][1] = _translation[1]; 371 | translationMatrix.columns[3][2] = _translation[2]; 372 | 373 | simd_float4x4 rotationMatrix = simd_matrix4x4(_rotationQuaternion); 374 | 375 | simd_float4x4 scaleMatrix = matrix_identity_float4x4; 376 | scaleMatrix.columns[0][0] = _scale[0]; 377 | scaleMatrix.columns[1][1] = _scale[1]; 378 | scaleMatrix.columns[2][2] = _scale[2]; 379 | 380 | return matrix_multiply(matrix, matrix_multiply(matrix_multiply(translationMatrix, rotationMatrix), scaleMatrix)); 381 | } 382 | 383 | - (void) recursiveAddNodeWithId: (NSInteger)nodeID toSCNNode:(SCNNode *)node fromModel: (const tinygltf::Model &)model { 384 | const auto &tinyNode = model.nodes[nodeID]; 385 | SCNNode *scnNode = [self makeSCNNodeForTinyNodeByID:nodeID]; 386 | 387 | if (@available(iOS 11.0, *)) { 388 | scnNode.simdTransform = [self computeTransformForTinyNode:tinyNode]; 389 | } else { 390 | scnNode.transform = SCNMatrix4FromMat4([self computeTransformForTinyNode:tinyNode]); 391 | } 392 | scnNode.name = node.name; 393 | 394 | [node addChildNode:scnNode]; 395 | 396 | NSArray *meshNodes = [self nodesForTinyMesh:tinyNode.mesh withSkin:tinyNode.skin fromModel:model]; 397 | for (SCNNode *meshNode in meshNodes) { 398 | [scnNode addChildNode:meshNode]; 399 | } 400 | 401 | for (const auto &child: tinyNode.children){ 402 | [self recursiveAddNodeWithId:child toSCNNode:scnNode fromModel:model]; 403 | } 404 | } 405 | 406 | - (NSArray *)nodesForTinyMesh: (NSInteger)meshID withSkin:(NSInteger)skinID fromModel:(const tinygltf::Model &)model { 407 | if (meshID == -1){ 408 | return nil; 409 | } 410 | 411 | const auto& mesh = model.meshes[meshID]; 412 | 413 | NSMutableArray *nodes = [NSMutableArray array]; 414 | 415 | NSArray *bones = [self bonesForTinySkin:skinID fromModel:model]; 416 | NSArray *inverseBindMatrices = [self inverseBindMatricesForTinySkin:skinID fromModel:model]; 417 | 418 | for (size_t i = 0; i < mesh.primitives.size(); ++i) { 419 | const auto& submesh = mesh.primitives[i]; 420 | 421 | NSMutableArray *sources = [NSMutableArray array]; 422 | NSMutableArray *elements = [NSMutableArray array]; 423 | 424 | auto addAttribute = [&](const std::string& attribute, SCNGeometrySourceSemantic semantic) { 425 | const auto &attributeIter = submesh.attributes.find(attribute); 426 | 427 | if (attributeIter == submesh.attributes.end()){ 428 | return; 429 | } 430 | 431 | SCNGeometrySource *attributeSource = [self geometrySourceWithSemantic:semantic accessorID:attributeIter->second fromModel:model]; 432 | if (attributeSource != nil) { 433 | [sources addObject:attributeSource]; 434 | } 435 | 436 | return; 437 | }; 438 | 439 | addAttribute(GLTFAttributeSemanticPosition, SCNGeometrySourceSemanticVertex); 440 | addAttribute(GLTFAttributeSemanticNormal, SCNGeometrySourceSemanticNormal); 441 | addAttribute(GLTFAttributeSemanticTangent, SCNGeometrySourceSemanticTangent); 442 | addAttribute(GLTFAttributeSemanticTexCoord0, SCNGeometrySourceSemanticTexcoord); 443 | addAttribute(GLTFAttributeSemanticColor0, SCNGeometrySourceSemanticColor); 444 | 445 | const auto& indexAccessor = model.accessors[submesh.indices]; 446 | const auto& indexBufferView = model.bufferViews[indexAccessor.bufferView]; 447 | const auto& indexBuffer = model.buffers[indexBufferView.buffer]; 448 | 449 | SCNGeometryPrimitiveType primitiveType = TinyGLTFSCNGeometryPrimitiveTypeForPrimitiveType(submesh.mode); 450 | NSInteger bytesPerIndex = (indexAccessor.componentType == TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT) ? sizeof(uint16_t) : sizeof(uint32_t); 451 | NSData *indexData = [NSData dataWithBytes:indexBuffer.data.data() + indexBufferView.byteOffset + indexAccessor.byteOffset length:indexAccessor.count * bytesPerIndex]; 452 | NSInteger indexCount = indexAccessor.count; 453 | NSInteger primitiveCount = TinyGLTFPrimitiveCountForIndexCount(indexCount, primitiveType); 454 | SCNGeometryElement *geometryElement = [SCNGeometryElement geometryElementWithData:indexData 455 | primitiveType:primitiveType 456 | primitiveCount:primitiveCount 457 | bytesPerIndex:bytesPerIndex]; 458 | [elements addObject:geometryElement]; 459 | 460 | SCNGeometry *geometry = [SCNGeometry geometryWithSources:sources elements:elements]; 461 | 462 | 463 | SCNMaterial *material = [self materialForTinyMaterial:submesh.material fromModel:model]; 464 | if (material != nil) { 465 | geometry.materials = @[material]; 466 | } 467 | 468 | SCNNode *node = [SCNNode node]; 469 | node.geometry = geometry; 470 | 471 | const auto& boneWeightsIter = submesh.attributes.find(GLTFAttributeSemanticWeights0); 472 | const auto& boneIndicesIter = submesh.attributes.find(GLTFAttributeSemanticJoints0); 473 | 474 | if (boneIndicesIter != submesh.attributes.end() && boneWeightsIter != submesh.attributes.end()) { 475 | SCNGeometrySource *boneWeights = [self geometrySourceWithSemantic:SCNGeometrySourceSemanticVertex accessorID:boneWeightsIter->second fromModel:model]; 476 | SCNGeometrySource *boneIndices = [self geometrySourceWithSemantic:SCNGeometrySourceSemanticVertex accessorID:boneIndicesIter->second fromModel:model]; 477 | 478 | if (boneWeights != nil && boneIndices != nil) { 479 | SCNSkinner *skinner = [SCNSkinner skinnerWithBaseGeometry:geometry 480 | bones:bones 481 | boneInverseBindTransforms:inverseBindMatrices 482 | boneWeights:boneWeights 483 | boneIndices:boneIndices]; 484 | node.skinner = skinner; 485 | } 486 | } 487 | 488 | [nodes addObject:node]; 489 | } 490 | 491 | return nodes; 492 | } 493 | 494 | - (SCNMaterial *)materialForTinyMaterial:(NSInteger)materialID fromModel:(const tinygltf::Model &)model { 495 | if (materialID == -1) { 496 | return nil; 497 | } 498 | 499 | const auto& material = model.materials[materialID]; 500 | 501 | SCNMaterial *scnMaterial = [SCNMaterial material]; 502 | 503 | scnMaterial.name = [NSString stringWithUTF8String:material.name.c_str()]; 504 | 505 | scnMaterial.lightingModelName = SCNLightingModelPhysicallyBased; 506 | scnMaterial.doubleSided = material.doubleSided; 507 | 508 | if (material.pbrMetallicRoughness.baseColorTexture.index != -1) { 509 | const auto& baseColorTexture = model.textures[material.pbrMetallicRoughness.baseColorTexture.index]; 510 | 511 | if (baseColorTexture.source != -1) { 512 | scnMaterial.diffuse.contents = (__bridge id)[self cgImageForTinyImage:baseColorTexture.source channelMask:TinyImageChannel::TinyImageChannelAll fromModel:model]; 513 | } 514 | 515 | if (baseColorTexture.sampler != -1) { 516 | const auto &baseColorTextureSampler = model.samplers[baseColorTexture.sampler]; 517 | scnMaterial.diffuse.wrapS = GLTFSCNWrapModeForAddressMode(baseColorTextureSampler.wrapS); 518 | scnMaterial.diffuse.wrapT = GLTFSCNWrapModeForAddressMode(baseColorTextureSampler.wrapT); 519 | } 520 | } 521 | 522 | if (scnMaterial.diffuse.contents == nil || material.pbrMetallicRoughness.baseColorTexture.index == -1) { 523 | const auto& colorFactor = material.pbrMetallicRoughness.baseColorFactor; 524 | 525 | if (colorFactor.size() >= 4) { 526 | scnMaterial.diffuse.contents = (__bridge_transfer id)[self newCGColorForFloat4:simd_make_float4(colorFactor[0], colorFactor[1], colorFactor[2], colorFactor[3])]; 527 | } 528 | } 529 | 530 | scnMaterial.diffuse.mappingChannel = material.pbrMetallicRoughness.baseColorTexture.texCoord; 531 | 532 | if (material.pbrMetallicRoughness.metallicRoughnessTexture.index != -1) { 533 | const auto& metallicRoughnessTexture = model.textures[material.pbrMetallicRoughness.metallicRoughnessTexture.index]; 534 | 535 | if (metallicRoughnessTexture.source != -1) { 536 | scnMaterial.metalness.contents = (__bridge id)[self cgImageForTinyImage:metallicRoughnessTexture.source channelMask:TinyImageChannel::TinyImageChannelBlue fromModel:model]; 537 | 538 | scnMaterial.roughness.contents = (__bridge id)[self cgImageForTinyImage:metallicRoughnessTexture.source channelMask:TinyImageChannel::TinyImageChannelGreen fromModel:model]; 539 | } 540 | 541 | if (metallicRoughnessTexture.sampler != -1) { 542 | const auto &metallicRoughnessTextureSampler = model.samplers[metallicRoughnessTexture.sampler]; 543 | scnMaterial.metalness.wrapS = GLTFSCNWrapModeForAddressMode(metallicRoughnessTextureSampler.wrapS); 544 | scnMaterial.metalness.wrapT = GLTFSCNWrapModeForAddressMode(metallicRoughnessTextureSampler.wrapT); 545 | scnMaterial.roughness.wrapS = GLTFSCNWrapModeForAddressMode(metallicRoughnessTextureSampler.wrapS); 546 | scnMaterial.roughness.wrapT = GLTFSCNWrapModeForAddressMode(metallicRoughnessTextureSampler.wrapT); 547 | } 548 | } 549 | 550 | if (scnMaterial.metalness.contents == nil || material.pbrMetallicRoughness.metallicRoughnessTexture.index == -1) { 551 | scnMaterial.metalness.contents = @(material.pbrMetallicRoughness.metallicFactor); 552 | } 553 | 554 | if (scnMaterial.roughness.contents == nil || material.pbrMetallicRoughness.metallicRoughnessTexture.index == -1) { 555 | scnMaterial.metalness.contents = @(material.pbrMetallicRoughness.roughnessFactor); 556 | } 557 | 558 | scnMaterial.metalness.mappingChannel = material.pbrMetallicRoughness.metallicRoughnessTexture.texCoord; 559 | scnMaterial.roughness.mappingChannel = material.pbrMetallicRoughness.metallicRoughnessTexture.texCoord; 560 | 561 | 562 | 563 | if (material.normalTexture.index != -1) { 564 | const auto& normalTexture = model.textures[material.normalTexture.index]; 565 | 566 | if (normalTexture.source != -1) { 567 | scnMaterial.normal.contents = (__bridge id)[self cgImageForTinyImage:normalTexture.source channelMask:TinyImageChannel::TinyImageChannelAll fromModel:model]; 568 | } 569 | 570 | if (normalTexture.sampler != -1) { 571 | const auto &normalTextureSampler = model.samplers[normalTexture.sampler]; 572 | scnMaterial.normal.wrapS = GLTFSCNWrapModeForAddressMode(normalTextureSampler.wrapS); 573 | scnMaterial.normal.wrapT = GLTFSCNWrapModeForAddressMode(normalTextureSampler.wrapT); 574 | } 575 | } 576 | 577 | scnMaterial.normal.mappingChannel = material.normalTexture.texCoord; 578 | 579 | if (material.occlusionTexture.index != -1) { 580 | const auto& occlusionTexture = model.textures[material.occlusionTexture.index]; 581 | 582 | if (occlusionTexture.source != -1) { 583 | scnMaterial.ambientOcclusion.contents = (__bridge id)[self cgImageForTinyImage:occlusionTexture.source channelMask:TinyImageChannel::TinyImageChannelRed fromModel:model]; 584 | } 585 | 586 | if (occlusionTexture.sampler != -1) { 587 | const auto &occlusionTextureSampler = model.samplers[occlusionTexture.sampler]; 588 | scnMaterial.ambientOcclusion.wrapS = GLTFSCNWrapModeForAddressMode(occlusionTextureSampler.wrapS); 589 | scnMaterial.ambientOcclusion.wrapT = GLTFSCNWrapModeForAddressMode(occlusionTextureSampler.wrapT); 590 | } 591 | } 592 | 593 | scnMaterial.ambientOcclusion.mappingChannel = material.occlusionTexture.texCoord; 594 | 595 | if (material.emissiveTexture.index != -1) { 596 | const auto& emissiveTexture = model.textures[material.emissiveTexture.index]; 597 | 598 | if (emissiveTexture.source != -1) { 599 | scnMaterial.emission.contents = (__bridge id)[self cgImageForTinyImage:emissiveTexture.source channelMask:TinyImageChannel::TinyImageChannelAll fromModel:model]; 600 | } 601 | 602 | if (emissiveTexture.sampler != -1) { 603 | const auto &emissiveTextureSampler = model.samplers[emissiveTexture.sampler]; 604 | scnMaterial.emission.wrapS = GLTFSCNWrapModeForAddressMode(emissiveTextureSampler.wrapS); 605 | scnMaterial.emission.wrapT = GLTFSCNWrapModeForAddressMode(emissiveTextureSampler.wrapT); 606 | } 607 | } 608 | 609 | if (scnMaterial.emission.contents == nil || material.emissiveTexture.index == -1) { 610 | const auto& emissiveFactor = material.emissiveFactor; 611 | 612 | if (emissiveFactor.size() >= 3) { 613 | scnMaterial.emission.contents = (__bridge_transfer id)[self newCGColorForFloat3:simd_make_float3(emissiveFactor[0], emissiveFactor[1], emissiveFactor[2])]; 614 | } 615 | } 616 | 617 | scnMaterial.emission.mappingChannel = material.emissiveTexture.texCoord; 618 | 619 | return scnMaterial; 620 | } 621 | 622 | - (CGImageRef)newCGImageByExtractingChannel:(NSInteger)channelIndex fromCGImage:(const tinygltf::Image *)sourceImage { 623 | if (sourceImage == NULL) { 624 | return NULL; 625 | } 626 | 627 | NSData *imageData = [[NSData alloc] initWithBytes:sourceImage->image.data() length:sourceImage->image.size()]; 628 | 629 | CGDataProviderRef imgDataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef) imageData); 630 | CGImageRef imageRef = CGImageCreateWithJPEGDataProvider(imgDataProvider, NULL, true, kCGRenderingIntentDefault); 631 | 632 | size_t width = sourceImage->width; 633 | size_t height = sourceImage->height; 634 | size_t bpc = 8; 635 | size_t Bpr = width * 4; 636 | 637 | uint8_t *pixels = (uint8_t *)malloc(Bpr * height); 638 | 639 | CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); 640 | CGContextRef context = CGBitmapContextCreate(pixels, width, height, bpc, Bpr, colorSpace, kCGImageAlphaPremultipliedLast); 641 | CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef); 642 | 643 | for (int i = 0; i < width * height; ++i) { 644 | uint8_t components[4] = { pixels[i * 4 + 0], pixels[i * 4 + 1], pixels[i * 4 + 2], pixels[i * 4 + 3] }; // RGBA 645 | pixels[i] = components[channelIndex]; 646 | } 647 | 648 | CGColorSpaceRef monoColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericGrayGamma2_2); 649 | CGContextRef monoContext = CGBitmapContextCreate(pixels, width, height, bpc, width, monoColorSpace, kCGImageAlphaNone); 650 | 651 | CGImageRef channelImage = CGBitmapContextCreateImage(monoContext); 652 | 653 | CGColorSpaceRelease(monoColorSpace); 654 | CGContextRelease(monoContext); 655 | CGColorSpaceRelease(colorSpace); 656 | CGContextRelease(context); 657 | free(pixels); 658 | // CGImageRelease(imageRef); 659 | 660 | return channelImage; 661 | } 662 | 663 | - (CGImage *)cgImageForTinyImage:(NSInteger)imageID channelMask:(TinyImageChannel)channelMask fromModel:(const tinygltf::Model &)model { 664 | if (imageID == -1) { 665 | return nil; 666 | } 667 | 668 | const auto& image = model.images[imageID]; 669 | 670 | NSString *imageName = nil; 671 | if (image.name.size() == 0) { 672 | imageName = [NSString stringWithFormat:@"%ld", (long)imageID]; 673 | } else { 674 | imageName = [NSString stringWithFormat:@"%s", image.name.c_str()]; 675 | } 676 | 677 | NSString *maskedIdentifier = [NSString stringWithFormat:@"%@/%d", imageName, (int)channelMask]; 678 | 679 | // Check the cache to see if we already have an exact match for the requested image and channel subset 680 | CGImageRef exactCachedImage = (__bridge CGImageRef)self.cgImagesForImagesAndChannels[maskedIdentifier]; 681 | if (exactCachedImage != nil) { 682 | return exactCachedImage; 683 | } 684 | 685 | // If we don't have an exact match for the image+channel pair, we may still have the original image cached 686 | NSString *unmaskedIdentifier = [NSString stringWithFormat:@"%@/%d", imageName, (int)TinyImageChannel::TinyImageChannelAll]; 687 | CGImageRef originalImage = (__bridge CGImageRef)self.cgImagesForImagesAndChannels[unmaskedIdentifier]; 688 | 689 | if (originalImage == NULL) { 690 | // We got unlucky, so we need to load and cache the original 691 | if (image.uri.size() != 0) { 692 | 693 | NSURL *url = [[NSURL alloc] initFileURLWithPath:[[NSString alloc] initWithFormat:@"%s", image.uri.c_str()] relativeToURL:self.gltfPath]; 694 | CGImageSourceRef imageSource = CGImageSourceCreateWithURL((__bridge CFURLRef)url, nil); 695 | originalImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil); 696 | if (imageSource) { 697 | CFRelease(imageSource); 698 | } 699 | } else if ( image.bufferView != -1) { 700 | 701 | const auto& bufferView = model.bufferViews[image.bufferView]; 702 | const auto& buffer = model.buffers[bufferView.buffer]; 703 | 704 | NSData *imageData = [NSData dataWithBytes:buffer.data.data() + bufferView.byteOffset length:bufferView.byteLength]; 705 | CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, nil); 706 | originalImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil); 707 | if (imageSource) { 708 | CFRelease(imageSource); 709 | } 710 | } 711 | 712 | self.cgImagesForImagesAndChannels[unmaskedIdentifier] = (__bridge id)originalImage; 713 | CGImageRelease(originalImage); 714 | } 715 | 716 | // Now that we have the original, we may need to extract the requisite channel and cache the result 717 | if (channelMask != TinyImageChannel::TinyImageChannelAll) { 718 | CGImageRef extractedImage = [self newCGImageByExtractingChannel:(int)channelMask fromCGImage:&image]; 719 | self.cgImagesForImagesAndChannels[maskedIdentifier] = (__bridge id)extractedImage; 720 | CGImageRelease(extractedImage); 721 | return extractedImage; 722 | } 723 | 724 | return originalImage; 725 | } 726 | 727 | - (SCNGeometrySource *)geometrySourceWithSemantic:(SCNGeometrySourceSemantic)semantic accessorID:(NSInteger)accessorID fromModel: (const tinygltf::Model &)model{ 728 | if (accessorID == -1) { 729 | return nil; 730 | } 731 | 732 | const auto& accessor = model.accessors[accessorID]; 733 | 734 | const auto& bufferView = model.bufferViews[accessor.bufferView]; 735 | const auto& buffer = model.buffers[bufferView.buffer]; 736 | 737 | NSInteger bytesPerElement = TinyGLTFSizeOfComponentTypeWithDimension(accessor.componentType, accessor.type); 738 | BOOL componentsAreFloat = TINYGLTF_COMPONENT_TYPE_FLOAT == accessor.componentType; 739 | NSInteger componentsPerElement = TinyGLTFComponentCountForDimension(accessor.type); 740 | NSInteger bytesPerComponent = bytesPerElement / componentsPerElement; 741 | NSInteger dataOffset = 0; 742 | NSInteger dataStride = bufferView.byteStride; 743 | if (dataStride == 0) { 744 | dataStride = bytesPerElement; 745 | } 746 | 747 | const char *dataBase = ((char *)(buffer.data.data()) + bufferView.byteOffset + accessor.byteOffset); 748 | 749 | if ([semantic isEqualToString:SCNGeometrySourceSemanticBoneWeights]) 750 | { 751 | for (int i = 0; i < accessor.count; ++i) { 752 | float *weights = (float *)(dataBase + i * dataStride); 753 | float sum = weights[0] + weights[1] + weights[2] + weights[3]; 754 | if (sum != 1.0f) { 755 | weights[0] /= sum; 756 | weights[1] /= sum; 757 | weights[2] /= sum; 758 | weights[3] /= sum; 759 | } 760 | } 761 | } 762 | 763 | NSData *data = [NSData dataWithBytes:dataBase length:accessor.count * dataStride]; 764 | 765 | SCNGeometrySource *source = [SCNGeometrySource geometrySourceWithData:data 766 | semantic:semantic 767 | vectorCount:accessor.count 768 | floatComponents:componentsAreFloat 769 | componentsPerVector:componentsPerElement 770 | bytesPerComponent:bytesPerComponent 771 | dataOffset:dataOffset 772 | dataStride:dataStride]; 773 | return source; 774 | } 775 | 776 | - (NSArray *)inverseBindMatricesForTinySkin: (NSInteger) skinID fromModel: (const tinygltf::Model &)model { 777 | if (skinID == -1){ 778 | return @[]; 779 | } 780 | 781 | NSNumber *objectSkinID = [NSNumber numberWithInteger:skinID]; 782 | const auto& skin = model.skins[skinID]; 783 | 784 | NSArray *inverseBindMatrices = self.inverseBindMatricesForSkins[objectSkinID]; 785 | 786 | if (inverseBindMatrices != nil) { 787 | return inverseBindMatrices; 788 | } 789 | 790 | NSMutableArray *matrices = [NSMutableArray array]; 791 | 792 | const auto& accessor = model.accessors[skin.inverseBindMatrices]; 793 | const auto& bufferView = model.bufferViews[accessor.bufferView]; 794 | 795 | const unsigned char *buffer = model.buffers[bufferView.buffer].data.data(); 796 | GLTFMatrix4 *ibms = (GLTFMatrix4 *)(buffer + bufferView.byteOffset + accessor.byteOffset); 797 | 798 | for (int i = 0; i < accessor.count; ++i) { 799 | SCNMatrix4 ibm = GLTFSCNMatrix4FromFloat4x4(ibms[i]); 800 | NSValue *ibmValue = [NSValue valueWithSCNMatrix4:ibm]; 801 | [matrices addObject:ibmValue]; 802 | } 803 | matrices = [matrices copy]; 804 | self.inverseBindMatricesForSkins[objectSkinID] = matrices; 805 | 806 | return matrices; 807 | } 808 | 809 | - (NSArray *) bonesForTinySkin: (NSInteger) skinID fromModel: (const tinygltf::Model &)model { 810 | if (skinID == -1) { 811 | return @[]; 812 | } 813 | 814 | const auto& skin = model.skins[skinID]; 815 | 816 | NSMutableArray *bones = [NSMutableArray array]; 817 | for (const auto& jointNode: skin.joints) { 818 | SCNNode *boneNode = [self makeSCNNodeForTinyNodeByID:jointNode]; 819 | if (boneNode != nil) { 820 | [bones addObject:boneNode]; 821 | } else { 822 | #if DEBUG 823 | NSLog(@"WARNING: Did not find node for joint with identifier %d", jointNode); 824 | #endif 825 | } 826 | } 827 | 828 | if (bones.count == skin.joints.size()) { 829 | return [bones copy]; 830 | } else { 831 | #if DEBUG 832 | NSLog(@"WARNING: Bone count for skinner does not match joint node count for skin with identifier %ld", (long)skinID); 833 | #endif 834 | } 835 | 836 | return @[]; 837 | } 838 | 839 | - (SCNNode *) makeSCNNodeForTinyNodeByID: (NSInteger)nodeID { 840 | NSNumber *objectNodeID = [NSNumber numberWithInteger:nodeID]; 841 | SCNNode *scnNode = self.scnNodesForTinyNodes[objectNodeID]; 842 | 843 | if (scnNode == nil) { 844 | scnNode = [SCNNode node]; 845 | self.scnNodesForTinyNodes[objectNodeID] = scnNode; 846 | } 847 | 848 | return scnNode; 849 | } 850 | 851 | - (CGColorRef)newCGColorForFloat4:(simd_float4)v { 852 | CGFloat components[] = { v.x, v.y, v.z, v.w }; 853 | CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); 854 | CGColorRef color = CGColorCreate(colorSpace, &components[0]); 855 | CGColorSpaceRelease(colorSpace); 856 | return color; 857 | } 858 | 859 | - (CGColorRef)newCGColorForFloat3:(simd_float3)v { 860 | CGFloat components[] = { v.x, v.y, v.z, 1 }; 861 | CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); 862 | CGColorRef color = CGColorCreate(colorSpace, &components[0]); 863 | CGColorSpaceRelease(colorSpace); 864 | return color; 865 | } 866 | 867 | - (NSArray *)arrayFromQuaternionAccessor:(const tinygltf::Accessor &)accessor fromModel: (const tinygltf::Model &)model { 868 | 869 | const auto& buffetView = model.bufferViews[accessor.bufferView]; 870 | const auto& buffer = model.buffers[buffetView.buffer]; 871 | 872 | NSMutableArray *values = [NSMutableArray array]; 873 | 874 | const GLTFVector4 *quaternions = (const GLTFVector4 *)((const char *)buffer.data.data() + buffetView.byteOffset + accessor.byteOffset); 875 | NSInteger count = accessor.count; 876 | for (NSInteger i = 0; i < count; ++i) { 877 | SCNVector4 quat = SCNVector4Zero; 878 | if (quaternions != nullptr) { 879 | quat = (SCNVector4){ quaternions[i].x, quaternions[i].y, quaternions[i].z, quaternions[i].w }; 880 | } 881 | NSValue *value = [NSValue valueWithSCNVector4:quat]; 882 | [values addObject:value]; 883 | } 884 | return [values copy]; 885 | } 886 | 887 | - (NSArray *)vectorArrayFromAccessor:(const tinygltf::Accessor &)accessor fromModel: (const tinygltf::Model &)model { 888 | const auto& buffetView = model.bufferViews[accessor.bufferView]; 889 | const auto& buffer = model.buffers[buffetView.buffer]; 890 | 891 | NSMutableArray *values = [NSMutableArray array]; 892 | const GLTFVector3 *vectors = (const GLTFVector3 *)((const char *)buffer.data.data() + buffetView.byteOffset + accessor.byteOffset); 893 | NSInteger count = accessor.count; 894 | for (NSInteger i = 0; i < count; ++i) { 895 | SCNVector3 scnVec = SCNVector3Zero; 896 | if (vectors != nullptr) { 897 | GLTFVector3 vec = vectors[i]; 898 | scnVec = (SCNVector3){ vec.x, vec.y, vec.z }; 899 | } 900 | 901 | NSValue *value = [NSValue valueWithSCNVector3:scnVec]; 902 | [values addObject:value]; 903 | } 904 | return [values copy]; 905 | } 906 | 907 | - (NSArray *)vectorArrayFromScalarAccessor:(const tinygltf::Accessor &)accessor fromModel: (const tinygltf::Model &)model { 908 | const auto& buffetView = model.bufferViews[accessor.bufferView]; 909 | const auto& buffer = model.buffers[buffetView.buffer]; 910 | 911 | NSMutableArray *values = [NSMutableArray array]; 912 | const float *floats = (const float *)((const char *)buffer.data.data() + buffetView.byteOffset + accessor.byteOffset); 913 | NSInteger count = accessor.count; 914 | for (NSInteger i = 0; i < count; ++i) { 915 | SCNVector3 scnVec = SCNVector3Zero; 916 | if (floats != nullptr) { 917 | scnVec = (SCNVector3){ floats[i], floats[i], floats[i] }; 918 | } 919 | NSValue *value = [NSValue valueWithSCNVector3:scnVec]; 920 | [values addObject:value]; 921 | } 922 | return [values copy]; 923 | } 924 | 925 | - (NSArray *)normalizedArrayFromFloatAccessor:(const tinygltf::Accessor &)accessor fromModel: (const tinygltf::Model &)model minimumValue:(float)minimumValue maximumValue:(float)maximumValue { 926 | const auto& buffetView = model.bufferViews[accessor.bufferView]; 927 | const auto& buffer = model.buffers[buffetView.buffer]; 928 | 929 | NSMutableArray *values = [NSMutableArray array]; 930 | const float *floats = (const float *)((const char *)buffer.data.data() + buffetView.byteOffset + accessor.byteOffset); 931 | NSInteger count = accessor.count; 932 | for (NSInteger i = 0; i < count; ++i) { 933 | float f = 0; 934 | if (floats != nullptr) { 935 | f = floats[i]; 936 | } 937 | 938 | f = fmin(fmax(0, (f - minimumValue) / (maximumValue - minimumValue)), 1); 939 | NSValue *value = [NSNumber numberWithFloat:f]; 940 | [values addObject:value]; 941 | } 942 | return [values copy]; 943 | } 944 | 945 | - (NSTimeInterval)startTime:(const tinygltf::Accessor &)inputAccessor fromModel: (const tinygltf::Model &)model { 946 | const auto& buffetView = model.bufferViews[inputAccessor.bufferView]; 947 | const auto& buffer = model.buffers[buffetView.buffer]; 948 | 949 | const float *timeValues = (const float *)((const char *)buffer.data.data() + buffetView.byteOffset + inputAccessor.byteOffset); 950 | 951 | float startTime = 0; 952 | if (timeValues != nullptr) { 953 | startTime = timeValues[0]; 954 | } 955 | return startTime; 956 | } 957 | 958 | - (NSTimeInterval)endTime:(const tinygltf::Accessor &)outputAccessor fromModel: (const tinygltf::Model &)model keyFrameCount:(int)keyFrameCount { 959 | const auto& buffetView = model.bufferViews[outputAccessor.bufferView]; 960 | const auto& buffer = model.buffers[buffetView.buffer]; 961 | 962 | const float *timeValues = (const float *)((const char *)buffer.data.data() + buffetView.byteOffset + outputAccessor.byteOffset); 963 | float endTime = 0; 964 | if (timeValues != nullptr) { 965 | endTime = timeValues[keyFrameCount - 1]; 966 | } 967 | 968 | return endTime; 969 | } 970 | 971 | - (NSString *)_nextAnonymousAnimationName { 972 | NSString *name = [NSString stringWithFormat:@"UNNAMED_%d", (int)self.namelessAnimationIndex]; 973 | self.namelessAnimationIndex = self.namelessAnimationIndex + 1; 974 | return name; 975 | } 976 | 977 | @end 978 | -------------------------------------------------------------------------------- /glTF-quicklook/main.c: -------------------------------------------------------------------------------- 1 | //============================================================================== 2 | // 3 | // DO NO MODIFY THE CONTENT OF THIS FILE 4 | // 5 | // This file contains the generic CFPlug-in code necessary for your generator 6 | // To complete your generator implement the function in GenerateThumbnailForURL/GeneratePreviewForURL.c 7 | // 8 | //============================================================================== 9 | 10 | 11 | 12 | 13 | 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | // ----------------------------------------------------------------------------- 21 | // constants 22 | // ----------------------------------------------------------------------------- 23 | 24 | // Don't modify this line 25 | #define PLUGIN_ID "E924F51E-B20F-473F-A421-1244C6EF0033" 26 | 27 | // 28 | // Below is the generic glue code for all plug-ins. 29 | // 30 | // You should not have to modify this code aside from changing 31 | // names if you decide to change the names defined in the Info.plist 32 | // 33 | 34 | 35 | // ----------------------------------------------------------------------------- 36 | // typedefs 37 | // ----------------------------------------------------------------------------- 38 | 39 | // The thumbnail generation function to be implemented in GenerateThumbnailForURL.c 40 | OSStatus GenerateThumbnailForURL(void *thisInterface, QLThumbnailRequestRef thumbnail, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize); 41 | void CancelThumbnailGeneration(void* thisInterface, QLThumbnailRequestRef thumbnail); 42 | 43 | // The preview generation function to be implemented in GeneratePreviewForURL.c 44 | OSStatus GeneratePreviewForURL(void *thisInterface, QLPreviewRequestRef preview, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options); 45 | void CancelPreviewGeneration(void *thisInterface, QLPreviewRequestRef preview); 46 | 47 | // The layout for an instance of QuickLookGeneratorPlugIn 48 | typedef struct __QuickLookGeneratorPluginType 49 | { 50 | void *conduitInterface; 51 | CFUUIDRef factoryID; 52 | UInt32 refCount; 53 | } QuickLookGeneratorPluginType; 54 | 55 | // ----------------------------------------------------------------------------- 56 | // prototypes 57 | // ----------------------------------------------------------------------------- 58 | // Forward declaration for the IUnknown implementation. 59 | // 60 | 61 | QuickLookGeneratorPluginType *AllocQuickLookGeneratorPluginType(CFUUIDRef inFactoryID); 62 | void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInstance); 63 | HRESULT QuickLookGeneratorQueryInterface(void *thisInstance,REFIID iid,LPVOID *ppv); 64 | void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator,CFUUIDRef typeID); 65 | ULONG QuickLookGeneratorPluginAddRef(void *thisInstance); 66 | ULONG QuickLookGeneratorPluginRelease(void *thisInstance); 67 | 68 | // ----------------------------------------------------------------------------- 69 | // myInterfaceFtbl definition 70 | // ----------------------------------------------------------------------------- 71 | // The QLGeneratorInterfaceStruct function table. 72 | // 73 | static QLGeneratorInterfaceStruct myInterfaceFtbl = { 74 | NULL, 75 | QuickLookGeneratorQueryInterface, 76 | QuickLookGeneratorPluginAddRef, 77 | QuickLookGeneratorPluginRelease, 78 | NULL, 79 | NULL, 80 | NULL, 81 | NULL 82 | }; 83 | 84 | 85 | // ----------------------------------------------------------------------------- 86 | // AllocQuickLookGeneratorPluginType 87 | // ----------------------------------------------------------------------------- 88 | // Utility function that allocates a new instance. 89 | // You can do some initial setup for the generator here if you wish 90 | // like allocating globals etc... 91 | // 92 | QuickLookGeneratorPluginType *AllocQuickLookGeneratorPluginType(CFUUIDRef inFactoryID) 93 | { 94 | QuickLookGeneratorPluginType *theNewInstance; 95 | 96 | theNewInstance = (QuickLookGeneratorPluginType *)malloc(sizeof(QuickLookGeneratorPluginType)); 97 | memset(theNewInstance,0,sizeof(QuickLookGeneratorPluginType)); 98 | 99 | /* Point to the function table Malloc enough to store the stuff and copy the filler from myInterfaceFtbl over */ 100 | theNewInstance->conduitInterface = malloc(sizeof(QLGeneratorInterfaceStruct)); 101 | memcpy(theNewInstance->conduitInterface,&myInterfaceFtbl,sizeof(QLGeneratorInterfaceStruct)); 102 | 103 | /* Retain and keep an open instance refcount for each factory. */ 104 | theNewInstance->factoryID = CFRetain(inFactoryID); 105 | CFPlugInAddInstanceForFactory(inFactoryID); 106 | 107 | /* This function returns the IUnknown interface so set the refCount to one. */ 108 | theNewInstance->refCount = 1; 109 | return theNewInstance; 110 | } 111 | 112 | // ----------------------------------------------------------------------------- 113 | // DeallocQuickLookGeneratorPluginType 114 | // ----------------------------------------------------------------------------- 115 | // Utility function that deallocates the instance when 116 | // the refCount goes to zero. 117 | // In the current implementation generator interfaces are never deallocated 118 | // but implement this as this might change in the future 119 | // 120 | void DeallocQuickLookGeneratorPluginType(QuickLookGeneratorPluginType *thisInstance) 121 | { 122 | CFUUIDRef theFactoryID; 123 | 124 | theFactoryID = thisInstance->factoryID; 125 | /* Free the conduitInterface table up */ 126 | free(thisInstance->conduitInterface); 127 | 128 | /* Free the instance structure */ 129 | free(thisInstance); 130 | if (theFactoryID){ 131 | CFPlugInRemoveInstanceForFactory(theFactoryID); 132 | CFRelease(theFactoryID); 133 | } 134 | } 135 | 136 | // ----------------------------------------------------------------------------- 137 | // QuickLookGeneratorQueryInterface 138 | // ----------------------------------------------------------------------------- 139 | // Implementation of the IUnknown QueryInterface function. 140 | // 141 | HRESULT QuickLookGeneratorQueryInterface(void *thisInstance,REFIID iid,LPVOID *ppv) 142 | { 143 | CFUUIDRef interfaceID; 144 | 145 | interfaceID = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault,iid); 146 | 147 | if (CFEqual(interfaceID,kQLGeneratorCallbacksInterfaceID)){ 148 | /* If the Right interface was requested, bump the ref count, 149 | * set the ppv parameter equal to the instance, and 150 | * return good status. 151 | */ 152 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->GenerateThumbnailForURL = GenerateThumbnailForURL; 153 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->CancelThumbnailGeneration = CancelThumbnailGeneration; 154 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->GeneratePreviewForURL = GeneratePreviewForURL; 155 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType *)thisInstance)->conduitInterface)->CancelPreviewGeneration = CancelPreviewGeneration; 156 | ((QLGeneratorInterfaceStruct *)((QuickLookGeneratorPluginType*)thisInstance)->conduitInterface)->AddRef(thisInstance); 157 | *ppv = thisInstance; 158 | CFRelease(interfaceID); 159 | return S_OK; 160 | }else{ 161 | /* Requested interface unknown, bail with error. */ 162 | *ppv = NULL; 163 | CFRelease(interfaceID); 164 | return E_NOINTERFACE; 165 | } 166 | } 167 | 168 | // ----------------------------------------------------------------------------- 169 | // QuickLookGeneratorPluginAddRef 170 | // ----------------------------------------------------------------------------- 171 | // Implementation of reference counting for this type. Whenever an interface 172 | // is requested, bump the refCount for the instance. NOTE: returning the 173 | // refcount is a convention but is not required so don't rely on it. 174 | // 175 | ULONG QuickLookGeneratorPluginAddRef(void *thisInstance) 176 | { 177 | ((QuickLookGeneratorPluginType *)thisInstance )->refCount += 1; 178 | return ((QuickLookGeneratorPluginType*) thisInstance)->refCount; 179 | } 180 | 181 | // ----------------------------------------------------------------------------- 182 | // QuickLookGeneratorPluginRelease 183 | // ----------------------------------------------------------------------------- 184 | // When an interface is released, decrement the refCount. 185 | // If the refCount goes to zero, deallocate the instance. 186 | // 187 | ULONG QuickLookGeneratorPluginRelease(void *thisInstance) 188 | { 189 | ((QuickLookGeneratorPluginType*)thisInstance)->refCount -= 1; 190 | if (((QuickLookGeneratorPluginType*)thisInstance)->refCount == 0){ 191 | DeallocQuickLookGeneratorPluginType((QuickLookGeneratorPluginType*)thisInstance ); 192 | return 0; 193 | }else{ 194 | return ((QuickLookGeneratorPluginType*) thisInstance )->refCount; 195 | } 196 | } 197 | 198 | // ----------------------------------------------------------------------------- 199 | // QuickLookGeneratorPluginFactory 200 | // ----------------------------------------------------------------------------- 201 | void *QuickLookGeneratorPluginFactory(CFAllocatorRef allocator,CFUUIDRef typeID) 202 | { 203 | QuickLookGeneratorPluginType *result; 204 | CFUUIDRef uuid; 205 | 206 | /* If correct type is being requested, allocate an 207 | * instance of kQLGeneratorTypeID and return the IUnknown interface. 208 | */ 209 | if (CFEqual(typeID,kQLGeneratorTypeID)){ 210 | uuid = CFUUIDCreateFromString(kCFAllocatorDefault,CFSTR(PLUGIN_ID)); 211 | result = AllocQuickLookGeneratorPluginType(uuid); 212 | CFRelease(uuid); 213 | return result; 214 | } 215 | /* If the requested type is incorrect, return NULL. */ 216 | return NULL; 217 | } 218 | 219 | --------------------------------------------------------------------------------