Repository: Microsoft/glTF-Toolkit Branch: master Commit: 505b91d9a42f Files: 84 Total size: 17.0 MB Directory structure: gitextract_t5s8ddnm/ ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── WindowsMRAssetConverter/ │ ├── AssetType.cpp │ ├── AssetType.h │ ├── CommandLine.cpp │ ├── CommandLine.h │ ├── FileSystem.cpp │ ├── FileSystem.h │ ├── README.md │ ├── WindowsMRAssetConverter.cpp │ ├── WindowsMRAssetConverter.vcxproj │ ├── WindowsMRAssetConverter.vcxproj.filters │ ├── packages.config │ ├── stdafx.cpp │ ├── stdafx.h │ └── targetver.h ├── glTF-Toolkit/ │ ├── glTF-Toolkit.vcxproj │ ├── glTF-Toolkit.vcxproj.filters │ ├── inc/ │ │ ├── AccessorUtils.h │ │ ├── DeviceResources.h │ │ ├── GLBtoGLTF.h │ │ ├── GLTFLODUtils.h │ │ ├── GLTFMeshCompressionUtils.h │ │ ├── GLTFSDK.h │ │ ├── GLTFSpecularGlossinessUtils.h │ │ ├── GLTFTextureCompressionUtils.h │ │ ├── GLTFTexturePackingUtils.h │ │ ├── GLTFTextureUtils.h │ │ ├── SerializeBinary.h │ │ └── pch.h │ ├── packages.config │ └── src/ │ ├── DeviceResources.cpp │ ├── GLBtoGLTF.cpp │ ├── GLTFLODUtils.cpp │ ├── GLTFMeshCompressionUtils.cpp │ ├── GLTFSpecularGlossinessUtils.cpp │ ├── GLTFTextureCompressionUtils.cpp │ ├── GLTFTexturePackingUtils.cpp │ ├── GLTFTextureUtils.cpp │ ├── SerializeBinary.cpp │ └── pch.cpp ├── glTF-Toolkit.Test/ │ ├── GLBSerializerTests.cpp │ ├── GLBtoGLTFTests.cpp │ ├── GLTFLODUtilsTests.cpp │ ├── GLTFTextureCompressionUtilsTests.cpp │ ├── GLTFTexturePackingUtilsTests.cpp │ ├── Helpers/ │ │ ├── StreamMock.h │ │ ├── TestUtils.h │ │ └── WStringUtils.h │ ├── Resources/ │ │ └── gltf/ │ │ ├── CubeAsset3D.gltf │ │ ├── CubeWithLOD.gltf │ │ ├── TextureTest/ │ │ │ └── TextureTest.gltf │ │ ├── WaterBottle/ │ │ │ └── WaterBottle.gltf │ │ └── WaterBottle_ORM/ │ │ ├── WaterBottle.gltf │ │ ├── WaterBottle_WindowsMR.gltf │ │ ├── WaterBottle_baseColor.DDS │ │ ├── WaterBottle_emissive.DDS │ │ ├── WaterBottle_normal.dds │ │ └── WaterBottle_occlusionRoughnessMetallic.dds │ ├── glTF-Toolkit.Test.vcxproj │ ├── glTF-Toolkit.Test.vcxproj.filters │ └── packages.config ├── glTF-Toolkit.UWP/ │ ├── GLTFSerialization.cpp │ ├── GLTFSerialization.h │ ├── GLTFStreams.h │ ├── WindowsMRConversion.cpp │ ├── WindowsMRConversion.h │ ├── glTF-Toolkit.UWP.vcxproj │ ├── glTF-Toolkit.UWP.vcxproj.filters │ ├── packages.config │ ├── pch.cpp │ └── pch.h ├── glTF-Toolkit.UWP.Test/ │ ├── Assets/ │ │ └── 3DModels/ │ │ └── WaterBottle.glb │ ├── Package.appxmanifest │ ├── Properties/ │ │ ├── AssemblyInfo.cs │ │ └── UnitTestApp.rd.xml │ ├── UWPTest.cs │ ├── UnitTestApp.xaml │ ├── UnitTestApp.xaml.cs │ └── glTF-Toolkit.UWP.Test.csproj └── glTF-Toolkit.sln ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # .NET Core project.lock.json project.fragment.lock.json artifacts/ **/Properties/launchSettings.json *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Typescript v1 declaration files typings/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # JetBrains Rider .idea/ *.sln.iml # CodeRush .cr/ # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs ================================================ FILE: CODE_OF_CONDUCT.md ================================================ This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) Microsoft Corporation. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE ================================================ FILE: README.md ================================================ # Microsoft glTF Toolkit This project contains a collection of tools and libraries to modify and optimize glTF assets. Additionally the repository includes a command line tool that uses these steps in sequence in order to convert a glTF 2.0 core asset for use in the Windows Mixed Reality home, following the published [documentation](https://developer.microsoft.com/en-us/windows/mixed-reality/creating_3d_models_for_use_in_the_windows_mixed_reality_home). The latest release of the Windows Mixed Reality Asset converter is available on the [releases tab](https://github.com/Microsoft/glTF-Toolkit/releases). [![Build status](https://ci.appveyor.com/api/projects/status/4n8m94mpc03dcuxt?svg=true)](https://ci.appveyor.com/project/robertos/gltf-toolkit) ## Features The current release includes code for: - Packing PBR material textures using [DirectXTex](http://github.com/Microsoft/DirectXTex) for use with the [MSFT_packing_occlusionRoughnessMetallic](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_packing_occlusionRoughnessMetallic) and [MSFT_packing_normalRoughnessMetallic](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_packing_normalRoughnessMetallic) extensions. - Compressing textures as BC3, BC5 or BC7 and generate mip maps using [DirectXTex](http://github.com/Microsoft/DirectXTex) for use with the [MSFT_texture_dds](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_texture_dds) extension. - Removing [KHR_materials_pbrSpecularGlossiness](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness) by converting material prameters to metallic-roughness. - Mesh compression using [KHR_draco_mesh_compression](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_draco_mesh_compression) extension; this can only be used on 1809 and later and should only be used for assets that are transmitted over the network as load time is increased with compression. - Merging multiple glTF assets into a asset with multiple levels of detail using the [MSFT_lod](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_lod) extension. - A command line tool that combines these components to create optimized glTF assets for the Windows Mixed Reality Home - A UWP compatible Windows Runtime component to perform conversions between GLTF and GLB, as well as optimize assets for Windows Mixed Reality at runtime ## Dependencies This project consumes the following projects through NuGet packages: - [Microsoft.glTF.CPP](https://www.nuget.org/packages/Microsoft.glTF.CPP), licensed under the MIT license - [DirectXTex](http://github.com/Microsoft/DirectXTex), licensed under the MIT license - [RapidJSON](https://github.com/Tencent/rapidjson/), licensed under the MIT license - [Draco](https://github.com/google/draco/), licensed under Apache License 2.0 ## Building This project can be built using Visual Studio 2017 Update 4 on Windows 10 Fall Creators Update (16299.0). ## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.microsoft.com. When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. ## License Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the [MIT License](LICENSE). ================================================ FILE: SECURITY.md ================================================ ## Security Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. ## Reporting Security Issues **Please do not report security vulnerabilities through public GitHub issues.** Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) * Full paths of source file(s) related to the manifestation of the issue * The location of the affected source code (tag/branch/commit or direct URL) * Any special configuration required to reproduce the issue * Step-by-step instructions to reproduce the issue * Proof-of-concept or exploit code (if possible) * Impact of the issue, including how an attacker might exploit the issue This information will help us triage your report more quickly. If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. ## Preferred Languages We prefer all communications to be in English. ## Policy Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). ================================================ FILE: WindowsMRAssetConverter/AssetType.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "stdafx.h" #include "AssetType.h" const wchar_t * EXTENSION_GLTF = L".gltf"; const wchar_t * EXTENSION_GLB = L".glb"; AssetType AssetTypeUtils::AssetTypeFromFilePath(const std::wstring& assetPath) { const wchar_t *inputExtensionRaw = nullptr; if (FAILED(PathCchFindExtension(assetPath.c_str(), assetPath.length() + 1, &inputExtensionRaw))) { throw std::invalid_argument("Invalid input file extension."); } if (_wcsicmp(inputExtensionRaw, EXTENSION_GLTF) == 0) { return AssetType::GLTF; } else if (_wcsicmp(inputExtensionRaw, EXTENSION_GLB) == 0) { return AssetType::GLB; } else { throw std::invalid_argument("Invalid file, please provide a GLTF or GLB."); } } ================================================ FILE: WindowsMRAssetConverter/AssetType.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once extern const wchar_t * EXTENSION_GLTF; extern const wchar_t * EXTENSION_GLB; enum class AssetType { GLTF, GLB }; namespace AssetTypeUtils { AssetType AssetTypeFromFilePath(const std::wstring& assetPath); } ================================================ FILE: WindowsMRAssetConverter/CommandLine.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "stdafx.h" #include "CommandLine.h" #include "FileSystem.h" // Constants const wchar_t * PARAM_OUTFILE = L"-o"; const wchar_t * PARAM_TMPDIR = L"-temp-directory"; const wchar_t * PARAM_LOD = L"-lod"; const wchar_t * PARAM_SCREENCOVERAGE = L"-screen-coverage"; const wchar_t * PARAM_MAXTEXTURESIZE = L"-max-texture-size"; const wchar_t * PARAM_SHARE_MATERIALS = L"-share-materials"; const wchar_t * PARAM_MIN_VERSION = L"-min-version"; const wchar_t * PARAM_PLATFORM = L"-platform"; const wchar_t * PARAM_REPLACE_TEXTURES = L"-replace-textures"; const wchar_t * PARAM_COMPRESS_MESHES = L"-compress-meshes"; const wchar_t * PARAM_VALUE_VERSION_1709 = L"1709"; const wchar_t * PARAM_VALUE_VERSION_1803 = L"1803"; const wchar_t * PARAM_VALUE_VERSION_1809 = L"1809"; const wchar_t * PARAM_VALUE_VERSION_RS3 = L"rs3"; const wchar_t * PARAM_VALUE_VERSION_RS4 = L"rs4"; const wchar_t * PARAM_VALUE_VERSION_RS5 = L"rs5"; const wchar_t * PARAM_VALUE_VERSION_LATEST = L"latest"; const wchar_t * PARAM_VALUE_HOLOGRAPHIC = L"holographic"; const wchar_t * PARAM_VALUE_HOLOLENS= L"hololens"; const wchar_t * PARAM_VALUE_DESKTOP = L"desktop"; const wchar_t * PARAM_VALUE_PC = L"pc"; const wchar_t * PARAM_VALUE_ALL = L"all"; const wchar_t * SUFFIX_CONVERTED = L"_converted"; const wchar_t * CLI_INDENT = L" "; const size_t MAXTEXTURESIZE_DEFAULT = 512; const size_t MAXTEXTURESIZE_MAX = 4096; const CommandLine::Version MIN_VERSION_DEFAULT = CommandLine::Version::Version1709; const CommandLine::Platform PLATFORM_DEFAULT = CommandLine::Platform::Desktop; enum class CommandLineParsingState { Initial, InputRead, ReadOutFile, ReadTmpDir, ReadLods, ReadScreenCoverage, ReadMaxTextureSize, ReadMinVersion, ReadPlatform }; void CommandLine::PrintHelp() { auto indent = std::wstring(CLI_INDENT); std::wcerr << std::endl << L"Windows Mixed Reality Asset Converter" << std::endl << L"=====================================" << std::endl << std::endl << L"A command line tool to convert core GLTF 2.0 assets for use in " << L"the Windows Mixed Reality home, with the proper texture packing, compression and merged LODs." << std::endl << std::endl << L"Usage: WindowsMRAssetConverter " << std::endl << std::endl << L"Optional arguments:" << std::endl << indent << "[" << std::wstring(PARAM_OUTFILE) << L" ]" << std::endl << indent << "[" << std::wstring(PARAM_TMPDIR) << L" ] - default is the system temp folder for the user" << std::endl << indent << "[" << std::wstring(PARAM_PLATFORM) << " <" << PARAM_VALUE_ALL << " | " << PARAM_VALUE_HOLOGRAPHIC << " | " << PARAM_VALUE_DESKTOP << ">] - defaults to " << PARAM_VALUE_DESKTOP << std::endl << indent << "[" << std::wstring(PARAM_MIN_VERSION) << " <" << PARAM_VALUE_VERSION_1709 << " | " << PARAM_VALUE_VERSION_1803 << " | " << PARAM_VALUE_VERSION_1809 << " | " << PARAM_VALUE_VERSION_LATEST << ">] - defaults to " << PARAM_VALUE_VERSION_1709 << std::endl << indent << "[" << std::wstring(PARAM_LOD) << " ]" << std::endl << indent << "[" << std::wstring(PARAM_SCREENCOVERAGE) << " ]" << std::endl << indent << "[" << std::wstring(PARAM_SHARE_MATERIALS) << "] - disabled if not present" << std::endl << indent << "[" << std::wstring(PARAM_MAXTEXTURESIZE) << " ] - defaults to 512" << std::endl << indent << "[" << std::wstring(PARAM_REPLACE_TEXTURES) << "] - disabled if not present" << std::endl << indent << "[" << std::wstring(PARAM_COMPRESS_MESHES) << "] - compress meshes with Draco" << std::endl << std::endl << "Example:" << std::endl << indent << "WindowsMRAssetConverter FileToConvert.gltf " << std::wstring(PARAM_OUTFILE) << " ConvertedFile.glb " << std::wstring(PARAM_LOD) << " Lod1.gltf Lod2.gltf " << std::wstring(PARAM_SCREENCOVERAGE) << " 0.5 0.2 0.01" << std::endl << std::endl << "The above will convert \"FileToConvert.gltf\" into \"ConvertedFile.glb\" in the " << "current directory." << std::endl << std::endl << "If the file is a GLB and the output name is not specified, defaults to the same name as input " << "+ \"_converted.glb\"." << std::endl << std::endl; } void CommandLine::ParseCommandLineArguments( int argc, wchar_t *argv[], std::wstring& inputFilePath, AssetType& inputAssetType, std::wstring& outFilePath, std::wstring& tempDirectory, std::vector& lodFilePaths, std::vector& screenCoveragePercentages, size_t& maxTextureSize, bool& shareMaterials, Version& minVersion, Platform& targetPlatforms, bool& replaceTextures, bool& compressMeshes) { CommandLineParsingState state = CommandLineParsingState::Initial; inputFilePath = FileSystem::GetFullPath(std::wstring(argv[1])); inputAssetType = AssetTypeUtils::AssetTypeFromFilePath(inputFilePath); // Reset input parameters outFilePath = L""; tempDirectory = L""; lodFilePaths.clear(); screenCoveragePercentages.clear(); maxTextureSize = MAXTEXTURESIZE_DEFAULT; shareMaterials = false; minVersion = MIN_VERSION_DEFAULT; targetPlatforms = PLATFORM_DEFAULT; replaceTextures = false; compressMeshes = false; state = CommandLineParsingState::InputRead; std::wstring outFile; std::wstring tmpDir; for (int i = 2; i < argc; i++) { std::wstring param = argv[i]; if (param == PARAM_OUTFILE) { outFile = L""; state = CommandLineParsingState::ReadOutFile; } else if (param == PARAM_TMPDIR) { tmpDir = L""; state = CommandLineParsingState::ReadTmpDir; } else if (param == PARAM_LOD) { lodFilePaths.clear(); state = CommandLineParsingState::ReadLods; } else if (param == PARAM_SCREENCOVERAGE) { screenCoveragePercentages.clear(); state = CommandLineParsingState::ReadScreenCoverage; } else if (param == PARAM_MAXTEXTURESIZE) { maxTextureSize = MAXTEXTURESIZE_DEFAULT; state = CommandLineParsingState::ReadMaxTextureSize; } else if (param == PARAM_SHARE_MATERIALS) { shareMaterials = true; state = CommandLineParsingState::InputRead; } else if (param == PARAM_MIN_VERSION) { minVersion = MIN_VERSION_DEFAULT; state = CommandLineParsingState::ReadMinVersion; } else if (param == PARAM_PLATFORM) { targetPlatforms = PLATFORM_DEFAULT; state = CommandLineParsingState::ReadPlatform; } else if (param == PARAM_REPLACE_TEXTURES) { replaceTextures = true; state = CommandLineParsingState::InputRead; } else if (param == PARAM_COMPRESS_MESHES) { if (minVersion >= CommandLine::Version::Version1809) { compressMeshes = true; } else { throw std::invalid_argument("Invalid min version specified with mesh compression; must be at least 1809."); } state = CommandLineParsingState::InputRead; } else { switch (state) { case CommandLineParsingState::ReadOutFile: outFile = FileSystem::GetFullPath(param); state = CommandLineParsingState::InputRead; break; case CommandLineParsingState::ReadTmpDir: tmpDir = FileSystem::GetFullPath(param); state = CommandLineParsingState::InputRead; break; case CommandLineParsingState::ReadLods: lodFilePaths.push_back(FileSystem::GetFullPath(param)); break; case CommandLineParsingState::ReadScreenCoverage: { auto paramA = std::string(param.begin(), param.end()); screenCoveragePercentages.push_back(std::atof(paramA.c_str())); break; } case CommandLineParsingState::ReadMaxTextureSize: maxTextureSize = std::min(static_cast(std::stoul(param.c_str())), MAXTEXTURESIZE_MAX); break; case CommandLineParsingState::ReadMinVersion: if (_wcsicmp(param.c_str(), PARAM_VALUE_VERSION_1709) == 0 || _wcsicmp(param.c_str(), PARAM_VALUE_VERSION_RS3) == 0) { minVersion = Version::Version1709; } else if (_wcsicmp(param.c_str(), PARAM_VALUE_VERSION_1803) == 0 || _wcsicmp(param.c_str(), PARAM_VALUE_VERSION_RS4) == 0) { minVersion = Version::Version1803; } else if (_wcsicmp(param.c_str(), PARAM_VALUE_VERSION_1809) == 0 || _wcsicmp(param.c_str(), PARAM_VALUE_VERSION_RS5) == 0) { minVersion = Version::Version1809; } else if (_wcsicmp(param.c_str(), PARAM_VALUE_VERSION_LATEST) == 0) { minVersion = Version::Latest; } else { throw std::invalid_argument("Invalid min version specified. For help, try the command again without parameters."); } state = CommandLineParsingState::InputRead; break; case CommandLineParsingState::ReadPlatform: if (_wcsicmp(param.c_str(), PARAM_VALUE_ALL) == 0) { targetPlatforms = (Platform) (Platform::Desktop | Platform::Holographic); } else if (_wcsicmp(param.c_str(), PARAM_VALUE_HOLOGRAPHIC) == 0 || _wcsicmp(param.c_str(), PARAM_VALUE_HOLOLENS) == 0) { targetPlatforms = Platform::Holographic; } else if (_wcsicmp(param.c_str(), PARAM_VALUE_DESKTOP) == 0 || _wcsicmp(param.c_str(), PARAM_VALUE_PC) == 0) { targetPlatforms = Platform::Desktop; } else { throw std::invalid_argument("Invalid platform specified. For help, try the command again without parameters."); } state = CommandLineParsingState::InputRead; break; case CommandLineParsingState::Initial: case CommandLineParsingState::InputRead: default: // Invalid argument detected throw std::invalid_argument("Invalid usage. For help, try the command again without parameters."); } } } if (!std::experimental::filesystem::exists(inputFilePath)) { throw std::invalid_argument("Input file not found."); } for (auto& lodFilePath : lodFilePaths) { if (!std::experimental::filesystem::exists(lodFilePath)) { throw std::invalid_argument("Lod file not found."); } } if (outFile.empty()) { std::wstring inputFilePathWithoutExtension = inputFilePath; if (FAILED(PathCchRemoveExtension(&inputFilePathWithoutExtension[0], inputFilePathWithoutExtension.length() + 1))) { throw std::invalid_argument("Invalid input file extension."); } outFile = std::wstring(&inputFilePathWithoutExtension[0]); if (inputAssetType == AssetType::GLB) { outFile += SUFFIX_CONVERTED; } outFile += EXTENSION_GLB; } outFilePath = outFile; if (tmpDir.empty()) { tmpDir = FileSystem::CreateTempFolder(); } tempDirectory = tmpDir; } ================================================ FILE: WindowsMRAssetConverter/CommandLine.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once #include #include "AssetType.h" namespace CommandLine { enum Platform { None = 0x0, Holographic = 0x1, Desktop = 0x2 }; enum class Version { Version1709, // Fall Creators Update (RS3) Version1803, // Spring Creators Update (RS4) Version1809, // Fall 2018 Update (RS5) Latest = Version1809 }; void PrintHelp(); void ParseCommandLineArguments( int argc, wchar_t *argv[], std::wstring& inputFilePath, AssetType& inputAssetType, std::wstring& outFilePath, std::wstring& tempDirectory, std::vector& lodFilePaths, std::vector& screenCoveragePercentages, size_t& maxTextureSize, bool& sharedMaterials, Version& minVersion, Platform& targetPlatforms, bool& replaceTextures, bool& compressMeshes); }; ================================================ FILE: WindowsMRAssetConverter/FileSystem.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "stdafx.h" #include "FileSystem.h" std::wstring FileSystem::GetRelativePathWithTrailingSeparator(std::wstring from, std::wstring to) { // once c++17 filesystem is fully supported, this should become something like: // return std::filesystem::relative(to, from) + std::filesystem::path::preferred_separator; std::experimental::filesystem::path fromFS(std::experimental::filesystem::canonical(from)); std::experimental::filesystem::path toFS(std::experimental::filesystem::canonical(to)); std::experimental::filesystem::path result; auto fromIter = fromFS.begin(); auto toIter = toFS.begin(); while (fromIter != fromFS.end() || toIter != toFS.end()) { const auto& f = *fromIter; const auto& t = *toIter; if (f == t) { fromIter++; toIter++; } else { while (fromIter != fromFS.end()) { result /= ".."; fromIter++; } while (toIter != toFS.end()) { result /= *toIter; toIter++; } result += std::experimental::filesystem::path::preferred_separator; } } return result; } std::wstring FileSystem::GetBasePath(const std::wstring& path) { std::wstring pathCopy(path); wchar_t *basePath = &pathCopy[0]; if (FAILED(PathCchRemoveFileSpec(basePath, pathCopy.length() + 1))) { throw std::invalid_argument("Invalid input path."); } return std::move(std::wstring(basePath)); } std::wstring FileSystem::GetFullPath(const std::wstring& path) { wchar_t fullPath[MAX_PATH]; if (GetFullPathName(path.c_str(), ARRAYSIZE(fullPath), fullPath, NULL) == 0) { throw std::invalid_argument("Invalid input file path."); } return std::move(std::wstring(fullPath)); } std::wstring FileSystem::CreateSubFolder(const std::wstring& parentPath, const std::wstring& subFolderName) { std::wstring errorMessageW = L"Could not create a sub-folder of " + parentPath + L"."; std::string errorMessage(errorMessageW.begin(), errorMessageW.end()); wchar_t subFolderPath[MAX_PATH]; if (FAILED(PathCchCombine(subFolderPath, ARRAYSIZE(subFolderPath), parentPath.c_str(), (subFolderName + L"\\").c_str()))) { throw std::runtime_error(errorMessage); } if (CreateDirectory(subFolderPath, NULL) == 0 && GetLastError() != ERROR_ALREADY_EXISTS) { throw std::runtime_error(errorMessage); } return std::move(std::wstring(subFolderPath)); } std::wstring FileSystem::CreateTempFolder() { std::wstring errorMessageW = L"Could not get a temporary folder. Try specifying one in the command line."; std::string errorMessage(errorMessageW.begin(), errorMessageW.end()); wchar_t tmpDirRaw[MAX_PATH]; auto returnValue = GetTempPath(MAX_PATH, tmpDirRaw); if (returnValue > MAX_PATH || (returnValue == 0)) { throw std::runtime_error(errorMessage); } // Get a random folder to drop the files GUID guid = { 0 }; if (FAILED(CoCreateGuid(&guid))) { throw std::runtime_error(errorMessage); } wchar_t guidRaw[MAX_PATH]; if (StringFromGUID2(guid, guidRaw, ARRAYSIZE(guidRaw)) == 0) { throw std::runtime_error(errorMessage); } return std::move(CreateSubFolder(tmpDirRaw, guidRaw)); } ================================================ FILE: WindowsMRAssetConverter/FileSystem.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once #include namespace FileSystem { std::wstring GetRelativePathWithTrailingSeparator(std::wstring from, std::wstring to); std::wstring GetBasePath(const std::wstring& path); std::wstring GetFullPath(const std::wstring& path); std::wstring CreateSubFolder(const std::wstring& parentPath, const std::wstring& subFolderName); std::wstring CreateTempFolder(); }; ================================================ FILE: WindowsMRAssetConverter/README.md ================================================ # Windows Mixed Reality Asset Converter A command line tool to convert core GLTF 2.0 for use in the Windows Mixed Reality home, following the published [documentation](https://developer.microsoft.com/en-us/windows/mixed-reality/creating_3d_models_for_use_in_the_windows_mixed_reality_home). Note that this tool does not enforce any limits specified in the documentation (polygon count, texture size, etc.), so you might still encounter issues placing models if you do not conform to those limits. ## Usage WindowsMRAssetConverter _<path to GLTF/GLB>_ ## Optional arguments - `-o ` - Specifies the output file name and directory for the output GLB. - If the file is a GLB and the output name is not specified, the tool defaults to the same name as input + "_converted.glb". - `-platform ` - **Default:** `desktop` - `desktop`: optimizes assets for immersive PC-based headsets using the [MSFT_packing_occlusionRoughnessMetallic](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_packing_occlusionRoughnessMetallic) extension. - `holographic`: optimizes assets for HoloLens using the [MSFT_packing_normalRoughnessMetallic](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_packing_normalRoughnessMetallic) extension. - `all`: creates assets optimized for both holographic and desktop devices, but with a larger file size. - `-min-version <1709 | 1803 | latest>` - **Default:** `1709` - Specifies the minimum version of Windows 10 supported by this asset. - The current options are `1709` (Fall Creators Update) and `1803` (Spring Creators Update), as well as `latest` which is currently the same as `1803`. - Supporting Windows 10 version 1709 results in assets with a larger file size. If your app is compatible with Windows 10 1803+ only, it is recommended to set `-min-version 1803`. - This setting does not have any effect on the Holographic platform. - `-lod ` - Specifies a list of assets that represent levels of detail, from higher to lower, that should be merged with the main asset and used as alternates when the asset is displayed from a distance (with limited screen coverage). - `-screen-coverage ` - Specifies the maximum screen coverage values for each of the levels of detail, according to the [MSFT_lod](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_lod) extension specification. - `-share-materials` - If enabled, creates assets that share materials between different levels of detail. - This assumes all LOD documents use the same indices for each material, and uses the textures from the most detailed level. - `-temp-directory ` - **Default:** system temp folder for the user - Allows overriding the temporary folder where intermediate files (packed/compressed textures, converted GLBs) will be placed. - `-max-texture-size ` - **Default:** 512 - Allows overriding the maximum texture dimension (width/height) when compressing textures. The recommended maximum dimension in the [documentation](https://developer.microsoft.com/en-us/windows/mixed-reality/creating_3d_models_for_use_in_the_windows_mixed_reality_home#texture_resolutions_and_workflow) is 512, and the allowed maximum is 4096. - `-replace-textures` - If enabled, replaces all textures with their DDS compressed equivalents during the compression step. - This results in a smaller file size, but the resulting file will not be compatible with most glTF viewers. ## Example `WindowsMRAssetConverter FileToConvert.gltf -o ConvertedFile.glb -platform all -lod Lod1.gltf Lod2.gltf -screen-coverage 0.5 0.2 0.01` The above will convert _FileToConvert.gltf_ into _ConvertedFile.glb_ in the current directory. ## Pipeline overview Each asset goes through the following steps when converting for compatibility with the Windows Mixed Reality home: 1. **Conversion from GLB** - Any GLB files are converted to loose glTF + assets, to simplify the code for reading resources 1. **Texture packing** - The textures that are relevant for the Windows MR home are packed according to the [documentation](https://developer.microsoft.com/en-us/windows/mixed-reality/creating_3d_models_for_use_in_the_windows_mixed_reality_home#materials) using the [MSFT\_packing\_occlusionRoughnessMetallic](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_packing_occlusionRoughnessMetallic) and [MSFT\_packing\_normalRoughnessMetallic](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_packing_normalRoughnessMetallic) extensions as necessary 1. **Texture compression** - All textures that are used in the Windows MR home must be compressed as DDS BC5 or BC7 according to the [documentation](https://developer.microsoft.com/en-us/windows/mixed-reality/creating_3d_models_for_use_in_the_windows_mixed_reality_home#materials). This step also generates mip maps for the textures, and resizes them down if necessary 1. **LOD merging** - All assets that represent levels of detail are merged into the main asset using the [MSFT_lod](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_lod) extension 1. **GLB export** - The resulting assets are exported as a GLB with all resources. As part of this step, accessors are modified to conform to the [glTF implementation notes in the documentation](https://developer.microsoft.com/en-us/windows/mixed-reality/creating_3d_models_for_use_in_the_windows_mixed_reality_home#gltf_implementation_notes): component types are converted to types supported by the Windows MR home, and the min and max values are calculated before serializing the accessors to the GLB ## Additional resources - [Creating 3D models for use in the Windows Mixed Reality home](https://developer.microsoft.com/en-us/windows/mixed-reality/creating_3d_models_for_use_in_the_windows_mixed_reality_home) - [Microsoft glTF LOD Extension Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_lod) - [PC Mixed Reality Texture Packing Extensions Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_packing_occlusionRoughnessMetallic) - [Holographic Mixed Reality Texture Packing Extensions Specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_packing_normalRoughnessMetallic) - [Microsoft DDS Textures glTF extensions specification](https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/MSFT_texture_dds) - [Implementing 3D app launchers](https://developer.microsoft.com/en-us/windows/mixed-reality/implementing_3d_app_launchers) - [Implementing 3D deep links for your app in the Windows Mixed Reality home](https://developer.microsoft.com/en-us/windows/mixed-reality/implementing_3d_deep_links_for_your_app_in_the_windows_mixed_reality_home) - [Navigating the Windows Mixed Reality home](https://developer.microsoft.com/en-us/windows/mixed-reality/navigating_the_windows_mixed_reality_home) ================================================ FILE: WindowsMRAssetConverter/WindowsMRAssetConverter.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "stdafx.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "CommandLine.h" #include "FileSystem.h" #include "GLTFTextureUtils.h" using namespace Microsoft::glTF; using namespace Microsoft::glTF::Toolkit; class GLTFStreamReader : public IStreamReader { public: GLTFStreamReader(std::wstring uriBase) : m_uriBase(uriBase) {} virtual ~GLTFStreamReader() override {} virtual std::shared_ptr GetInputStream(const std::string& filename) const override { std::wstring filenameW = std::wstring(filename.begin(), filename.end()); wchar_t uriAbsoluteRaw[MAX_PATH]; // Note: PathCchCombine will return the last argument if it's an absolute path if (FAILED(::PathCchCombine(uriAbsoluteRaw, ARRAYSIZE(uriAbsoluteRaw), m_uriBase.c_str(), filenameW.c_str()))) { throw std::invalid_argument("Could not get the base path for the GLTF resources. Try specifying the full path."); } return std::make_shared(uriAbsoluteRaw, std::ios::binary); } private: const std::wstring m_uriBase; }; class GLBStreamWriter : public Microsoft::glTF::IStreamWriter { public: GLBStreamWriter(const std::wstring& filename) : m_stream(std::make_shared(filename, std::ios_base::binary | std::ios_base::out)) { } std::shared_ptr GetOutputStream(const std::string&) const override { return m_stream; } private: std::shared_ptr m_stream; }; Document ProcessTextures( size_t maxTextureSize, TexturePacking packing, bool retainOriginalImages, const std::wstring& tempDirectory, const Document& document, const std::shared_ptr& streamReader) { Document resultDocument(document); std::string tempDirectoryA(tempDirectory.begin(), tempDirectory.end()); std::wcout << L"Specular Glossiness conversion..." << std::endl; // 0. Specular Glossiness conversion resultDocument = GLTFSpecularGlossinessUtils::ConvertMaterials(streamReader, resultDocument, tempDirectoryA); std::wcout << L"Removing redundant textures and images..." << std::endl; // 1. Remove redundant textures and images resultDocument = GLTFTextureUtils::RemoveRedundantTexturesAndImages(resultDocument); std::wcout << L"Packing textures..." << std::endl; // 2. Texture Packing resultDocument = GLTFTexturePackingUtils::PackAllMaterialsForWindowsMR(streamReader, resultDocument, packing, tempDirectoryA); std::wcout << L"Compressing textures - this can take a few minutes..." << std::endl; // 3. Texture Compression resultDocument = GLTFTextureCompressionUtils::CompressAllTexturesForWindowsMR(streamReader, resultDocument, tempDirectoryA, maxTextureSize, retainOriginalImages); return resultDocument; } Document LoadAndConvertDocumentForWindowsMR( std::wstring& inputFilePath, AssetType inputAssetType, const std::wstring& tempDirectory, bool meshCompression) { // Load the document std::experimental::filesystem::path inputFilePathFS(inputFilePath); std::wstring inputFileName = inputFilePathFS.filename(); std::wcout << L"Loading input document: " << inputFileName << L"..." << std::endl; std::string tempDirectoryA(tempDirectory.begin(), tempDirectory.end()); if (inputAssetType == AssetType::GLB) { // Convert the GLB to GLTF in the temp directory std::string inputFilePathA(inputFilePath.begin(), inputFilePath.end()); // inputGltfName is the path to the converted GLTF without extension std::wstring inputGltfName = inputFilePathFS.stem(); std::string inputGltfNameA = std::string(inputGltfName.begin(), inputGltfName.end()); GLBToGLTF::UnpackGLB(inputFilePathA, tempDirectoryA, inputGltfNameA); inputFilePath = tempDirectory + inputGltfName + EXTENSION_GLTF; } auto stream = std::make_shared(inputFilePath, std::ios::in); Document document = Deserialize(*stream, KHR::GetKHRExtensionDeserializer()); // Get the base path from where to read all the assets auto streamReader = std::make_shared(FileSystem::GetBasePath(inputFilePath)); if (meshCompression) { std::wcout << L"Compressing meshes - this can take a few minutes..." << std::endl; document = GLTFMeshCompressionUtils::CompressMeshes(streamReader, document, {}, tempDirectoryA); } return document; } int wmain(int argc, wchar_t *argv[]) { if (argc < 2) { CommandLine::PrintHelp(); return 0; } // Initialize COM CoInitialize(NULL); try { // Arguments std::wstring inputFilePath; AssetType inputAssetType; std::wstring outFilePath; std::wstring tempDirectory; std::vector lodFilePaths; std::vector screenCoveragePercentages; size_t maxTextureSize; bool shareMaterials; CommandLine::Version minVersion; CommandLine::Platform targetPlatforms; bool replaceTextures; bool meshCompression = false; CommandLine::ParseCommandLineArguments( argc, argv, inputFilePath, inputAssetType, outFilePath, tempDirectory, lodFilePaths, screenCoveragePercentages, maxTextureSize, shareMaterials, minVersion, targetPlatforms, replaceTextures, meshCompression); TexturePacking packing = TexturePacking::None; std::wstring compatibleVersionsText; if ((targetPlatforms & CommandLine::Platform::Holographic) > 0) { compatibleVersionsText += L"HoloLens"; // Holographic mode: NRM packing = (TexturePacking)(packing | TexturePacking::NormalRoughnessMetallic); } if ((targetPlatforms & CommandLine::Platform::Desktop) > 0) { if (!compatibleVersionsText.empty()) { compatibleVersionsText += L" and "; } // Desktop 1803+ mode: ORM packing = (TexturePacking)(packing | TexturePacking::OcclusionRoughnessMetallic); if (minVersion == CommandLine::Version::Version1709) { // Desktop 1709 mode: RMO packing = (TexturePacking)(packing | TexturePacking::RoughnessMetallicOcclusion); compatibleVersionsText += L"Desktop (version 1709+)"; } else if (minVersion == CommandLine::Version::Version1803) { compatibleVersionsText += L"Desktop (version 1803+)"; } else { compatibleVersionsText += L"Desktop (version 1809+)"; } } std::wcout << L"\nThis will generate an asset compatible with " << compatibleVersionsText << L"\n" << std::endl; // Load document, and perform steps: // 1. Mesh Compression auto document = LoadAndConvertDocumentForWindowsMR(inputFilePath, inputAssetType, tempDirectory, meshCompression); // 2. LOD Merging if (!lodFilePaths.empty()) { std::wcout << L"Merging LODs..." << std::endl; std::vector lodDocuments; std::vector lodDocumentRelativePaths; lodDocuments.push_back(document); for (size_t i = 0; i < lodFilePaths.size(); i++) { // Apply the same optimizations for each LOD auto lod = lodFilePaths[i]; auto subFolder = FileSystem::CreateSubFolder(tempDirectory, L"lod" + std::to_wstring(i + 1)); lodDocuments.push_back(LoadAndConvertDocumentForWindowsMR(lod, AssetTypeUtils::AssetTypeFromFilePath(lod), subFolder, meshCompression)); lodDocumentRelativePaths.push_back(FileSystem::GetRelativePathWithTrailingSeparator(FileSystem::GetBasePath(inputFilePath), FileSystem::GetBasePath(lod))); } document = GLTFLODUtils::MergeDocumentsAsLODs(lodDocuments, screenCoveragePercentages, lodDocumentRelativePaths, shareMaterials); } // 3. Texture Packing // 4. Texture Compression auto streamReader = std::make_shared(FileSystem::GetBasePath(inputFilePath)); document = ProcessTextures(maxTextureSize, packing, !replaceTextures, tempDirectory, document, streamReader); // 5. Make sure there's a default scene if (!document.HasDefaultScene()) { document.defaultSceneId = document.scenes.Elements()[0].id; } // 6. GLB Export std::wcout << L"Exporting as GLB..." << std::endl; // The Windows MR Fall Creators update has restrictions on the supported // component types of accessors. AccessorConversionStrategy accessorConversion = [](const Accessor& accessor) { if (accessor.type == AccessorType::TYPE_SCALAR) { switch (accessor.componentType) { case ComponentType::COMPONENT_BYTE: case ComponentType::COMPONENT_UNSIGNED_BYTE: case ComponentType::COMPONENT_SHORT: return ComponentType::COMPONENT_UNSIGNED_SHORT; default: return accessor.componentType; } } else if (accessor.type == AccessorType::TYPE_VEC2 || accessor.type == AccessorType::TYPE_VEC3) { return ComponentType::COMPONENT_FLOAT; } return accessor.componentType; }; SerializeBinary(document, streamReader, std::make_shared(outFilePath), accessorConversion); std::wcout << L"Done!" << std::endl; std::wcout << L"Output file: " << outFilePath << std::endl; } catch (std::exception ex) { std::cerr << ex.what() << std::endl; return 1; } return 0; } ================================================ FILE: WindowsMRAssetConverter/WindowsMRAssetConverter.vcxproj ================================================ Debug Win32 Release Win32 Debug x64 Release x64 15.0 {8A19D99C-78DC-4267-AB57-DB1DDBFBEFDF} Win32Proj WindowsMRAssetConverter 10.0.16299.0 Application true v141 Unicode Application false v141 true Unicode Application true v141 Unicode Application false v141 true Unicode true $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(SolutionDir)Built\Int\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ true $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(SolutionDir)Built\Int\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ false $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(SolutionDir)Built\Int\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ false $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(SolutionDir)Built\Int\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ Use Level4 Disabled true WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) $(SolutionDir)glTF-Toolkit\inc stdcpp17 /permissive- %(AdditionalOptions) true 4996 Console true d3d11.lib;dxgi.lib;pathcch.lib;%(AdditionalDependencies) Use Level4 Disabled true _DEBUG;_CONSOLE;%(PreprocessorDefinitions) $(SolutionDir)glTF-Toolkit\inc stdcpp17 /permissive- %(AdditionalOptions) true 4996 Console true d3d11.lib;dxgi.lib;pathcch.lib;shlwapi.lib;%(AdditionalDependencies) Use Level4 MaxSpeed true true true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) $(SolutionDir)glTF-Toolkit\inc stdcpp17 /permissive- %(AdditionalOptions) true 4996 Console true true true d3d11.lib;dxgi.lib;pathcch.lib;%(AdditionalDependencies) Use Level4 MaxSpeed true true true NDEBUG;_CONSOLE;%(PreprocessorDefinitions) $(SolutionDir)glTF-Toolkit\inc stdcpp17 /permissive- %(AdditionalOptions) true 4996 Console true true true d3d11.lib;dxgi.lib;pathcch.lib;shlwapi.lib;%(AdditionalDependencies) Create Create Create Create {ff0275f1-58cb-4745-ba81-f6c1df66e206} false This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. ================================================ FILE: WindowsMRAssetConverter/WindowsMRAssetConverter.vcxproj.filters ================================================  {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;hm;inl;inc;xsd {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms Header Files Header Files Header Files Header Files Header Files Source Files Source Files Source Files Source Files Source Files ================================================ FILE: WindowsMRAssetConverter/packages.config ================================================  ================================================ FILE: WindowsMRAssetConverter/stdafx.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "stdafx.h" ================================================ FILE: WindowsMRAssetConverter/stdafx.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once // Use the C++ standard templated min/max #define NOMINMAX // DirectX apps don't need GDI #define NODRAWTEXT #define NOGDI #define NOBITMAP // Include if you need this #define NOMCX // Include if you need this #define NOSERVICE // WinHelp is deprecated #define NOHELP #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include ================================================ FILE: WindowsMRAssetConverter/targetver.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once // Including SDKDDKVer.h defines the highest available Windows platform. // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. #include ================================================ FILE: glTF-Toolkit/glTF-Toolkit.vcxproj ================================================ Debug ARM Debug Win32 Release ARM Release Win32 Debug x64 Release x64 Debug Static Runtime ARM Debug Static Runtime Win32 Release Static Runtime ARM Release Static Runtime Win32 Debug Static Runtime x64 Release Static Runtime x64 15.0 {FF0275F1-58CB-4745-BA81-F6C1DF66E206} Win32Proj glTFToolkit 10.0.16299.0 StaticLibrary Unicode v141 true true false true $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(SolutionDir)Built\Int\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ false true Use pch.h stdcpp17 Level4 inc;%(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) /permissive- %(AdditionalOptions) true true true _LIB;%(PreprocessorDefinitions) MaxSpeed true true MultiThreaded Windows true true true true Disabled _DEBUG;%(PreprocessorDefinitions) MultiThreadedDebug NDEBUG;%(PreprocessorDefinitions) WIN32;%(PreprocessorDefinitions) Create This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. ================================================ FILE: glTF-Toolkit/glTF-Toolkit.vcxproj.filters ================================================  {2156fb82-8d5e-4cc3-b2ac-e13a18bc665f} {620de9b6-9c44-44b6-9d93-99834c5efdf2} inc inc inc inc inc inc inc inc inc inc inc inc src src src src src src src src src src ================================================ FILE: glTF-Toolkit/inc/AccessorUtils.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once #include "GLTFSDK.h" #include #include namespace Microsoft::glTF::Toolkit { /// /// Utilities to manipulate accessors in a glTF asset. /// class AccessorUtils { public: // Note: XML Documentation cannot be applied to templated types per https://docs.microsoft.com/en-us/cpp/ide/xml-documentation-visual-cpp // Calculates the min and max values for an accessor according to the glTF 2.0 specification. // accessor is: The accessor definition for which the min and max values will be calculated. // accessorContents is: The raw data contained in the accessor. // returns: A pair containing the min and max vectors for the accessor, in that order. template static std::pair, std::vector> CalculateMinMax(const Accessor& accessor, const std::vector& accessorContents) { auto typeCount = Accessor::GetTypeCount(accessor.type); auto min = std::vector(typeCount); auto max = std::vector(typeCount); if (accessorContents.size() < typeCount) { throw std::invalid_argument("The accessor must contain data in order to calculate min and max."); } // Initialize min and max with the first elements of the array for (size_t j = 0; j < typeCount; j++) { auto current = static_cast(accessorContents[j]); min[j] = current; max[j] = current; } for (size_t i = 1; i < accessor.count; i++) { for (size_t j = 0; j < typeCount; j++) { auto current = static_cast(accessorContents[i * typeCount + j]); min[j] = std::min(min[j], current); max[j] = std::max(max[j], current); } } return std::make_pair(min, max); } }; } ================================================ FILE: glTF-Toolkit/inc/DeviceResources.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once namespace DX { /// /// Controls all the DirectX device resources. /// class DeviceResources { public: DeviceResources(D3D_FEATURE_LEVEL minFeatureLevel = D3D_FEATURE_LEVEL_10_0); void CreateDeviceResources(); void HandleDeviceLost(); // Direct3D Accessors. ID3D11Device1* GetD3DDevice() const { return m_d3dDevice.Get(); } ID3D11DeviceContext1* GetD3DDeviceContext() const { return m_d3dContext.Get(); } IDXGISwapChain1* GetSwapChain() const { return m_swapChain.Get(); } D3D_FEATURE_LEVEL GetDeviceFeatureLevel() const { return m_d3dFeatureLevel; } private: void GetHardwareAdapter(IDXGIAdapter1** ppAdapter); // Direct3D objects. Microsoft::WRL::ComPtr m_d3dDevice; Microsoft::WRL::ComPtr m_d3dContext; Microsoft::WRL::ComPtr m_swapChain; Microsoft::WRL::ComPtr m_d3dAnnotation; // Direct3D properties. D3D_FEATURE_LEVEL m_d3dMinFeatureLevel; // Cached device properties. D3D_FEATURE_LEVEL m_d3dFeatureLevel; }; // Helper class for COM exceptions class com_exception : public std::exception { public: com_exception(HRESULT hr) : result(hr) {} virtual const char* what() const override { static char s_str[64] = { 0 }; sprintf_s(s_str, "Failure with HRESULT of %08X", result); return s_str; } private: HRESULT result; }; // Helper utility converts D3D API failures into exceptions. inline void ThrowIfFailed(HRESULT hr) { if (FAILED(hr)) { throw com_exception(hr); } } } ================================================ FILE: glTF-Toolkit/inc/GLBtoGLTF.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once #include "GLTFSDK.h" namespace Microsoft::glTF::Toolkit { /// /// Utilities to convert glTF-Binary files (GLB) to /// unpacked glTF assets. /// class GLBToGLTF { public: /// /// Unpacks a GLB asset into a GLTF manifest and its /// resources (bin files and images). /// /// The path to the GLB file to unpack. /// The directory to which the glTF manifest and resources will be unpacked. /// /// The name of the output glTF manifest file, without the extension. /// This name will be used as a prefix to all unpacked resources. /// static void UnpackGLB(const std::string& glbPath, const std::string& outDirectory, const std::string& gltfName); /// /// Extracts the contents of all buffer views from a GLB file into a /// byte vector that can be saves as a bin file to be used in a glTF file. /// /// A stream pointing to the GLB file. /// The manifest describing the GLB asset. /// The offset on the input file where the GLB buffer starts. /// The length of the new buffer (sum of all buffer view lengths). /// /// The binary content of the buffer views as a vector. /// static std::vector SaveBin(std::istream* in, const Microsoft::glTF::Document& glbDoc, const size_t bufferOffset, const size_t newBufferlength, std::unordered_set& unpackedBufferViews); /// /// Loads all images in a glTF-Binary (GLB) asset into a map relating each image identifier to the contents of that image. /// /// A stream pointing to the GLB file. /// The manifest describing the GLB asset. /// The name that should be used when creating the identifiers for the image files. /// The offset on the input file where the GLB buffer starts. /// /// A map relating each image identifier to the contents of that image. /// static std::unordered_map> GetImagesData(std::istream* in, const Microsoft::glTF::Document& glbDoc, const std::string& name, const size_t bufferOffset); /// /// Creates the glTF manifest that represents a GLB file after unpacking. /// /// The original manifest contained in the GLB file. /// The name that should be used when creating the identifiers for the image and bin files when unpacking. /// /// A new glTF manifest that represents the same file, but with images and resources referenced by URI instead of embedded ina GLB buffer. /// static Microsoft::glTF::Document CreateGLTFDocument(const Microsoft::glTF::Document& glbDoc, const std::string& name, std::unordered_set& unpackedBufferViews); }; } ================================================ FILE: glTF-Toolkit/inc/GLTFLODUtils.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once #include "GLTFSDK.h" namespace Microsoft::glTF::Toolkit { extern const char* EXTENSION_MSFT_LOD; extern const char* MSFT_LOD_IDS_KEY; typedef std::unordered_map>> LODMap; /// /// Utilities to load and merge levels of detail (LOD) in glTF assets using the MSFT_lod extension. /// class GLTFLODUtils { public: /// /// Parses the node LODs in a GLTF asset as a map that can be used to read LOD values for each node. /// /// A map that relates each node ID to a vector of its levels of detail node IDs. /// The glTF document containing LODs to be parsed. static LODMap ParseDocumentNodeLODs(const Document& doc); /// /// Inserts each LOD Document as a node LOD (at the root level) of the specified primary GLTF asset. /// Note: Animation is not currently supported. /// /// The primary GLTF Document with the inserted LOD node. /// A vector of glTF documents to merge as LODs. The first element of the vector is assumed to be the primary LOD. /// A vector of relative path prefixes to the non-LOD0 LOD gltf documents. Used for finding resources in those LODs. /// If not specified, all resources are assumed to be in the same directory. static Document MergeDocumentsAsLODs(const std::vector& docs, const std::vector& relativePaths = std::vector(), const bool& sharedMaterials = false); /// /// Inserts each LOD Document as a node LOD (at the root level) of the specified primary GLTF asset. /// Note: Animation is not currently supported. /// /// The primary GLTF Document with the inserted LOD node. /// A vector of glTF documents to merge as LODs. The first element of the vector is assumed to be the primary LOD. /// A vector with the screen coverage percentages corresponding to each LOD. If the size of this /// vector is larger than the size of , lower coverage values will cause the asset to be invisible. /// A vector of relative path prefixes to the non-LOD0 LOD gltf documents. Used for finding resources in those LODs. /// If not specified, all resources are assumed to be in the same directory. static Document MergeDocumentsAsLODs(const std::vector& docs, const std::vector& screenCoveragePercentages, const std::vector& relativePaths = std::vector(), const bool& sharedMaterials = false); /// /// Determines the highest number of Node LODs for a given glTF asset. /// /// The glTF asset for which to count the max number of node LODs. /// A map containing the parsed node LODs in the document. /// The highest number of Node LODs in the asset. static uint32_t NumberOfNodeLODLevels(const Document& doc, const LODMap& lods); }; } ================================================ FILE: glTF-Toolkit/inc/GLTFMeshCompressionUtils.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once #include "GLTFSDK.h" #include "GLTFSDK/BufferBuilder.h" namespace Microsoft::glTF::Toolkit { /// Draco compression options. struct CompressionOptions { int PositionQuantizationBits = 14; int TexCoordQuantizationBits = 12; int NormalQuantizationBits = 10; int ColorQuantizationBits = 8; int GenericQuantizationBits = 12; int Speed = 3; }; /// /// Utilities to compress textures in a glTF asset. /// class GLTFMeshCompressionUtils { public: /// /// Applies to every mesh in the document, following the same parameter structure as that function. /// /// A stream reader that is capable of accessing the resources used in the glTF asset by URI. /// The document from which the mesh will be loaded. /// The compression options that will be used. /// The output directory to which compressed data should be saved. /// /// A new glTF manifest that uses the KHR_draco_mesh_compression extension to point to the compressed meshes. /// static Document CompressMeshes( std::shared_ptr streamReader, const Document & doc, CompressionOptions options, const std::string& outputDirectory); /// /// Applies Draco mesh compression to the supplied mesh and creates a new set of vertex buffers for all the primitive attributes. /// /// A stream reader that is capable of accessing the resources used in the glTF asset by URI. /// The document from which the mesh will be loaded. /// The mesh which the mesh will be compressed. /// The compression options that will be used. /// The output buffer builder that handles bufferId generation for the return document. /// Out parameter of BufferView Ids that are no longer in use and should be removed. /// /// A new glTF manifest that uses the KHR_draco_mesh_compression extension to point to the compressed meshes. /// static Document CompressMesh( std::shared_ptr streamReader, const Document & doc, CompressionOptions options, const Mesh & mesh, BufferBuilder* builder, std::unordered_set& bufferViewsToRemove); }; } ================================================ FILE: glTF-Toolkit/inc/GLTFSDK.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once #pragma warning(push) #pragma warning(disable : 4634) #pragma warning(disable : 4996) #include #include #include #include #include #include #include #include #include #include #pragma warning(pop) ================================================ FILE: glTF-Toolkit/inc/GLTFSpecularGlossinessUtils.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once #include "GLTFSDK.h" namespace Microsoft::glTF::Toolkit { /// /// Utilities to remove Specular Glossiness from a glTF asset. /// class GLTFSpecularGlossinessUtils { public: /// /// Applies to every material in the document, following the same parameter structure as that function. /// /// A stream reader that is capable of accessing the resources used in the glTF asset by URI. /// The document from which the mesh will be loaded. /// The output directory to which compressed data should be saved. /// /// A new glTF manifest without the KHR_materials_pbrSpecularGlossiness extension. /// static Document ConvertMaterials(std::shared_ptr streamReader, const Document & doc, const std::string& outputDirectory); /// /// Removes the KHR_materials_pbrSpecularGlossiness extension by converting the parameters to Metal Roughness. /// /// A stream reader that is capable of accessing the resources used in the glTF asset by URI. /// The document from which the mesh will be loaded. /// The material to be converted. /// The output directory to which compressed data should be saved. /// /// A new glTF manifest without the KHR_materials_pbrSpecularGlossiness extension. /// static Document ConvertMaterial(std::shared_ptr streamReader, const Document & doc, const Material & material, const std::string& outputDirectory); }; } ================================================ FILE: glTF-Toolkit/inc/GLTFTextureCompressionUtils.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once #include "GLTFSDK.h" namespace DirectX { class ScratchImage; } namespace Microsoft::glTF::Toolkit { extern const char* EXTENSION_MSFT_TEXTURE_DDS; /// Supported compression algorithms for textures. enum class TextureCompression { None, BC3, BC5, BC7, BC7_SRGB }; /// /// Utilities to compress textures in a glTF asset. /// class GLTFTextureCompressionUtils { public: /// Compresses a texture in a glTF from a WIC-readable format (PNG, JPEG, BMP, GIF, TIFF, HD Photo, ICO) /// into a DDS with the appropriate compression. /// If a dds extension already exists for this texture, do nothing. /// The stream reader that will be used to get streams to each image from its URI. /// Input glTF document. /// Texture object that is contained in input document. If texture does not exist in document, /// throws exception. /// The desired block compression method (e.g. BC5, BC7). /// The output directory to which compressed files should be saved. /// The maximum texture size to which textures should be resized before compression, in pixels. /// If true, also generates mip maps when compressing. /// If true, retains the original image on the resulting glTF. If false, /// replaces that image (making the glTF incompatible with most core glTF 2.0 viewers). /// Returns a new Document that contains a new reference to the compressed dds file added as part /// of the MSFT_texture_dds extension. /// /// Example Input: /// /// "textures": [ /// { /// "source": 0, /// } /// ], /// "images": [ /// { /// "uri": "defaultTexture.png" /// } /// ] /// /// /// Example Output (BC7 Compression, with retainOriginalImage == true): /// /// "textures": /// [ /// { /// "source": 0, /// "extensions": { /// "MSFT_texture_dds": { /// "source": 1 /// } /// } /// } /// ], /// "images": [ /// { /// "uri": "defaultTexture.png" /// }, /// { /// "uri": "defaultTexture-BC7.DDS" /// } /// ] /// /// /// static Document CompressTextureAsDDS(std::shared_ptr streamReader, const Document & doc, const Texture & texture, TextureCompression compression, const std::string& outputDirectory, size_t maxTextureSize = std::numeric_limits::max(), bool generateMipMaps = true, bool retainOriginalImage = true, bool treatAsLinear = true); /// /// Applies to all textures in the document that are accessible via materials according to the /// requirements of the Windows Mixed Reality home. /// Normal textures get compressed with BC5, while baseColorTexture, occlusion, metallicRoughness and emissive textures get compressed with BC7. /// The stream reader that will be used to get streams to each image from its URI. /// Input glTF document. /// The output directory to which compressed files should be saved. /// The maximum texture size to which textures should be resized before compression, in pixels. /// If true, also generates mip maps when compressing. /// If true, retains the original image on the resulting glTF. If false, /// replaces that image (making the glTF incompatible with most core glTF 2.0 viewers). /// Returns a new Document that contains alternate textures for all applicable materials following the requirements of the Windows /// Mixed Reality home using the MSFT_texture_dds extension. /// static Document CompressAllTexturesForWindowsMR(std::shared_ptr streamReader, const Document & doc, const std::string& outputDirectory, size_t maxTextureSize = std::numeric_limits::max(), bool retainOriginalImages = true); /// /// Compresses a DirectX::ScratchImage in place using the specified compression. /// /// The image to compress. /// The desired compression algorithm. static void CompressImage(DirectX::ScratchImage& image, TextureCompression compression); }; } ================================================ FILE: glTF-Toolkit/inc/GLTFTexturePackingUtils.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once #include "GLTFSDK.h" namespace Microsoft::glTF::Toolkit { extern const char* EXTENSION_MSFT_PACKING_ORM; extern const char* EXTENSION_MSFT_PACKING_NRM; extern const char* MSFT_PACKING_INDEX_KEY; extern const char* MSFT_PACKING_ORM_ORMTEXTURE_KEY; extern const char* MSFT_PACKING_ORM_RMOTEXTURE_KEY; extern const char* MSFT_PACKING_ORM_NORMALTEXTURE_KEY; extern const char* MSFT_PACKING_NRM_KEY; /// Texture packing flags. May be combined to pack multiple formats at once. enum TexturePacking { None = 0x0, OcclusionRoughnessMetallic = 0x1, RoughnessMetallicOcclusion = 0x2, NormalRoughnessMetallic = 0x4 }; /// /// Utilities to pack textures from glTF assets and refer to them from an asset /// using the MSFT_packing_occlusionRoughnessMetallic extension. /// class GLTFTexturePackingUtils { public: /// /// Packs a single material's textures for Windows Mixed Reality for all the packing schemes selected, and adds the resulting texture(s) back to the material in the document. /// /// A stream reader that is capable of accessing the resources used in the glTF asset by URI. /// The document from which the texture will be loaded. /// The material to be packed. /// The packing scheme that will be used to pick the textures and choose their order. /// The output directory to which packed textures should be saved. /// /// A new glTF manifest that uses the MSFT_packing_occlusionRoughnessMetallic extension to point to the packed textures. /// static Document PackMaterialForWindowsMR(std::shared_ptr streamReader, const Document & doc, const Material & material, TexturePacking packing, const std::string& outputDirectory); /// /// Applies to every material in the document, following the same parameter structure as that function. /// /// A stream reader that is capable of accessing the resources used in the glTF asset by URI. /// The document from which the texture will be loaded. /// The packing scheme that will be used to pick the textures and choose their order. /// The output directory to which packed textures should be saved. /// /// A new glTF manifest that uses the MSFT_packing_occlusionRoughnessMetallic extension to point to the packed textures. /// static Document PackAllMaterialsForWindowsMR(std::shared_ptr streamReader, const Document & doc, TexturePacking packing, const std::string& outputDirectory); static std::unordered_set GetTextureIndicesFromMsftExtensions(const Material& material); }; } ================================================ FILE: glTF-Toolkit/inc/GLTFTextureUtils.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information.#pragma once #include "GLTFSDK.h" #include #include #include namespace Microsoft::glTF::Toolkit { enum Channel { Red = 0, Green = 4, Blue = 8, Alpha = 12 }; /// /// Utilities to load textures from glTF assets. /// class GLTFTextureUtils { public: /// /// Loads a texture into a scratch image in the DXGI_FORMAT_R32G32B32A32_FLOAT format for in-memory processing. /// /// A scratch image containing the loaded texture in the DXGI_FORMAT_R32G32B32A32_FLOAT format. /// A stream reader that is capable of accessing the resources used in the glTF asset by URI. /// The document from which the texture will be loaded. /// The identifier of the texture to be loaded. static DirectX::ScratchImage LoadTexture(std::shared_ptr streamReader, const Document& doc, const std::string& textureId, bool treatAsLinear = true); /// /// Gets the value of channel `channel` in pixel index `offset` in image `imageData` /// assumed to be formatted as DXGI_FORMAT_R32G32B32A32_FLOAT /// static float* GetChannelValue(uint8_t * imageData, size_t offset, Channel channel); static std::string SaveAsPng(DirectX::ScratchImage* image, const std::string& fileName, const std::string& directory, const GUID* targetFormat = &GUID_WICPixelFormat24bppBGR); static std::string AddImageToDocument(Document& doc, const std::string& imageUri); static void ResizeToLargest(std::unique_ptr& image1, std::unique_ptr& image2); static void ResizeIfNeeded(const std::unique_ptr& image, size_t resizedWidth, size_t resizedHeight); static Document RemoveRedundantTexturesAndImages(const Document& doc); }; } ================================================ FILE: glTF-Toolkit/inc/SerializeBinary.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information.#pragma once #include "GLTFSDK.h" #include #include #include #include "AccessorUtils.h" namespace Microsoft::glTF::Toolkit { /// /// A function that determines to which type an accessor should be converted, /// based on the accessor metadata. /// typedef std::function AccessorConversionStrategy; /// /// Serializes a glTF asset as a glTF binary (GLB) file. /// /// The glTF asset manifest to be serialized. /// A stream reader that is capable of accessing the resources used in the glTF asset by URI. /// A stream factory that is capable of creating an output stream where the GLB will be saved, and a temporary stream for /// use during the serialization process. void SerializeBinary(const Document& document, std::shared_ptr inputStreamReader, std::shared_ptr outputStreamWriter, const AccessorConversionStrategy& accessorConversion = nullptr); /// /// Serializes a glTF asset as a glTF binary (GLB) file. /// /// The glTF asset manifest to be serialized. /// A resource reader that is capable of accessing the resources used in the document. /// A stream factory that is capable of creating an output stream where the GLB will be saved, and a temporary stream for /// use during the serialization process. void SerializeBinary(const Document& document, const GLTFResourceReader& resourceReader, std::shared_ptr outputStreamWriter, const AccessorConversionStrategy& accessorConversion = nullptr); } ================================================ FILE: glTF-Toolkit/inc/pch.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once // Use the C++ standard templated min/max #define NOMINMAX // DirectX apps don't need GDI #define NODRAWTEXT #define NOGDI #define NOBITMAP // Include if you need this #define NOMCX // Include if you need this #define NOSERVICE // WinHelp is deprecated #define NOHELP #define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include #include // Silence C4996 for CodeCVT deprecations. CodeCVT is still used for UTF8 conversions in GLTFLODUtils.cpp // TODO: Remove #define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING #include #undef _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING #include #include #include #include #include #include #include #include #include ================================================ FILE: glTF-Toolkit/packages.config ================================================  ================================================ FILE: glTF-Toolkit/src/DeviceResources.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" #include "DeviceResources.h" using namespace DirectX; using namespace DX; using Microsoft::WRL::ComPtr; namespace { #if defined(_DEBUG) // Check for SDK Layer support. inline bool SdkLayersAvailable() { HRESULT hr = D3D11CreateDevice( nullptr, D3D_DRIVER_TYPE_NULL, // There is no need to create a real hardware device. 0, D3D11_CREATE_DEVICE_DEBUG, // Check for the SDK layers. nullptr, // Any feature level will do. 0, D3D11_SDK_VERSION, nullptr, // No need to keep the D3D device reference. nullptr, // No need to know the feature level. nullptr // No need to keep the D3D device context reference. ); return SUCCEEDED(hr); } #endif }; // Constructor for DeviceResources. DeviceResources::DeviceResources(D3D_FEATURE_LEVEL minFeatureLevel) : m_d3dMinFeatureLevel(minFeatureLevel), m_d3dFeatureLevel(D3D_FEATURE_LEVEL_9_1) { } // Configures the Direct3D device, and stores handles to it and the device context. void DeviceResources::CreateDeviceResources() { UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT; #if defined(_DEBUG) if (SdkLayersAvailable()) { // If the project is in a debug build, enable debugging via SDK Layers with this flag. creationFlags |= D3D11_CREATE_DEVICE_DEBUG; } else { OutputDebugStringA("WARNING: Direct3D Debug Device is not available\n"); } #endif // Determine DirectX hardware feature levels this app will support. static const D3D_FEATURE_LEVEL s_featureLevels[] = { D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1, D3D_FEATURE_LEVEL_10_0, D3D_FEATURE_LEVEL_9_3, D3D_FEATURE_LEVEL_9_2, D3D_FEATURE_LEVEL_9_1, }; UINT featLevelCount = 0; for (; featLevelCount < _countof(s_featureLevels); ++featLevelCount) { if (s_featureLevels[featLevelCount] < m_d3dMinFeatureLevel) break; } if (!featLevelCount) { throw std::out_of_range("minFeatureLevel too high"); } ComPtr adapter; GetHardwareAdapter(adapter.GetAddressOf()); // Create the Direct3D 11 API device object and a corresponding context. ComPtr device; ComPtr context; HRESULT hr = E_FAIL; if (adapter) { hr = D3D11CreateDevice( adapter.Get(), D3D_DRIVER_TYPE_UNKNOWN, 0, creationFlags, s_featureLevels, featLevelCount, D3D11_SDK_VERSION, device.GetAddressOf(), // Returns the Direct3D device created. &m_d3dFeatureLevel, // Returns feature level of device created. context.GetAddressOf() // Returns the device immediate context. ); } #if defined(NDEBUG) else { throw std::exception("No Direct3D hardware device found"); } #else if (FAILED(hr)) { // If the initialization fails, fall back to the WARP device. // For more information on WARP, see: // http://go.microsoft.com/fwlink/?LinkId=286690 hr = D3D11CreateDevice( nullptr, D3D_DRIVER_TYPE_WARP, // Create a WARP device instead of a hardware device. 0, creationFlags, s_featureLevels, featLevelCount, D3D11_SDK_VERSION, device.GetAddressOf(), &m_d3dFeatureLevel, context.GetAddressOf() ); if (SUCCEEDED(hr)) { OutputDebugStringA("Direct3D Adapter - WARP\n"); } } #endif ThrowIfFailed(hr); #ifndef NDEBUG ComPtr d3dDebug; if (SUCCEEDED(device.As(&d3dDebug))) { ComPtr d3dInfoQueue; if (SUCCEEDED(d3dDebug.As(&d3dInfoQueue))) { #ifdef _DEBUG d3dInfoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_CORRUPTION, true); d3dInfoQueue->SetBreakOnSeverity(D3D11_MESSAGE_SEVERITY_ERROR, true); #endif D3D11_MESSAGE_ID hide [] = { D3D11_MESSAGE_ID_SETPRIVATEDATA_CHANGINGPARAMS, }; D3D11_INFO_QUEUE_FILTER filter = {}; filter.DenyList.NumIDs = _countof(hide); filter.DenyList.pIDList = hide; d3dInfoQueue->AddStorageFilterEntries(&filter); } } #endif ThrowIfFailed(device.As(&m_d3dDevice)); ThrowIfFailed(context.As(&m_d3dContext)); ThrowIfFailed(context.As(&m_d3dAnnotation)); } // Recreate all device resources and set them back to the current state. void DeviceResources::HandleDeviceLost() { m_swapChain.Reset(); m_d3dContext.Reset(); m_d3dAnnotation.Reset(); #ifdef _DEBUG { ComPtr d3dDebug; if (SUCCEEDED(m_d3dDevice.As(&d3dDebug))) { d3dDebug->ReportLiveDeviceObjects(D3D11_RLDO_SUMMARY); } } #endif m_d3dDevice.Reset(); CreateDeviceResources(); } // This method acquires the first available hardware adapter. // If no such adapter can be found, *ppAdapter will be set to nullptr. void DeviceResources::GetHardwareAdapter(IDXGIAdapter1** ppAdapter) { *ppAdapter = nullptr; ComPtr dxgiFactory; ThrowIfFailed(CreateDXGIFactory1(IID_PPV_ARGS(dxgiFactory.GetAddressOf()))); ComPtr adapter; for (UINT adapterIndex = 0; DXGI_ERROR_NOT_FOUND != dxgiFactory->EnumAdapters1(adapterIndex, adapter.ReleaseAndGetAddressOf()); adapterIndex++) { DXGI_ADAPTER_DESC1 desc; adapter->GetDesc1(&desc); if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) { // Don't select the Basic Render Driver adapter. continue; } #ifdef _DEBUG wchar_t buff[256] = {}; swprintf_s(buff, L"Direct3D Adapter (%u): VID:%04X, PID:%04X - %ls\n", adapterIndex, desc.VendorId, desc.DeviceId, desc.Description); OutputDebugStringW(buff); #endif break; } *ppAdapter = adapter.Detach(); } ================================================ FILE: glTF-Toolkit/src/GLBtoGLTF.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" #include "GLBtoGLTF.h" #include "GLTFSDK/ExtensionsKHR.h" using namespace Microsoft::glTF; using namespace Microsoft::glTF::Toolkit; static std::unordered_map s_gltfMimeTypes = { { MIMETYPE_PNG, FILE_EXT_PNG }, { MIMETYPE_JPEG, FILE_EXT_JPEG }, { "image/vnd-ms.dds", "dds" }, { "text/plain", "glsl" }, { "audio/wav", "wav" }, }; std::string GuessFileExtension(const std::string& mimeType) { auto itr = s_gltfMimeTypes.find(mimeType); if (itr != s_gltfMimeTypes.end()) { return itr->second; } return BUFFER_EXTENSION; } namespace { class StreamMock : public IStreamReader { public: StreamMock() : m_stream(std::make_shared(std::ios_base::app | std::ios_base::binary | std::ios_base::in | std::ios_base::out)) { } std::shared_ptr GetInputStream(const std::string&) const override { return m_stream; } private: std::shared_ptr m_stream; }; size_t GetGLBBufferChunkOffset(std::ifstream* input) { // get offset from beginning of glb binary to beginning of buffer chunk input->seekg(GLB2_HEADER_BYTE_SIZE, std::ios::beg); uint32_t length = 0; for (int i = 0; i < GLB_CHUNK_TYPE_SIZE*CHAR_BIT; i += CHAR_BIT) { uint8_t c = static_cast(input->get()); length |= ((uint16_t)c << i); } // 28 is total length of non-json blocks from start of glb blob // 28 = (GLB2_HEADER_BYTE_SIZE = 12bytes) + (uint32 = 4bytes) * 4 return length + GLB2_HEADER_BYTE_SIZE + GLB_CHUNK_TYPE_SIZE * 4; } } std::vector GLBToGLTF::SaveBin(std::istream* input, const Document& glbDoc, const size_t bufferOffset, const size_t newBufferlength, std::unordered_set& unpackedBufferViews) { if (newBufferlength == 0) { return {}; } const auto images = glbDoc.images.Elements(); const auto bufferViews = glbDoc.bufferViews.Elements(); // gather all non-image bufferViews in UsedBufferViews std::vector usedBufferViews(bufferViews.size()); auto end = copy_if(bufferViews.begin(), bufferViews.end(), usedBufferViews.begin(), [unpackedBufferViews](const auto& a) { return unpackedBufferViews.count(a.id) == 0; }); usedBufferViews.resize(distance(usedBufferViews.begin(), end)); // sort buffer views by offset sort(usedBufferViews.begin(), usedBufferViews.end(), [](const BufferView& a, const BufferView& b) { return a.byteOffset < b.byteOffset; }); std::vector result(newBufferlength); size_t vecpos = 0; // number of chunks read size_t currOffset = bufferOffset; // offset into buffer input->seekg(bufferOffset, std::ios::beg); for (const auto& bufferView : usedBufferViews) { // traverse through original buffer while grabbing non-image buffer segments size_t nextOffset = bufferOffset + bufferView.byteOffset; if (currOffset < nextOffset) { // skip over buffer segments of no interest size_t chunkLength = nextOffset - currOffset; input->seekg(chunkLength, std::ios::cur); currOffset += chunkLength; } if (vecpos % GLB_BUFFER_OFFSET_ALIGNMENT != 0) { // Alignment padding // Accessor component sizes can be 1, 2, 4. // Aligning to 4 will satisfy requirements but wastes space vecpos += (GLB_BUFFER_OFFSET_ALIGNMENT - (vecpos % GLB_BUFFER_OFFSET_ALIGNMENT)); } // read and increment vecpos + offset input->read(&result[vecpos], bufferView.byteLength); currOffset += bufferView.byteLength; vecpos += bufferView.byteLength; } if (vecpos == 0) { return {}; } return result; } std::unordered_map> GLBToGLTF::GetImagesData(std::istream* input, const Document& glbDoc, const std::string& name, const size_t bufferOffset) { input->seekg(0, std::ios::beg); std::unordered_map imageIDs; std::vector images = std::vector(glbDoc.images.Elements()); if (images.size() == 0) { return {}; } int imgId = 0; for (const auto& img : images) { // save mapping of original image order imageIDs[img.bufferViewId] = imgId; imgId++; } // sort images by buffer offset so only traverse once sort(images.begin(), images.end(), [glbDoc](const auto& a, const auto& b) { return glbDoc.bufferViews.Get(a.bufferViewId).byteOffset < glbDoc.bufferViews.Get(b.bufferViewId).byteOffset; }); size_t currOffset = bufferOffset; // offset into buffer input->seekg(bufferOffset, std::ios::beg); std::unordered_map> imageStream; for (const auto& img : images) { // traverse through buffer while saving images auto bufferView = glbDoc.bufferViews.Get(img.bufferViewId); size_t nextImageOffset = bufferOffset + bufferView.byteOffset; if (currOffset < nextImageOffset) { // skip over non-image buffer segments size_t chunkLength = nextImageOffset - currOffset; input->seekg(chunkLength, std::ios::cur); currOffset = nextImageOffset; } // read and increment offset std::vector result; result.resize(bufferView.byteLength); input->read(&result[0], bufferView.byteLength); currOffset += bufferView.byteLength; // write image file std::string outname = name + "_image" + std::to_string(imageIDs[img.bufferViewId]) + "." + GuessFileExtension(img.mimeType); imageStream[outname] = std::move(result); } return imageStream; } std::unordered_map> GetExtensionsData(std::istream* input, const Document& glbDoc, const std::string& name, const size_t bufferOffset) { std::unordered_map> extensionStreams; // Collect anything in extensions that looks like it should be unpacked. for (const auto& extension : glbDoc.extensions) { rapidjson::Document extensionJson; extensionJson.Parse(extension.second.c_str()); if (!extensionJson.IsObject()) { continue; } for (auto& member : extensionJson.GetObject()) { if (!member.value.IsArray()) { continue; } for (auto& possibleBuffer : member.value.GetArray()) { if (!possibleBuffer.IsObject()) { continue; } std::string bufferViewId{}; std::string mimeType{}; if (possibleBuffer.HasMember("bufferView")) { bufferViewId = std::to_string(possibleBuffer["bufferView"].GetUint()); } else { continue; } if (possibleBuffer.HasMember("mimeType")) { mimeType = possibleBuffer["mimeType"].GetString(); } try { auto bufferView = glbDoc.bufferViews.Get(bufferViewId); auto filename = name + "_" + extension.first + "_" + member.name.GetString() + "_" + bufferViewId + "." + GuessFileExtension(mimeType); size_t offset = bufferOffset + bufferView.byteOffset; input->seekg(offset, std::ios::beg); std::vector result; result.resize(bufferView.byteLength); input->read(&result[0], bufferView.byteLength); extensionStreams[filename] = std::move(result); } catch (...) { // Didn't work out. continue; } } } } return extensionStreams; } // Create modified gltf from original by removing image buffer segments and updating // images, bufferViews and accessors fields accordingly Document GLBToGLTF::CreateGLTFDocument(const Document& glbDoc, const std::string& name, std::unordered_set& unpackedBufferViews) { Document gltfDoc(glbDoc); gltfDoc.images.Clear(); gltfDoc.buffers.Clear(); gltfDoc.bufferViews.Clear(); gltfDoc.accessors.Clear(); const auto images = glbDoc.images.Elements(); const auto buffers = glbDoc.buffers.Elements(); const auto bufferViews = glbDoc.bufferViews.Elements(); const auto accessors = glbDoc.accessors.Elements(); std::unordered_map bufferViewIndex; size_t updatedBufferSize = 0; int imgId = 0; for (const auto& im : images) { // find which buffer segments correspond to images unpackedBufferViews.insert(im.bufferViewId); // update image fields with image names instead of buffer views Image updatedImage; updatedImage.id = std::to_string(imgId); updatedImage.uri = name + "_image" + std::to_string(imgId) + "." + GuessFileExtension(im.mimeType); gltfDoc.images.Append(std::move(updatedImage)); imgId++; } // Collect anything in extensions that looks like it should be unpacked. for (auto& extension : gltfDoc.extensions) { rapidjson::Document extensionJson; extensionJson.Parse(extension.second.c_str()); if (!extensionJson.IsObject()) { continue; } for (auto& member : extensionJson.GetObject()) { if (!member.value.IsArray()) { continue; } for (auto& possibleBuffer : member.value.GetArray()) { if (!possibleBuffer.IsObject()) { continue; } std::string bufferViewId{}; std::string mimeType{}; if (possibleBuffer.HasMember("bufferView")) { bufferViewId = std::to_string(possibleBuffer["bufferView"].GetUint()); unpackedBufferViews.insert(bufferViewId); } else { continue; } if (possibleBuffer.HasMember("mimeType")) { mimeType = possibleBuffer["mimeType"].GetString(); } try { possibleBuffer.RemoveMember("uri"); possibleBuffer.RemoveMember("mimeType"); possibleBuffer.RemoveMember("bufferView"); auto filename = name + "_" + extension.first + "_" + member.name.GetString() + "_" + bufferViewId + "." + GuessFileExtension(mimeType); possibleBuffer.AddMember("uri", rapidjson::Value(filename.c_str(), extensionJson.GetAllocator()), extensionJson.GetAllocator()); } catch (...) { // Didn't work out. continue; } } } rapidjson::StringBuffer buffer; rapidjson::Writer jsonWriter(buffer); extensionJson.Accept(jsonWriter); extension.second = buffer.GetString(); } // gather all non-image bufferViews in UsedBufferViews std::vector usedBufferViews(bufferViews.size()); auto end = copy_if(bufferViews.begin(), bufferViews.end(), usedBufferViews.begin(), [&unpackedBufferViews](const auto& a) { return unpackedBufferViews.count(a.id) == 0; }); usedBufferViews.resize(distance(usedBufferViews.begin(), end)); // group buffer views by buffer, then sort them by byteOffset to calculate their new byteOffsets sort(usedBufferViews.begin(), usedBufferViews.end(), [](const auto& a, const auto& b) { return a.byteOffset < b.byteOffset; }); int updatedBufferViewId = 0; size_t currentOffset = 0; for (const auto& b : usedBufferViews) { // provide new byte ranges for bufferviews size_t padding = 0; auto updatedBufferView = b; updatedBufferView.id = std::to_string(updatedBufferViewId); if (currentOffset % GLB_BUFFER_OFFSET_ALIGNMENT != 0) { // alignment padding as in SaveBin padding = (GLB_BUFFER_OFFSET_ALIGNMENT - (currentOffset % GLB_BUFFER_OFFSET_ALIGNMENT)); currentOffset += padding; } updatedBufferView.byteOffset = currentOffset; currentOffset += b.byteLength; gltfDoc.bufferViews.Append(std::move(updatedBufferView)); bufferViewIndex[b.id] = std::to_string(updatedBufferViewId); updatedBufferSize += (b.byteLength + padding); updatedBufferViewId++; } if (!buffers.empty()) { auto updatedBuffer = buffers[0]; updatedBuffer.byteLength = updatedBufferSize; updatedBuffer.uri = name + "." + BUFFER_EXTENSION; gltfDoc.buffers.Append(std::move(updatedBuffer)); } for (const auto& a : accessors) { if (unpackedBufferViews.find(a.bufferViewId) == unpackedBufferViews.end()) { // update acessors with new bufferview IDs, the above check may not be needed auto updatedAccessor = a; updatedAccessor.bufferViewId = bufferViewIndex[a.bufferViewId]; gltfDoc.accessors.Append(std::move(updatedAccessor)); } } bool changes = false; const auto meshes = glbDoc.meshes.Elements(); std::vector updatedMeshs; for (auto updatedMesh : meshes) { for (auto& primitive : updatedMesh.primitives) { if (primitive.HasExtension()) { auto& draco = primitive.GetExtension(); draco.bufferViewId = bufferViewIndex[draco.bufferViewId]; changes = true; } } updatedMeshs.emplace_back(updatedMesh); } if (changes) { for (const auto& mesh : updatedMeshs) { gltfDoc.meshes.Replace(mesh); } } return gltfDoc; } void GLBToGLTF::UnpackGLB(const std::string& glbPath, const std::string& outDirectory, const std::string& gltfName) { // read glb file into json auto glbStream = std::make_shared(glbPath, std::ios::binary); auto streamReader = std::make_shared(); GLBResourceReader reader(streamReader, glbStream); // get original json auto json = reader.GetJson(); auto doc = Deserialize(json, KHR::GetKHRExtensionDeserializer()); // create new modified json std::unordered_set unpackedBufferViews; auto gltfDoc = GLBToGLTF::CreateGLTFDocument(doc, gltfName, unpackedBufferViews); // serialize and write new gltf json auto gltfJson = Serialize(gltfDoc, KHR::GetKHRExtensionSerializer()); std::ofstream outputStream(outDirectory + gltfName + "." + GLTF_EXTENSION); outputStream << gltfJson; outputStream.flush(); // write images size_t bufferOffset = GetGLBBufferChunkOffset(glbStream.get()); for (auto image : GLBToGLTF::GetImagesData(glbStream.get(), doc, gltfName, bufferOffset)) { std::ofstream out(outDirectory + image.first, std::ios::binary); out.write(&image.second[0], image.second.size()); } for (auto ext : GetExtensionsData(glbStream.get(), doc, gltfName, bufferOffset)) { std::ofstream out(outDirectory + ext.first, std::ios::binary); out.write(&ext.second[0], ext.second.size()); } // get new buffer size and write new buffer if (gltfDoc.buffers.Size() != 0) { size_t newBufferSize = gltfDoc.buffers[0].byteLength; auto binFileData = GLBToGLTF::SaveBin(glbStream.get(), doc, bufferOffset, newBufferSize, unpackedBufferViews); std::ofstream out(outDirectory + gltfName + "." + BUFFER_EXTENSION, std::ios::binary); out.write(&binFileData[0], binFileData.size()); } } ================================================ FILE: glTF-Toolkit/src/GLTFLODUtils.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" #include "GLTFTextureCompressionUtils.h" #include "GLTFTexturePackingUtils.h" #include "GLTFLODUtils.h" #include "GLTFSDK/GLTF.h" #include "GLTFSDK/Constants.h" #include "GLTFSDK/Deserialize.h" #include "GLTFSDK/RapidJsonUtils.h" #include "GLTFSDK/ExtensionsKHR.h" #include #include #include #include #include using namespace Microsoft::glTF; using namespace Microsoft::glTF::Toolkit; const char* Microsoft::glTF::Toolkit::EXTENSION_MSFT_LOD = "MSFT_lod"; const char* Microsoft::glTF::Toolkit::MSFT_LOD_IDS_KEY = "ids"; namespace { inline void AddIndexOffset(std::string& id, size_t offset) { // an empty id string indicates that the id is not inuse and therefore should not be updated id = (id.empty()) ? "" : std::to_string(std::stoi(id) + offset); } inline void AddIndexOffset(MeshPrimitive& primitive, const char* attributeName, size_t offset) { // an empty id string indicates that the id is not inuse and therefore should not be updated auto attributeItr = primitive.attributes.find(attributeName); if (attributeItr != primitive.attributes.end()) { attributeItr->second = std::to_string(std::stoi(attributeItr->second) + offset); } } inline void AddIndexOffsetPacked(rapidjson::Value& json, const char* textureId, size_t offset) { if (json.HasMember(textureId)) { if (json[textureId].HasMember(MSFT_PACKING_INDEX_KEY)) { auto index = json[textureId][MSFT_PACKING_INDEX_KEY].GetInt(); json[textureId][MSFT_PACKING_INDEX_KEY] = index + offset; } } } std::vector ParseExtensionMSFTLod(const Node& node) { std::vector lodIds; auto lodExtension = node.extensions.find(Toolkit::EXTENSION_MSFT_LOD); if (lodExtension != node.extensions.end()) { auto json = RapidJsonUtils::CreateDocumentFromString(lodExtension->second); auto idIt = json.FindMember(Toolkit::MSFT_LOD_IDS_KEY); if (idIt != json.MemberEnd()) { for (rapidjson::Value::ConstValueIterator ait = idIt->value.Begin(); ait != idIt->value.End(); ++ait) { lodIds.push_back(std::to_string(ait->GetInt())); } } } return lodIds; } template std::string SerializeExtensionMSFTLod(const T&, const std::vector& lods, const Document& document) { // Omit MSFT_lod entirely if no LODs are available if (lods.empty()) { return std::string(); } rapidjson::Document doc(rapidjson::kObjectType); rapidjson::Document::AllocatorType& a = doc.GetAllocator(); std::vector lodIndices; lodIndices.reserve(lods.size()); if (std::is_same()) { for (const auto& lodId : lods) { lodIndices.push_back(ToKnownSizeType(document.materials.GetIndex(lodId))); } } else if (std::is_same()) { for (const auto& lodId : lods) { lodIndices.push_back(ToKnownSizeType(document.nodes.GetIndex(lodId))); } } else { throw GLTFException("LODs can only be applied to materials or nodes."); } doc.AddMember(RapidJsonUtils::ToStringValue(Toolkit::MSFT_LOD_IDS_KEY, a), RapidJsonUtils::ToJsonArray(lodIndices, a), a); rapidjson::StringBuffer stringBuffer; rapidjson::Writer writer(stringBuffer); doc.Accept(writer); return stringBuffer.GetString(); } Document AddGLTFNodeLOD(const Document& primary, LODMap& primaryLods, const Document& lod, const std::wstring& relativePath = L"", bool sharedMaterials = false) { Microsoft::glTF::Document gltfLod(primary); auto primaryScenes = primary.scenes.Elements(); auto lodScenes = lod.scenes.Elements(); size_t MaxLODLevel = 0; // Both GLTF must have equivalent number and order of scenes and root nodes per scene otherwise merge will not be possible bool sceneNodeMatch = false; if (primaryScenes.size() == lodScenes.size()) { for (size_t sceneIdx = 0; sceneIdx < primaryScenes.size(); sceneIdx++) { if ((primaryScenes[sceneIdx].nodes.size() == lodScenes[sceneIdx].nodes.size()) && (lodScenes[sceneIdx].nodes.size() == 1 || std::equal(primaryScenes[sceneIdx].nodes.begin(), primaryScenes[sceneIdx].nodes.end(), lodScenes[sceneIdx].nodes.begin())) ) { sceneNodeMatch = true; auto primaryRootNode = gltfLod.nodes.Get(primaryScenes[sceneIdx].nodes[0]); MaxLODLevel = std::max(MaxLODLevel, primaryLods.at(primaryRootNode.id)->size()); } else { sceneNodeMatch = false; break; } } } MaxLODLevel++; if (!sceneNodeMatch || primaryScenes.empty()) { // Mis-match or empty scene; either way cannot merge Lod in throw new std::runtime_error("Primary Scene either empty or does not match scene node count of LOD gltf"); } std::string nodeLodLabel = "_lod" + std::to_string(MaxLODLevel); // lod merge is performed from the lowest reference back upwards // e.g. buffers/samplers/extensions do not reference any other part of the gltf manifest size_t buffersOffset = gltfLod.buffers.Size(); size_t samplersOffset = sharedMaterials ? 0 : gltfLod.samplers.Size(); { auto lodBuffers = lod.buffers.Elements(); for (auto buffer : lodBuffers) { AddIndexOffset(buffer.id, buffersOffset); std::string relativePathUtf8 = std::wstring_convert>().to_bytes(relativePath); buffer.uri = relativePathUtf8 + buffer.uri; gltfLod.buffers.Append(std::move(buffer)); } if (!sharedMaterials) { auto lodSamplers = lod.samplers.Elements(); for (auto sampler : lodSamplers) { AddIndexOffset(sampler.id, samplersOffset); gltfLod.samplers.Append(std::move(sampler)); } } for (const auto& extension : lod.extensionsUsed) { gltfLod.extensionsUsed.insert(extension); } // ensure that MSFT_LOD extension is specified as being used gltfLod.extensionsUsed.insert(Toolkit::EXTENSION_MSFT_LOD); } size_t accessorOffset = gltfLod.accessors.Size(); size_t texturesOffset = gltfLod.textures.Size(); { // Buffer Views depend upon Buffers size_t bufferViewsOffset = gltfLod.bufferViews.Size(); auto lodBufferViews = lod.bufferViews.Elements(); for (auto bufferView : lodBufferViews) { AddIndexOffset(bufferView.id, bufferViewsOffset); AddIndexOffset(bufferView.bufferId, buffersOffset); gltfLod.bufferViews.Append(std::move(bufferView)); } // Accessors depend upon Buffer views auto lodAccessors = lod.accessors.Elements(); for (auto accessor : lodAccessors) { AddIndexOffset(accessor.id, accessorOffset); AddIndexOffset(accessor.bufferViewId, bufferViewsOffset); gltfLod.accessors.Append(std::move(accessor)); } // Images depend upon Buffer views size_t imageOffset = sharedMaterials ? 0 : gltfLod.images.Size(); if (!sharedMaterials) { auto lodImages = lod.images.Elements(); for (auto image : lodImages) { AddIndexOffset(image.id, imageOffset); AddIndexOffset(image.bufferViewId, bufferViewsOffset); std::wstring_convert> conv; std::wstring uri = conv.from_bytes(image.uri); if (std::experimental::filesystem::path(uri).is_relative()) { // to be able to reference images with the same name, prefix with relative path std::string relativePathUtf8 = conv.to_bytes(relativePath); image.uri = relativePathUtf8 + image.uri; } gltfLod.images.Append(std::move(image)); } // Textures depend upon Samplers and Images auto lodTextures = lod.textures.Elements(); for (auto texture : lodTextures) { AddIndexOffset(texture.id, texturesOffset); AddIndexOffset(texture.samplerId, samplersOffset); AddIndexOffset(texture.imageId, imageOffset); // MSFT_texture_dds extension auto ddsExtensionIt = texture.extensions.find(EXTENSION_MSFT_TEXTURE_DDS); if (ddsExtensionIt != texture.extensions.end() && !ddsExtensionIt->second.empty()) { rapidjson::Document ddsJson = RapidJsonUtils::CreateDocumentFromString(ddsExtensionIt->second); if (ddsJson.HasMember("source")) { auto index = ddsJson["source"].GetInt(); ddsJson["source"] = index + imageOffset; } rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); ddsJson.Accept(writer); ddsExtensionIt->second = buffer.GetString(); } gltfLod.textures.Append(std::move(texture)); } } } // Material Merge // Note the extension KHR_materials_pbrSpecularGlossiness will be also updated // Materials depend upon textures size_t materialOffset = sharedMaterials ? 0 : gltfLod.materials.Size(); if (!sharedMaterials) { auto lodMaterials = lod.materials.Elements(); for (auto material : lodMaterials) { // post-fix with lod level indication; // no functional reason other than making it easier to natively read gltf files with lods material.name += nodeLodLabel; AddIndexOffset(material.id, materialOffset); AddIndexOffset(material.normalTexture.textureId, texturesOffset); AddIndexOffset(material.occlusionTexture.textureId, texturesOffset); AddIndexOffset(material.emissiveTexture.textureId, texturesOffset); AddIndexOffset(material.metallicRoughness.baseColorTexture.textureId, texturesOffset); AddIndexOffset(material.metallicRoughness.metallicRoughnessTexture.textureId, texturesOffset); if (material.HasExtension()) { AddIndexOffset(material.GetExtension().diffuseTexture.textureId, texturesOffset); AddIndexOffset(material.GetExtension().specularGlossinessTexture.textureId, texturesOffset); } // MSFT_packing_occlusionRoughnessMetallic packed textures auto ormExtensionIt = material.extensions.find(EXTENSION_MSFT_PACKING_ORM); if (ormExtensionIt != material.extensions.end() && !ormExtensionIt->second.empty()) { rapidjson::Document ormJson = RapidJsonUtils::CreateDocumentFromString(ormExtensionIt->second); AddIndexOffsetPacked(ormJson, MSFT_PACKING_ORM_ORMTEXTURE_KEY, texturesOffset); AddIndexOffsetPacked(ormJson, MSFT_PACKING_ORM_RMOTEXTURE_KEY, texturesOffset); AddIndexOffsetPacked(ormJson, MSFT_PACKING_ORM_NORMALTEXTURE_KEY, texturesOffset); rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); ormJson.Accept(writer); ormExtensionIt->second = buffer.GetString(); } // MSFT_packing_normalRoughnessMetallic packed texture auto nrmExtensionIt = material.extensions.find(EXTENSION_MSFT_PACKING_NRM); if (nrmExtensionIt != material.extensions.end() && !nrmExtensionIt->second.empty()) { rapidjson::Document nrmJson = RapidJsonUtils::CreateDocumentFromString(nrmExtensionIt->second); AddIndexOffsetPacked(nrmJson, MSFT_PACKING_NRM_KEY, texturesOffset); rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); nrmJson.Accept(writer); nrmExtensionIt->second = buffer.GetString(); } gltfLod.materials.Append(std::move(material)); } } // Meshs depend upon Accessors and Materials size_t meshOffset = gltfLod.meshes.Size(); { auto lodMeshes = lod.meshes.Elements(); for (auto mesh : lodMeshes) { // post-fix with lod level indication; // no functional reason other than making it easier to natively read gltf files with lods mesh.name += nodeLodLabel; AddIndexOffset(mesh.id, meshOffset); for (auto &primitive : mesh.primitives) { AddIndexOffset(primitive.indicesAccessorId, accessorOffset); AddIndexOffset(primitive, ACCESSOR_POSITION, accessorOffset); AddIndexOffset(primitive, ACCESSOR_NORMAL, accessorOffset); AddIndexOffset(primitive, ACCESSOR_TEXCOORD_0, accessorOffset); AddIndexOffset(primitive, ACCESSOR_TEXCOORD_1, accessorOffset); AddIndexOffset(primitive, ACCESSOR_COLOR_0, accessorOffset); AddIndexOffset(primitive, ACCESSOR_TANGENT, accessorOffset); AddIndexOffset(primitive, ACCESSOR_JOINTS_0, accessorOffset); AddIndexOffset(primitive, ACCESSOR_WEIGHTS_0, accessorOffset); if (sharedMaterials) { // lower quality LODs can have fewer images and textures than the highest LOD, // so we need to find the correct material index for the same material from the highest LOD const Material& localMaterial = lod.materials.Get(primitive.materialId); // find merged material index for the given material index in this LOD auto iter = std::find_if(gltfLod.materials.Elements().begin(), gltfLod.materials.Elements().end(), [localMaterial](const Material& globalMaterial) { // check that the materials are the same, noting that the texture and material ids will differ return localMaterial.name == globalMaterial.name && localMaterial.alphaMode == globalMaterial.alphaMode && localMaterial.alphaCutoff == globalMaterial.alphaCutoff && localMaterial.emissiveFactor == globalMaterial.emissiveFactor && localMaterial.doubleSided == globalMaterial.doubleSided && localMaterial.metallicRoughness.baseColorFactor == globalMaterial.metallicRoughness.baseColorFactor && localMaterial.metallicRoughness.metallicFactor == globalMaterial.metallicRoughness.metallicFactor && localMaterial.occlusionTexture.strength == globalMaterial.occlusionTexture.strength && localMaterial.HasExtension() == globalMaterial.HasExtension() && (!localMaterial.HasExtension() || (localMaterial.GetExtension().diffuseFactor == globalMaterial.GetExtension().diffuseFactor && localMaterial.GetExtension().glossinessFactor == globalMaterial.GetExtension().glossinessFactor && localMaterial.GetExtension().specularFactor == globalMaterial.GetExtension().specularFactor) ); } ); if (iter != gltfLod.materials.Elements().end()) { size_t newMaterialIndex = std::distance(gltfLod.materials.Elements().begin(), iter); primitive.materialId = std::to_string(newMaterialIndex); } else { throw new std::runtime_error("Couldn't find the shared material in the highest LOD."); } } else { AddIndexOffset(primitive.materialId, materialOffset); } } gltfLod.meshes.Append(std::move(mesh)); } } // Nodes depend upon Nodes and Meshes size_t nodeOffset = gltfLod.nodes.Size(); // Skins depend upon Nodes size_t skinOffset = gltfLod.skins.Size(); { auto nodes = lod.nodes.Elements(); for (auto node : nodes) { // post-fix with lod level indication; // no functional reason other than making it easier to natively read gltf files with lods node.name += nodeLodLabel; AddIndexOffset(node.id, nodeOffset); AddIndexOffset(node.meshId, meshOffset); if (!node.skinId.empty()) { AddIndexOffset(node.skinId, skinOffset); } for (auto &child : node.children) { AddIndexOffset(child, nodeOffset); } gltfLod.nodes.Append(std::move(node)); } } { auto skins = lod.skins.Elements(); for (auto skin : skins) { // post-fix with lod level indication; // no functional reason other than making it easier to natively read gltf files with lods skin.name += nodeLodLabel; AddIndexOffset(skin.id, skinOffset); AddIndexOffset(skin.skeletonId, nodeOffset); AddIndexOffset(skin.inverseBindMatricesAccessorId, accessorOffset); for (auto &jointId : skin.jointIds) { AddIndexOffset(jointId, nodeOffset); } gltfLod.skins.Append(std::move(skin)); } } // Animation channels depend upon Nodes and Accessors { for (size_t animationIndex = 0; animationIndex < gltfLod.animations.Size(); animationIndex++) { const auto &baseAnimation = gltfLod.animations[animationIndex]; Animation newAnimation(baseAnimation); const auto &lodAnimation = lod.animations[animationIndex]; size_t samplerOffset = baseAnimation.samplers.Size(); for (const auto &sampler : lodAnimation.samplers.Elements()) { AnimationSampler newSampler(sampler); AddIndexOffset(newSampler.id, samplerOffset); AddIndexOffset(newSampler.inputAccessorId, accessorOffset); AddIndexOffset(newSampler.outputAccessorId, accessorOffset); newAnimation.samplers.Append(std::move(newSampler)); } size_t channelOffset = baseAnimation.channels.Size(); for (auto channel : lodAnimation.channels.Elements()) { AddIndexOffset(channel.id, channelOffset); AddIndexOffset(channel.target.nodeId, nodeOffset); AddIndexOffset(channel.samplerId, samplerOffset); newAnimation.channels.Append(std::move(channel)); } gltfLod.animations.Replace(newAnimation); } } // update the primary GLTF root nodes lod extension to reference the new lod root node // N.B. new lods are always added to the back for (size_t sceneIdx = 0; sceneIdx < primaryScenes.size(); sceneIdx++) { for (size_t rootNodeIdx = 0; rootNodeIdx < primaryScenes[sceneIdx].nodes.size(); rootNodeIdx++) { auto idx = primaryScenes[sceneIdx].nodes[rootNodeIdx]; Node nodeWithLods(gltfLod.nodes.Get(idx)); int lodRootIdx = std::stoi(lodScenes[sceneIdx].nodes[rootNodeIdx]) + static_cast(nodeOffset); auto primaryNodeLod = primaryLods.at(nodeWithLods.id); primaryNodeLod->emplace_back(std::to_string(lodRootIdx)); } } return gltfLod; } } LODMap GLTFLODUtils::ParseDocumentNodeLODs(const Document& doc) { LODMap lodMap; for (auto node : doc.nodes.Elements()) { lodMap.emplace(node.id, std::move(std::make_shared>(ParseExtensionMSFTLod(node)))); } return lodMap; } Document GLTFLODUtils::MergeDocumentsAsLODs(const std::vector& docs, const std::vector& relativePaths, const bool& sharedMaterials) { if (docs.empty()) { throw std::invalid_argument("MergeDocumentsAsLODs passed empty vector"); } Document gltfPrimary(docs[0]); LODMap lods = ParseDocumentNodeLODs(gltfPrimary); for (size_t i = 1; i < docs.size(); i++) { gltfPrimary = AddGLTFNodeLOD(gltfPrimary, lods, docs[i], (relativePaths.size() == docs.size() - 1 ? relativePaths[i - 1] : L""), sharedMaterials); } for (auto lod : lods) { if (lod.second == nullptr || lod.second->size() == 0) { continue; } auto node = gltfPrimary.nodes.Get(lod.first); auto lodExtensionValue = SerializeExtensionMSFTLod(node, *lod.second, gltfPrimary); if (!lodExtensionValue.empty()) { node.extensions.emplace(EXTENSION_MSFT_LOD, lodExtensionValue); gltfPrimary.nodes.Replace(node); } } return gltfPrimary; } Document GLTFLODUtils::MergeDocumentsAsLODs(const std::vector& docs, const std::vector& screenCoveragePercentages, const std::vector& relativePaths, const bool& sharedMaterials) { Document merged = MergeDocumentsAsLODs(docs, relativePaths, sharedMaterials); if (screenCoveragePercentages.size() == 0) { return merged; } for (auto scene : merged.scenes.Elements()) { for (auto rootNodeIndex : scene.nodes) { auto primaryRootNode = merged.nodes.Get(rootNodeIndex); rapidjson::Document extrasJson(rapidjson::kObjectType); if (!primaryRootNode.extras.empty()) { extrasJson.Parse(primaryRootNode.extras.c_str()); } rapidjson::Document::AllocatorType& allocator = extrasJson.GetAllocator(); rapidjson::Value screenCoverageArray = RapidJsonUtils::ToJsonArray(screenCoveragePercentages, allocator); extrasJson.AddMember("MSFT_screencoverage", screenCoverageArray, allocator); rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); extrasJson.Accept(writer); primaryRootNode.extras = buffer.GetString(); merged.nodes.Replace(primaryRootNode); } } return merged; } uint32_t GLTFLODUtils::NumberOfNodeLODLevels(const Document& doc, const LODMap& lods) { size_t maxLODLevel = 0; for (auto node : doc.nodes.Elements()) { maxLODLevel = std::max(maxLODLevel, lods.at(node.id)->size()); } return static_cast(maxLODLevel); } ================================================ FILE: glTF-Toolkit/src/GLTFMeshCompressionUtils.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" #include "AccessorUtils.h" #include "GLTFMeshCompressionUtils.h" #include "GLTFSDK/MeshPrimitiveUtils.h" #include "GLTFSDK/ExtensionsKHR.h" #include "GLTFSDK/BufferBuilder.h" #pragma warning(push) #pragma warning(disable: 4018 4081 4244 4267 4389) #include "draco/compression/encode.h" #include "draco/core/cycle_timer.h" #include "draco/io/mesh_io.h" #include "draco/io/point_cloud_io.h" #pragma warning(pop) // Usings for glTF using namespace Microsoft::glTF; using namespace Microsoft::glTF::Toolkit; std::wstring PathConcat(const std::wstring& part1, const std::wstring& part2) { wchar_t uriAbsoluteRaw[MAX_PATH]; // Note: PathCchCombine will return the last argument if it's an absolute path if (FAILED(::PathCchCombine(uriAbsoluteRaw, ARRAYSIZE(uriAbsoluteRaw), part1.c_str(), part2.c_str()))) { auto msg = L"Could not combine the path names: " + part1 + L" and " + part2; throw std::invalid_argument(std::string(msg.begin(), msg.end())); } return uriAbsoluteRaw; } std::string PathConcat(const std::string& part1, const std::string& part2) { std::wstring part1W = std::wstring(part1.begin(), part1.end()); std::wstring part2W = std::wstring(part2.begin(), part2.end()); auto pathW = PathConcat(part1W, part2W); return std::string(pathW.begin(), pathW.end()); } class FilepathStreamWriter : public IStreamWriter { public: FilepathStreamWriter(std::string uriBase) : m_uriBase(uriBase) {} virtual ~FilepathStreamWriter() override {} virtual std::shared_ptr GetOutputStream(const std::string& filename) const override { return std::make_shared(PathConcat(m_uriBase, filename), std::ios::binary); } private: const std::string m_uriBase; }; draco::GeometryAttribute::Type GetTypeFromAttributeName(const std::string& name) { if (name == ACCESSOR_POSITION) { return draco::GeometryAttribute::Type::POSITION; } if (name == ACCESSOR_NORMAL) { return draco::GeometryAttribute::Type::NORMAL; } if (name == ACCESSOR_TEXCOORD_0) { return draco::GeometryAttribute::Type::TEX_COORD; } if (name == ACCESSOR_TEXCOORD_1) { return draco::GeometryAttribute::Type::TEX_COORD; } if (name == ACCESSOR_COLOR_0) { return draco::GeometryAttribute::Type::COLOR; } if (name == ACCESSOR_JOINTS_0) { return draco::GeometryAttribute::Type::GENERIC; } if (name == ACCESSOR_WEIGHTS_0) { return draco::GeometryAttribute::Type::GENERIC; } if (name == ACCESSOR_TANGENT) { return draco::GeometryAttribute::Type::GENERIC; } return draco::GeometryAttribute::Type::GENERIC; } draco::DataType GetDataType(const Accessor& accessor) { switch (accessor.componentType) { case COMPONENT_BYTE: return draco::DataType::DT_INT8; case COMPONENT_UNSIGNED_BYTE: return draco::DataType::DT_UINT8; case COMPONENT_SHORT: return draco::DataType::DT_INT16; case COMPONENT_UNSIGNED_SHORT: return draco::DataType::DT_UINT16; case COMPONENT_UNSIGNED_INT: return draco::DataType::DT_UINT32; case COMPONENT_FLOAT: return draco::DataType::DT_FLOAT32; } return draco::DataType::DT_INVALID; } template int InitializePointAttribute(draco::Mesh& dracoMesh, const std::string& attributeName, const Document& doc, GLTFResourceReader& reader, Accessor& accessor) { auto stride = sizeof(T) * Accessor::GetTypeCount(accessor.type); auto numComponents = Accessor::GetTypeCount(accessor.type); draco::PointAttribute pointAttr; pointAttr.Init(GetTypeFromAttributeName(attributeName), nullptr, numComponents, GetDataType(accessor), accessor.normalized, stride, 0); int attId = dracoMesh.AddAttribute(pointAttr, true, static_cast(accessor.count)); auto attrActual = dracoMesh.attribute(attId); std::vector values = reader.ReadBinaryData(doc, accessor); if ((accessor.min.empty() || accessor.max.empty()) && !values.empty()) { auto minmax = AccessorUtils::CalculateMinMax(accessor, values); accessor.min = minmax.first; accessor.max = minmax.second; } for (draco::PointIndex i(0); i < static_cast(accessor.count); ++i) { attrActual->SetAttributeValue(attrActual->mapped_index(i), &values[i.value() * numComponents]); } if (dracoMesh.num_points() == 0) { dracoMesh.set_num_points(static_cast(accessor.count)); } else if (dracoMesh.num_points() != accessor.count) { throw GLTFException("Inconsistent points count."); } return attId; } void SetEncoderOptions(draco::Encoder& encoder, const CompressionOptions& options) { encoder.SetAttributeQuantization(draco::GeometryAttribute::POSITION, options.PositionQuantizationBits); encoder.SetAttributeQuantization(draco::GeometryAttribute::TEX_COORD, options.TexCoordQuantizationBits); encoder.SetAttributeQuantization(draco::GeometryAttribute::NORMAL, options.NormalQuantizationBits); encoder.SetAttributeQuantization(draco::GeometryAttribute::COLOR, options.ColorQuantizationBits); encoder.SetAttributeQuantization(draco::GeometryAttribute::GENERIC, options.GenericQuantizationBits); encoder.SetSpeedOptions(options.Speed, options.Speed); encoder.SetTrackEncodedProperties(true); } Document GLTFMeshCompressionUtils::CompressMesh( std::shared_ptr streamReader, const Document & doc, CompressionOptions options, const Mesh & mesh, BufferBuilder* builder, std::unordered_set& bufferViewsToRemove) { GLTFResourceReader reader(streamReader); Document resultDocument(doc); draco::Encoder encoder; SetEncoderOptions(encoder, options); Mesh resultMesh(mesh); resultMesh.primitives.clear(); for (const auto& primitive : mesh.primitives) { if (primitive.HasExtension()) { resultMesh.primitives.emplace_back(primitive); continue; } auto dracoExtension = std::make_unique(); draco::Mesh dracoMesh; auto indices = MeshPrimitiveUtils::GetIndices32(doc, reader, primitive); size_t numFaces = indices.size() / 3; dracoMesh.SetNumFaces(numFaces); for (uint32_t i = 0; i < numFaces; i++) { draco::Mesh::Face face; face[0] = indices[(i * 3) + 0]; face[1] = indices[(i * 3) + 1]; face[2] = indices[(i * 3) + 2]; dracoMesh.SetFace(draco::FaceIndex(i), face); } Accessor indiciesAccessor(doc.accessors[primitive.indicesAccessorId]); bufferViewsToRemove.emplace(indiciesAccessor.bufferViewId); indiciesAccessor.bufferViewId = ""; indiciesAccessor.byteOffset = 0; resultDocument.accessors.Replace(indiciesAccessor); for (const auto& attribute : primitive.attributes) { const auto& accessor = doc.accessors[attribute.second]; Accessor attributeAccessor(accessor); int attId; switch (accessor.componentType) { case COMPONENT_BYTE: attId = InitializePointAttribute(dracoMesh, attribute.first, doc, reader, attributeAccessor); break; case COMPONENT_UNSIGNED_BYTE: attId = InitializePointAttribute(dracoMesh, attribute.first, doc, reader, attributeAccessor); break; case COMPONENT_SHORT: attId = InitializePointAttribute(dracoMesh, attribute.first, doc, reader, attributeAccessor); break; case COMPONENT_UNSIGNED_SHORT: attId = InitializePointAttribute(dracoMesh, attribute.first, doc, reader, attributeAccessor); break; case COMPONENT_UNSIGNED_INT: attId = InitializePointAttribute(dracoMesh, attribute.first, doc, reader, attributeAccessor); break; case COMPONENT_FLOAT: attId = InitializePointAttribute(dracoMesh, attribute.first, doc, reader, attributeAccessor); break; default: throw GLTFException("Unknown component type."); } bufferViewsToRemove.emplace(accessor.bufferViewId); attributeAccessor.bufferViewId = ""; attributeAccessor.byteOffset = 0; resultDocument.accessors.Replace(attributeAccessor); dracoExtension->attributes.emplace(attribute.first, dracoMesh.attribute(attId)->unique_id()); } if (primitive.targets.size() > 0) { // Set sequential encoding to preserve order of vertices. encoder.SetEncodingMethod(draco::MESH_SEQUENTIAL_ENCODING); } dracoMesh.DeduplicateAttributeValues(); dracoMesh.DeduplicatePointIds(); draco::EncoderBuffer buffer; const draco::Status status = encoder.EncodeMeshToBuffer(dracoMesh, &buffer); if (!status.ok()) { throw GLTFException(std::string("Failed to encode the mesh: ") + status.error_msg()); } // We must update the original accessors to the encoding out values. Accessor encodedIndexAccessor(resultDocument.accessors[primitive.indicesAccessorId]); encodedIndexAccessor.count = encoder.num_encoded_faces() * 3; resultDocument.accessors.Replace(encodedIndexAccessor); for (const auto& dracoAttribute : dracoExtension->attributes) { auto accessorId = primitive.attributes.at(dracoAttribute.first); Accessor encodedAccessor(resultDocument.accessors[accessorId]); encodedAccessor.count = encoder.num_encoded_points(); resultDocument.accessors.Replace(encodedAccessor); } // Finally put the encoded data in place. auto bufferView = builder->AddBufferView(buffer.data(), buffer.size()); dracoExtension->bufferViewId = bufferView.id; MeshPrimitive resultPrim(primitive); resultPrim.SetExtension(std::move(dracoExtension)); resultMesh.primitives.emplace_back(resultPrim); } resultDocument.meshes.Replace(resultMesh); return resultDocument; } Document GLTFMeshCompressionUtils::CompressMeshes(std::shared_ptr streamReader, const Document & doc, CompressionOptions options, const std::string& outputDirectory) { Document resultDocument(doc); auto writerStream = std::make_shared(outputDirectory); auto writer = std::make_unique(writerStream); writer->SetUriPrefix(PathConcat(outputDirectory, "MeshCompression")); std::unique_ptr builder = std::make_unique(std::move(writer), [&doc](const BufferBuilder& builder) { return std::to_string(doc.buffers.Size() + builder.GetBufferCount()); }, [&doc](const BufferBuilder& builder) { return std::to_string(doc.bufferViews.Size() + builder.GetBufferViewCount()); }, [&doc](const BufferBuilder& builder) { return std::to_string(doc.accessors.Size() + builder.GetAccessorCount()); }); auto buffer = builder->AddBuffer(); std::unordered_set bufferViewsToRemove; for (const auto& mesh : doc.meshes.Elements()) { resultDocument = CompressMesh(streamReader, resultDocument, options, mesh, builder.get(), bufferViewsToRemove); } for (const auto& bufferViewId : bufferViewsToRemove) { if (resultDocument.bufferViews.Has(bufferViewId)) { resultDocument.bufferViews.Remove(bufferViewId); } } builder->Output(resultDocument); resultDocument.extensionsUsed.emplace(KHR::MeshPrimitives::DRACOMESHCOMPRESSION_NAME); resultDocument.extensionsRequired.emplace(KHR::MeshPrimitives::DRACOMESHCOMPRESSION_NAME); return resultDocument; } ================================================ FILE: glTF-Toolkit/src/GLTFSpecularGlossinessUtils.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" #include "GLTFTextureUtils.h" #include "GLTFSDK/ExtensionsKHR.h" #include "GLTFSDK/PBRUtils.h" #include "GLTFSpecularGlossinessUtils.h" // Usings for glTF using namespace Microsoft::glTF; using namespace Toolkit; using namespace DirectX; void ConvertEntrySpecularGlossinessToMetallicRoughness( const XMVECTORF32& diffuseColor, const XMVECTORF32& specGloss, XMVECTORF32& diffuseOut, float& metallicOut, float& roughnessOut) { SpecularGlossinessValue sg; sg.diffuse = Color3(diffuseColor[0], diffuseColor[1], diffuseColor[2]); sg.opacity = diffuseColor[3]; sg.specular = Color3(specGloss[0], specGloss[1], specGloss[2]); sg.glossiness = specGloss[3]; MetallicRoughnessValue mr = SGToMR(sg); roughnessOut = mr.roughness; metallicOut = mr.metallic; diffuseOut = { mr.base.r, mr.base.g, mr.base.b, mr.opacity }; } void ConvertTextureSpecularGlossinessToMetallicRoughness( ScratchImage& out_metallicRoughnessTexture, ScratchImage& out_modulatedDiffuseTexture, const std::unique_ptr& diffuseTexture, const XMVECTORF32& diffuseFactor, const std::unique_ptr& specularGlossinessTexture, const XMVECTORF32& specularFactor) { size_t targetWidth = 4; size_t targetHeight = 4; uint8_t* diffusePixels = nullptr; if (diffuseTexture != nullptr) { targetWidth = diffuseTexture->GetMetadata().width; targetHeight = diffuseTexture->GetMetadata().height; diffusePixels = diffuseTexture->GetPixels(); } else if (specularGlossinessTexture != nullptr) { targetWidth = specularGlossinessTexture->GetMetadata().width; targetHeight = specularGlossinessTexture->GetMetadata().height; } uint8_t* specGlossPixels = nullptr; if (specularGlossinessTexture) { GLTFTextureUtils::ResizeIfNeeded(specularGlossinessTexture, targetWidth, targetHeight); specGlossPixels = specularGlossinessTexture->GetPixels(); } out_modulatedDiffuseTexture.Initialize2D(DXGI_FORMAT_R32G32B32A32_FLOAT, targetWidth, targetHeight, 1, 1); out_metallicRoughnessTexture.Initialize2D(DXGI_FORMAT_R32G32B32A32_FLOAT, targetWidth, targetHeight, 1, 1); auto diffuseOutPixels = out_modulatedDiffuseTexture.GetPixels(); auto metalRoughPixels = out_metallicRoughnessTexture.GetPixels(); for (uint32_t i = 0; i < targetHeight * targetWidth; ++i) { XMVECTORF32 diffuseColor { 1.0f, 1.0f, 1.0f, 1.0f }; if (diffusePixels != nullptr) { memcpy_s(&diffuseColor, 16, GLTFTextureUtils::GetChannelValue(diffusePixels, i, Red), 16); } diffuseColor.v = diffuseColor * diffuseFactor; XMVECTORF32 specGloss { 1.0f, 1.0f, 1.0f, 1.0f }; if (specularGlossinessTexture != nullptr) { memcpy_s(&specGloss, 16, GLTFTextureUtils::GetChannelValue(specGlossPixels, i, Red), 16); } specGloss.v = specGloss * specularFactor; float metallic; float roughness; XMVECTORF32 diffuseColorOut; ConvertEntrySpecularGlossinessToMetallicRoughness(diffuseColor, specGloss, diffuseColorOut, metallic, roughness); *GLTFTextureUtils::GetChannelValue(metalRoughPixels, i, Green) = roughness; *GLTFTextureUtils::GetChannelValue(metalRoughPixels, i, Blue) = metallic; auto diffuseOutPtr = GLTFTextureUtils::GetChannelValue(diffuseOutPixels, i, Red); memcpy_s(diffuseOutPtr, 16, diffuseColorOut, 16); } } Document GLTFSpecularGlossinessUtils::ConvertMaterial(std::shared_ptr streamReader, const Document & doc, const Material & material, const std::string& outputDirectory) { if (!material.HasExtension()) { return doc; } Document resultDoc(doc); Material resultMaterial(material); resultMaterial.RemoveExtension(); const auto& specularGlossiness = material.GetExtension(); XMVECTORF32 diffuseFactorIn = { specularGlossiness.diffuseFactor.r, specularGlossiness.diffuseFactor.g, specularGlossiness.diffuseFactor.b, specularGlossiness.diffuseFactor.a }; XMVECTORF32 specularFactor = { specularGlossiness.specularFactor.r, specularGlossiness.specularFactor.g, specularGlossiness.specularFactor.b, specularGlossiness.glossinessFactor }; // First, we check if there actually is a diffuse or specular glossiness texture to convert. // If not, we only perform the conversion on the materials parameters and early out. if (specularGlossiness.diffuseTexture.textureId.empty() && specularGlossiness.specularGlossinessTexture.textureId.empty()) { XMVECTORF32 diffuseFactor; float metallicFactor; float roughnessFactor; ConvertEntrySpecularGlossinessToMetallicRoughness(diffuseFactorIn, specularFactor, diffuseFactor, metallicFactor, roughnessFactor); resultMaterial.metallicRoughness.baseColorFactor.r = diffuseFactor.f[0]; resultMaterial.metallicRoughness.baseColorFactor.g = diffuseFactor.f[1]; resultMaterial.metallicRoughness.baseColorFactor.b = diffuseFactor.f[2]; resultMaterial.metallicRoughness.baseColorFactor.a = diffuseFactor.f[3]; resultMaterial.metallicRoughness.metallicFactor = metallicFactor; resultMaterial.metallicRoughness.roughnessFactor = roughnessFactor; resultDoc.materials.Replace(resultMaterial); } std::string samplerId; // Diffuse texture std::unique_ptr diffuseTexture; if (!specularGlossiness.diffuseTexture.textureId.empty()) { try { diffuseTexture = std::make_unique(GLTFTextureUtils::LoadTexture(streamReader, doc, specularGlossiness.diffuseTexture.textureId, false)); samplerId = doc.textures[specularGlossiness.diffuseTexture.textureId].samplerId; } catch (GLTFException) { throw GLTFException("Failed to load diffuse texture."); } } // SpecularGlossiness texture std::unique_ptr specularGlossinessTexture; if (!specularGlossiness.specularGlossinessTexture.textureId.empty()) { try { specularGlossinessTexture = std::make_unique(GLTFTextureUtils::LoadTexture(streamReader, doc, specularGlossiness.specularGlossinessTexture.textureId, false)); samplerId = samplerId.empty() ? doc.textures[specularGlossiness.specularGlossinessTexture.textureId].samplerId : samplerId; } catch (GLTFException) { throw GLTFException("Failed to load specular glossiness texture."); } } ScratchImage metallicRoughnessTexture; ScratchImage modulatedDiffuseTexture; ConvertTextureSpecularGlossinessToMetallicRoughness( metallicRoughnessTexture, modulatedDiffuseTexture, diffuseTexture, diffuseFactorIn, // will be baked into texture specularGlossinessTexture, specularFactor); Material::PBRMetallicRoughness gltfPBRMetallicRoughness; { DirectX::ScratchImage converted; if (FAILED(DirectX::Convert(*metallicRoughnessTexture.GetImage(0, 0, 0), DXGI_FORMAT_B8G8R8X8_UNORM, DirectX::TEX_FILTER_SRGB_IN, DirectX::TEX_THRESHOLD_DEFAULT, converted))) { throw GLTFException("Failed to convert texture to DXGI_FORMAT_B8G8R8X8_UNORM for processing."); } auto metallicRoughnessPath = GLTFTextureUtils::SaveAsPng(&converted, "metallicRoughness_" + material.id + ".png", outputDirectory); auto metallicRoughnessImageId = GLTFTextureUtils::AddImageToDocument(resultDoc, metallicRoughnessPath); Texture mrTexture; mrTexture.samplerId = samplerId; mrTexture.imageId = metallicRoughnessImageId; gltfPBRMetallicRoughness.metallicRoughnessTexture.textureId = resultDoc.textures.Append(mrTexture, AppendIdPolicy::GenerateOnEmpty).id; } { DirectX::ScratchImage converted; if (FAILED(DirectX::Convert(*modulatedDiffuseTexture.GetImage(0, 0, 0), DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, DirectX::TEX_FILTER_DEFAULT, DirectX::TEX_THRESHOLD_DEFAULT, converted))) { throw GLTFException("Failed to convert texture to DXGI_FORMAT_B8G8R8A8_UNORM_SRGB for processing."); } auto diffusePath = GLTFTextureUtils::SaveAsPng(&converted, "diffuse_" + material.id + ".png", outputDirectory, &GUID_WICPixelFormat32bppBGRA); auto diffuseImageId = GLTFTextureUtils::AddImageToDocument(resultDoc, diffusePath); Texture diffusGltfTexture; diffusGltfTexture.samplerId = samplerId; diffusGltfTexture.imageId = diffuseImageId; gltfPBRMetallicRoughness.baseColorTexture.textureId = resultDoc.textures.Append(diffusGltfTexture, AppendIdPolicy::GenerateOnEmpty).id; } resultMaterial.metallicRoughness = gltfPBRMetallicRoughness; resultDoc.materials.Replace(resultMaterial); return resultDoc; } Document GLTFSpecularGlossinessUtils::ConvertMaterials(std::shared_ptr streamReader, const Document & doc, const std::string & outputDirectory) { Document resultDocument(doc); for (const auto& material : doc.materials.Elements()) { resultDocument = ConvertMaterial(streamReader, resultDocument, material, outputDirectory); } resultDocument.extensionsUsed.erase(KHR::Materials::PBRSPECULARGLOSSINESS_NAME); resultDocument.extensionsRequired.erase(KHR::Materials::PBRSPECULARGLOSSINESS_NAME); return resultDocument; } ================================================ FILE: glTF-Toolkit/src/GLTFTextureCompressionUtils.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" #include "GLTFTextureUtils.h" #include "GLTFTexturePackingUtils.h" #include "GLTFTextureCompressionUtils.h" #include "DeviceResources.h" // Usings for ComPtr using namespace ABI::Windows::Foundation; using namespace Microsoft::WRL; // Usings for glTF using namespace Microsoft::glTF; using namespace Microsoft::glTF::Toolkit; #include const char* Microsoft::glTF::Toolkit::EXTENSION_MSFT_TEXTURE_DDS = "MSFT_texture_dds"; Document GLTFTextureCompressionUtils::CompressTextureAsDDS(std::shared_ptr streamReader, const Document & doc, const Texture & texture, TextureCompression compression, const std::string& outputDirectory, size_t maxTextureSize, bool generateMipMaps, bool retainOriginalImage, bool treatAsLinear) { Document outputDoc(doc); // Early return cases: // - No compression requested // - This texture doesn't have an image associated // - The texture already has a DDS extension if (compression == TextureCompression::None || texture.imageId.empty() || texture.extensions.find(EXTENSION_MSFT_TEXTURE_DDS) != texture.extensions.end()) { // Return copy of document return outputDoc; } auto image = std::make_unique(GLTFTextureUtils::LoadTexture(streamReader, doc, texture.id, treatAsLinear)); // Resize up to a multiple of 4 auto metadata = image->GetMetadata(); auto resizedWidth = metadata.width; auto resizedHeight = metadata.height; if (maxTextureSize < resizedWidth || maxTextureSize < resizedHeight) { // Scale auto scaleFactor = static_cast(maxTextureSize) / std::max(metadata.width, metadata.height); resizedWidth = static_cast(std::llround(metadata.width * scaleFactor)); resizedHeight = static_cast(std::llround(metadata.height * scaleFactor)); } if (resizedWidth % 4 != 0 || resizedHeight % 4 != 0) { static const std::function roundUpToMultipleOf4 = [](size_t input) { return input % 4 == 0 ? input : (input + 4) - (input % 4); }; resizedWidth = roundUpToMultipleOf4(resizedWidth); resizedHeight = roundUpToMultipleOf4(resizedHeight); } if (resizedWidth != metadata.width || resizedHeight != metadata.height) { auto resized = std::make_unique(); if (FAILED(DirectX::Resize(image->GetImages(), image->GetImageCount(), image->GetMetadata(), resizedWidth, resizedHeight, DirectX::TEX_FILTER_SEPARATE_ALPHA, *resized))) { throw GLTFException("Failed to resize image."); } image = std::move(resized); } if (generateMipMaps) { auto mipChain = std::make_unique(); if (FAILED(DirectX::GenerateMipMaps(image->GetImages(), image->GetImageCount(), image->GetMetadata(), DirectX::TEX_FILTER_SEPARATE_ALPHA, 0, *mipChain))) { throw GLTFException("Failed to generate mip maps."); } image = std::move(mipChain); } CompressImage(*image, compression); // Save image to file std::string outputImagePath = "texture_" + texture.id; if (!generateMipMaps) { // The default is to have mips, so note on the texture when it doesn't outputImagePath += "_nomips"; } switch (compression) { case TextureCompression::BC3: outputImagePath += "_BC3"; break; case TextureCompression::BC5: outputImagePath += "_BC5"; break; case TextureCompression::BC7: case TextureCompression::BC7_SRGB: outputImagePath += "_BC7"; break; default: throw GLTFException("Invalid compression."); break; } outputImagePath += ".dds"; std::wstring outputImagePathW(outputImagePath.begin(), outputImagePath.end()); wchar_t outputImageFullPath[MAX_PATH]; std::wstring outputDirectoryW(outputDirectory.begin(), outputDirectory.end()); if (FAILED(::PathCchCombine(outputImageFullPath, ARRAYSIZE(outputImageFullPath), outputDirectoryW.c_str(), outputImagePathW.c_str()))) { throw GLTFException("Failed to compose output file path."); } if (FAILED(SaveToDDSFile(image->GetImages(), image->GetImageCount(), image->GetMetadata(), DirectX::DDS_FLAGS::DDS_FLAGS_NONE, outputImageFullPath))) { throw GLTFException("Failed to save image as DDS."); } std::wstring outputImageFullPathW(outputImageFullPath); std::string outputImageFullPathA(outputImageFullPathW.begin(), outputImageFullPathW.end()); // Add back to GLTF std::string ddsImageId(texture.imageId); Image ddsImage(doc.images.Get(texture.imageId)); ddsImage.mimeType = "image/vnd-ms.dds"; ddsImage.uri = outputImageFullPathA; if (retainOriginalImage) { ddsImage.id.clear(); ddsImageId = outputDoc.images.Append(ddsImage, AppendIdPolicy::GenerateOnEmpty).id; } else { outputDoc.images.Replace(ddsImage); } Texture ddsTexture(texture); // Create the JSON for the DDS extension element rapidjson::Document ddsExtensionJson; ddsExtensionJson.SetObject(); ddsExtensionJson.AddMember("source", rapidjson::Value(outputDoc.images.GetIndex(ddsImageId)), ddsExtensionJson.GetAllocator()); rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); ddsExtensionJson.Accept(writer); ddsTexture.extensions.insert(std::pair(EXTENSION_MSFT_TEXTURE_DDS, buffer.GetString())); outputDoc.textures.Replace(ddsTexture); outputDoc.extensionsUsed.insert(EXTENSION_MSFT_TEXTURE_DDS); if (!retainOriginalImage) { outputDoc.extensionsRequired.insert(EXTENSION_MSFT_TEXTURE_DDS); } return outputDoc; } Document GLTFTextureCompressionUtils::CompressAllTexturesForWindowsMR(std::shared_ptr streamReader, const Document & doc, const std::string& outputDirectory, size_t maxTextureSize, bool retainOriginalImages) { Document outputDoc(doc); for (auto material : doc.materials.Elements()) { auto compressIfNotEmpty = [&outputDoc, &streamReader, &outputDirectory, maxTextureSize, retainOriginalImages](const std::string& textureId, TextureCompression compression, bool treatAsLinear = true) { if (!textureId.empty()) { outputDoc = CompressTextureAsDDS(streamReader, outputDoc, outputDoc.textures.Get(textureId), compression, outputDirectory, maxTextureSize, true, retainOriginalImages, treatAsLinear); } }; // Compress base and emissive texture as BC7 compressIfNotEmpty(material.metallicRoughness.baseColorTexture.textureId, TextureCompression::BC7_SRGB, false); compressIfNotEmpty(material.emissiveTexture.textureId, TextureCompression::BC7_SRGB, false); // Get textures from the MSFT_packing_occlusionRoughnessMetallic extension if (material.extensions.find(EXTENSION_MSFT_PACKING_ORM) != material.extensions.end()) { rapidjson::Document packingOrmContents; packingOrmContents.Parse(material.extensions[EXTENSION_MSFT_PACKING_ORM].c_str()); // Compress packed textures as BC7 if (packingOrmContents.HasMember(MSFT_PACKING_ORM_RMOTEXTURE_KEY)) { auto rmoTextureId = packingOrmContents[MSFT_PACKING_ORM_RMOTEXTURE_KEY][MSFT_PACKING_INDEX_KEY].GetInt(); compressIfNotEmpty(std::to_string(rmoTextureId), TextureCompression::BC7); } if (packingOrmContents.HasMember(MSFT_PACKING_ORM_ORMTEXTURE_KEY)) { auto ormTextureId = packingOrmContents[MSFT_PACKING_ORM_ORMTEXTURE_KEY][MSFT_PACKING_INDEX_KEY].GetInt(); compressIfNotEmpty(std::to_string(ormTextureId), TextureCompression::BC7); } // Compress normal texture as BC5 if (packingOrmContents.HasMember(MSFT_PACKING_ORM_NORMALTEXTURE_KEY)) { auto normalTextureId = packingOrmContents[MSFT_PACKING_ORM_NORMALTEXTURE_KEY][MSFT_PACKING_INDEX_KEY].GetInt(); compressIfNotEmpty(std::to_string(normalTextureId), TextureCompression::BC5); } } // Get textures from the MSFT_packing_normalRoughnessMetallic extension if (material.extensions.find(EXTENSION_MSFT_PACKING_NRM) != material.extensions.end()) { rapidjson::Document packingNrmContents; packingNrmContents.Parse(material.extensions[EXTENSION_MSFT_PACKING_NRM].c_str()); // Compress packed texture as BC7 if (packingNrmContents.HasMember(MSFT_PACKING_NRM_KEY)) { auto nrmTextureId = packingNrmContents[MSFT_PACKING_NRM_KEY][MSFT_PACKING_INDEX_KEY].GetInt(); compressIfNotEmpty(std::to_string(nrmTextureId), TextureCompression::BC7, false); // This tool generates sRGB-packaged images } } } return outputDoc; } void GLTFTextureCompressionUtils::CompressImage(DirectX::ScratchImage& image, TextureCompression compression) { if (compression == TextureCompression::None) { return; } DXGI_FORMAT compressionFormat = DXGI_FORMAT_BC7_UNORM; switch (compression) { case TextureCompression::BC3: compressionFormat = DXGI_FORMAT_BC3_UNORM; break; case TextureCompression::BC5: compressionFormat = DXGI_FORMAT_BC5_UNORM; break; case TextureCompression::BC7: compressionFormat = DXGI_FORMAT_BC7_UNORM; break; case TextureCompression::BC7_SRGB: compressionFormat = DXGI_FORMAT_BC7_UNORM_SRGB; break; default: throw std::invalid_argument("Invalid compression specified."); break; } bool gpuCompressionSuccessful = false; DirectX::ScratchImage compressedImage; try { DX::DeviceResources deviceResources; deviceResources.CreateDeviceResources(); ComPtr device(deviceResources.GetD3DDevice()); if (device != nullptr) { if (SUCCEEDED(DirectX::Compress(device.Get(), image.GetImages(), image.GetImageCount(), image.GetMetadata(), compressionFormat, DirectX::TEX_COMPRESS_DEFAULT, 1, compressedImage))) { gpuCompressionSuccessful = true; } } } catch (std::exception e) { // Failed to initialize device - GPU is not available } if (!gpuCompressionSuccessful) { // Try software compression if (FAILED(DirectX::Compress(image.GetImages(), image.GetImageCount(), image.GetMetadata(), compressionFormat, DirectX::TEX_COMPRESS_PARALLEL, 1, compressedImage))) { throw GLTFException("Failed to compress data using software compression"); } } image = std::move(compressedImage); } ================================================ FILE: glTF-Toolkit/src/GLTFTexturePackingUtils.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" #include #include "GLTFTextureUtils.h" #include "GLTFTexturePackingUtils.h" using namespace Microsoft::glTF; using namespace Microsoft::glTF::Toolkit; const char* Microsoft::glTF::Toolkit::EXTENSION_MSFT_PACKING_ORM = "MSFT_packing_occlusionRoughnessMetallic"; const char* Microsoft::glTF::Toolkit::EXTENSION_MSFT_PACKING_NRM = "MSFT_packing_normalRoughnessMetallic"; const char* Microsoft::glTF::Toolkit::MSFT_PACKING_INDEX_KEY = "index"; const char* Microsoft::glTF::Toolkit::MSFT_PACKING_ORM_ORMTEXTURE_KEY = "occlusionRoughnessMetallicTexture"; const char* Microsoft::glTF::Toolkit::MSFT_PACKING_ORM_RMOTEXTURE_KEY = "roughnessMetallicOcclusionTexture"; const char* Microsoft::glTF::Toolkit::MSFT_PACKING_ORM_NORMALTEXTURE_KEY = "normalTexture"; const char* Microsoft::glTF::Toolkit::MSFT_PACKING_NRM_KEY = "normalRoughnessMetallicTexture"; namespace { void AddTextureToExtension(const std::string& imageId, TexturePacking packing, Document& doc, rapidjson::Value& packedExtensionJson, rapidjson::MemoryPoolAllocator<>& a) { Texture packedTexture; packedTexture.imageId = imageId; auto textureId = doc.textures.Append(std::move(packedTexture), AppendIdPolicy::GenerateOnEmpty).id; rapidjson::Value packedTextureJson(rapidjson::kObjectType); { packedTextureJson.AddMember(rapidjson::StringRef(MSFT_PACKING_INDEX_KEY), rapidjson::Value(doc.textures.GetIndex(textureId)), a); } switch (packing) { case TexturePacking::OcclusionRoughnessMetallic: packedExtensionJson.AddMember(rapidjson::StringRef(MSFT_PACKING_ORM_ORMTEXTURE_KEY), packedTextureJson, a); break; case TexturePacking::RoughnessMetallicOcclusion: packedExtensionJson.AddMember(rapidjson::StringRef(MSFT_PACKING_ORM_RMOTEXTURE_KEY), packedTextureJson, a); break; case TexturePacking::NormalRoughnessMetallic: packedExtensionJson.AddMember(rapidjson::StringRef(MSFT_PACKING_NRM_KEY), packedTextureJson, a); break; default: throw GLTFException("Invalid packing."); } } void Renormalize(std::unique_ptr &normalImage, DirectX::ScratchImage &renormalizedImage) { auto image = normalImage->GetImage(0, 0, 0); if (FAILED(renormalizedImage.Initialize2D(image->format, image->width, image->height, 1, 1))) { throw GLTFException("Failed to initialize from texture."); } uint8_t *normalPixels = normalImage->GetPixels(); auto metadata = normalImage->GetMetadata(); auto renormalizedPixels = renormalizedImage.GetPixels(); const auto two = DirectX::XMVectorReplicate(2.0f); const auto minusOne = DirectX::XMVectorReplicate(-1.0f); const auto half = DirectX::XMVectorReplicate(0.5f); for (size_t i = 0; i < metadata.width * metadata.height; i += 1) { // renormalizedPixels = 0.5 * normalize(normalPixel * 2 - 1) + 0.5 const auto value = DirectX::XMVectorSet( *GLTFTextureUtils::GetChannelValue(normalPixels, i, Channel::Red), *GLTFTextureUtils::GetChannelValue(normalPixels, i, Channel::Green), *GLTFTextureUtils::GetChannelValue(normalPixels, i, Channel::Blue), 0.0f); auto normal = DirectX::XMVectorMultiplyAdd(value, two, minusOne); normal = DirectX::XMVector3Normalize(normal); const auto result = DirectX::XMVectorMultiplyAdd(half, normal, half); *GLTFTextureUtils::GetChannelValue(renormalizedPixels, i, Channel::Red) = DirectX::XMVectorGetX(result); *GLTFTextureUtils::GetChannelValue(renormalizedPixels, i, Channel::Green) = DirectX::XMVectorGetY(result); *GLTFTextureUtils::GetChannelValue(renormalizedPixels, i, Channel::Blue) = DirectX::XMVectorGetZ(result); } } void AdjustRoughness(std::unique_ptr &roughnessImage, std::unique_ptr &normalImage, DirectX::ScratchImage &adjustedImage) { auto image = roughnessImage->GetImage(0, 0, 0); if (FAILED(adjustedImage.Initialize2D(image->format, image->width, image->height, 1, 1))) { throw GLTFException("Failed to initialize from texture."); } const auto two = DirectX::XMVectorReplicate(2.0f); const auto minusOne = DirectX::XMVectorReplicate(-1.0f); uint8_t *normalPixels = normalImage->GetPixels(); auto metadata = normalImage->GetMetadata(); auto adjustedPixels = adjustedImage.GetPixels(); uint8_t *roughnessPixels = roughnessImage->GetPixels(); for (size_t i = 0; i < metadata.width * metadata.height; i += 1) { auto normal = DirectX::XMVectorSet( *GLTFTextureUtils::GetChannelValue(normalPixels, i, Channel::Red), *GLTFTextureUtils::GetChannelValue(normalPixels, i, Channel::Green), *GLTFTextureUtils::GetChannelValue(normalPixels, i, Channel::Blue), 0.0f); normal = DirectX::XMVectorMultiplyAdd(normal, two, minusOne); auto avgNormalLengthSquare = DirectX::XMVector3LengthSq(normal); float avgNormalLengthSquareF = DirectX::XMVectorGetX(avgNormalLengthSquare); float oldRoughness = *GLTFTextureUtils::GetChannelValue(roughnessPixels, i, Channel::Green); if (avgNormalLengthSquareF < 1.0f) { auto avgNormalLength = DirectX::XMVectorSqrt(avgNormalLengthSquare); float avgNormalLengthF = DirectX::XMVectorGetX(avgNormalLength); float kappa = (3.0f * avgNormalLengthF - avgNormalLengthF * avgNormalLengthSquareF) / (1.0f - avgNormalLengthSquareF); float variance = 1.0f / (2.0f * kappa); float newRoughness = sqrt(oldRoughness * oldRoughness + variance); *GLTFTextureUtils::GetChannelValue(adjustedPixels, i, Channel::Green) = newRoughness; } else { *GLTFTextureUtils::GetChannelValue(adjustedPixels, i, Channel::Green) = oldRoughness; } } } } std::unordered_set GLTFTexturePackingUtils::GetTextureIndicesFromMsftExtensions(const Material& material) { static const char* extensionKeys[] = { EXTENSION_MSFT_PACKING_ORM, EXTENSION_MSFT_PACKING_NRM }; static const char* textureKeys[] = { MSFT_PACKING_ORM_ORMTEXTURE_KEY, MSFT_PACKING_ORM_RMOTEXTURE_KEY, MSFT_PACKING_ORM_NORMALTEXTURE_KEY, MSFT_PACKING_NRM_KEY }; std::unordered_set textureIndices; for (const auto& extensionKey : extensionKeys) { auto extensionIt = material.extensions.find(extensionKey); if (extensionIt != material.extensions.end() && !extensionIt->second.empty()) { rapidjson::Document extJson = RapidJsonUtils::CreateDocumentFromString(extensionIt->second); for (const auto& textureKey : textureKeys) { if (extJson.HasMember(textureKey)) { const auto index = extJson[textureKey][MSFT_PACKING_INDEX_KEY].GetInt(); textureIndices.insert(index); } } } } return textureIndices; } Document GLTFTexturePackingUtils::PackMaterialForWindowsMR(std::shared_ptr streamReader, const Document& doc, const Material& material, TexturePacking packing, const std::string& outputDirectory) { Document outputDoc(doc); // No packing requested, return copy of document if (packing == TexturePacking::None) { return outputDoc; } // Read images from material auto metallicRoughness = material.metallicRoughness.metallicRoughnessTexture.textureId; auto normal = material.normalTexture.textureId; auto occlusion = material.occlusionTexture.textureId; bool hasMR = !metallicRoughness.empty(); bool hasNormal = !normal.empty(); bool hasOcclusion = !occlusion.empty(); // Early return if there's nothing to pack if (!hasMR && !hasOcclusion && !hasNormal) { // RM, O and Normal are empty, and the packing requires at least one of them return outputDoc; } // TODO: Optimization - If the texture pair (MR + O) has already been packed together with the // current packing, point to that existing texture instead of creating a new one Material outputMaterial = outputDoc.materials.Get(material.id); // Create the JSON for the material extension element rapidjson::Document ormExtensionJson; ormExtensionJson.SetObject(); rapidjson::MemoryPoolAllocator<>& ormAllocator = ormExtensionJson.GetAllocator(); rapidjson::Document nrmExtensionJson; nrmExtensionJson.SetObject(); rapidjson::MemoryPoolAllocator<>& nrmAllocator = nrmExtensionJson.GetAllocator(); std::unique_ptr metallicRoughnessImage = nullptr; if (hasMR) { try { metallicRoughnessImage = std::make_unique(GLTFTextureUtils::LoadTexture(streamReader, doc, metallicRoughness)); } catch (GLTFException) { throw GLTFException("Failed to load metallic roughness texture."); } } bool packingIncludesOrm = (packing & (TexturePacking::OcclusionRoughnessMetallic | TexturePacking::RoughnessMetallicOcclusion)) > 0; std::unique_ptr occlusionImage = nullptr; if (hasOcclusion && packingIncludesOrm) { try { occlusionImage = std::make_unique(GLTFTextureUtils::LoadTexture(streamReader, doc, occlusion)); } catch (GLTFException) { throw GLTFException("Failed to load occlusion texture."); } } if (hasMR && hasOcclusion && packingIncludesOrm) { GLTFTextureUtils::ResizeToLargest(metallicRoughnessImage, occlusionImage); } bool packingIncludesNrm = (packing & TexturePacking::NormalRoughnessMetallic) > 0; std::unique_ptr normalImage = nullptr; if (hasNormal && packingIncludesNrm) { try { normalImage = std::make_unique(GLTFTextureUtils::LoadTexture(streamReader, doc, normal)); } catch (GLTFException) { throw GLTFException("Failed to load normal texture."); } } if (hasMR && hasNormal && packingIncludesNrm) { GLTFTextureUtils::ResizeToLargest(metallicRoughnessImage, normalImage); } uint8_t *mrPixels = metallicRoughnessImage != nullptr ? metallicRoughnessImage->GetPixels() : nullptr; uint8_t *occlusionPixels = occlusionImage != nullptr ? occlusionImage->GetPixels() : nullptr; uint8_t *normalPixels = normalImage != nullptr ? normalImage->GetPixels() : nullptr; // Pack textures using DirectXTex if (packing & TexturePacking::OcclusionRoughnessMetallic && (hasMR || hasOcclusion)) { std::string ormImageId; // If occlusion and metallic roughness are pointing to the same texture, // according to the GLTF spec, that texture is already packed as ORM // (occlusion = R, roughness = G, metalness = B) if (occlusion == metallicRoughness && hasOcclusion) { ormImageId = metallicRoughness; } else { DirectX::ScratchImage orm; auto sourceImage = hasMR ? *metallicRoughnessImage->GetImage(0, 0, 0) : *occlusionImage->GetImage(0, 0, 0); if (FAILED(orm.Initialize2D(sourceImage.format, sourceImage.width, sourceImage.height, 1, 1))) { throw GLTFException("Failed to initialize from texture."); } auto ormPixels = orm.GetPixels(); auto metadata = orm.GetMetadata(); for (size_t i = 0; i < metadata.width * metadata.height; i += 1) { // Occlusion: Occ [R] -> ORM [R] *GLTFTextureUtils::GetChannelValue(ormPixels, i, Channel::Red) = hasOcclusion ? *GLTFTextureUtils::GetChannelValue(occlusionPixels, i, Channel::Red) : 255.0f; // Roughness: MR [G] -> ORM [G] *GLTFTextureUtils::GetChannelValue(ormPixels, i, Channel::Green) = hasMR ? *GLTFTextureUtils::GetChannelValue(mrPixels, i, Channel::Green) : 255.0f; // Metalness: MR [B] -> ORM [B] *GLTFTextureUtils::GetChannelValue(ormPixels, i, Channel::Blue) = hasMR ? *GLTFTextureUtils::GetChannelValue(mrPixels, i, Channel::Blue) : 255.0f; } // Convert with assumed sRGB because PNG defaults to that color space. DirectX::ScratchImage converted; if (FAILED(DirectX::Convert(*orm.GetImage(0, 0, 0), DXGI_FORMAT_B8G8R8X8_UNORM, DirectX::TEX_FILTER_SRGB_IN, DirectX::TEX_THRESHOLD_DEFAULT, converted))) { throw GLTFException("Failed to convert texture to DXGI_FORMAT_B8G8R8X8_UNORM for storage."); } auto imagePath = GLTFTextureUtils::SaveAsPng(&converted, "packing_orm_" + material.id + ".png", outputDirectory); ormImageId = GLTFTextureUtils::AddImageToDocument(outputDoc, imagePath); } AddTextureToExtension(ormImageId, TexturePacking::OcclusionRoughnessMetallic, outputDoc, ormExtensionJson, ormAllocator); } if (packing & TexturePacking::RoughnessMetallicOcclusion && (hasMR || hasOcclusion)) { DirectX::ScratchImage rmo; auto sourceImage = hasMR ? *metallicRoughnessImage->GetImage(0, 0, 0) : *occlusionImage->GetImage(0, 0, 0); if (FAILED(rmo.Initialize2D(sourceImage.format, sourceImage.width, sourceImage.height, 1, 1))) { throw GLTFException("Failed to initialize from texture."); } auto rmoPixels = rmo.GetPixels(); auto metadata = rmo.GetMetadata(); for (size_t i = 0; i < metadata.width * metadata.height; i += 1) { // Roughness: MR [G] -> RMO [R] *GLTFTextureUtils::GetChannelValue(rmoPixels, i, Channel::Red) = hasMR ? *GLTFTextureUtils::GetChannelValue(mrPixels, i, Channel::Green) : 255.0f; // Metalness: MR [B] -> RMO [G] *GLTFTextureUtils::GetChannelValue(rmoPixels, i, Channel::Green) = hasMR ? *GLTFTextureUtils::GetChannelValue(mrPixels, i, Channel::Blue) : 255.0f; // Occlusion: Occ [R] -> RMO [B] *GLTFTextureUtils::GetChannelValue(rmoPixels, i, Channel::Blue) = hasOcclusion ? *GLTFTextureUtils::GetChannelValue(occlusionPixels, i, Channel::Red) : 255.0f; } // Convert with assumed sRGB because PNG defaults to that color space. DirectX::ScratchImage converted; if (FAILED(DirectX::Convert(*rmo.GetImage(0, 0, 0), DXGI_FORMAT_B8G8R8X8_UNORM, DirectX::TEX_FILTER_SRGB_IN, DirectX::TEX_THRESHOLD_DEFAULT, converted))) { throw GLTFException("Failed to convert texture to DXGI_FORMAT_B8G8R8X8_UNORM for storage."); } auto imagePath = GLTFTextureUtils::SaveAsPng(&converted, "packing_rmo_" + material.id + ".png", outputDirectory); // Add back to GLTF auto rmoImageId = GLTFTextureUtils::AddImageToDocument(outputDoc, imagePath); AddTextureToExtension(rmoImageId, TexturePacking::RoughnessMetallicOcclusion, outputDoc, ormExtensionJson, ormAllocator); } if (packingIncludesNrm && (hasMR || hasNormal)) { uint8_t *renormalPixels = normalPixels; DirectX::ScratchImage renormalizedImage; uint8_t *roughnessPixels = mrPixels; DirectX::ScratchImage adjustRoughnessImage; if (hasNormal) { Renormalize(normalImage, renormalizedImage); renormalPixels = renormalizedImage.GetPixels(); if (hasMR) { AdjustRoughness(metallicRoughnessImage, normalImage, adjustRoughnessImage); roughnessPixels = adjustRoughnessImage.GetPixels(); } } DirectX::ScratchImage nrm; auto sourceImage = hasMR ? *metallicRoughnessImage->GetImage(0, 0, 0) : *normalImage->GetImage(0, 0, 0); if (FAILED(nrm.Initialize2D(sourceImage.format, sourceImage.width, sourceImage.height, 1, 1))) { throw GLTFException("Failed to initialize from texture."); } auto nrmPixels = nrm.GetPixels(); auto metadata = nrm.GetMetadata(); for (size_t i = 0; i < metadata.width * metadata.height; i += 1) { // Normal: N [RG] -> NRM [RG] *GLTFTextureUtils::GetChannelValue(nrmPixels, i, Channel::Red) = hasNormal ? *GLTFTextureUtils::GetChannelValue(renormalPixels, i, Channel::Red) : 255.0f; *GLTFTextureUtils::GetChannelValue(nrmPixels, i, Channel::Green) = hasNormal ? *GLTFTextureUtils::GetChannelValue(renormalPixels, i, Channel::Green) : 255.0f; // Roughness: MR [G] -> NRM [B] *GLTFTextureUtils::GetChannelValue(nrmPixels, i, Channel::Blue) = hasMR ? *GLTFTextureUtils::GetChannelValue(roughnessPixels, i, Channel::Green) : 255.0f; // Metalness: MR [B] -> NRM [A] *GLTFTextureUtils::GetChannelValue(nrmPixels, i, Channel::Alpha) = hasMR ? *GLTFTextureUtils::GetChannelValue(mrPixels, i, Channel::Blue) : 255.0f; } // Assumed sRGB because PNG defaults to that color space. auto imagePath = GLTFTextureUtils::SaveAsPng(&nrm, "packing_nrm_" + material.id + ".png", outputDirectory, &GUID_WICPixelFormat32bppBGRA); // Add back to GLTF auto nrmImageId = GLTFTextureUtils::AddImageToDocument(outputDoc, imagePath); AddTextureToExtension(nrmImageId, TexturePacking::NormalRoughnessMetallic, outputDoc, nrmExtensionJson, nrmAllocator); } if (packingIncludesOrm) { if (hasNormal) { rapidjson::Value ormNormalTextureJson(rapidjson::kObjectType); { ormNormalTextureJson.AddMember(rapidjson::StringRef(MSFT_PACKING_INDEX_KEY), rapidjson::Value(std::stoi(normal)), ormAllocator); } ormExtensionJson.AddMember(rapidjson::StringRef(MSFT_PACKING_ORM_NORMALTEXTURE_KEY), ormNormalTextureJson, ormAllocator); } rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); ormExtensionJson.Accept(writer); outputMaterial.extensions.insert(std::pair(EXTENSION_MSFT_PACKING_ORM, buffer.GetString())); outputDoc.extensionsUsed.insert(EXTENSION_MSFT_PACKING_ORM); } if (packingIncludesNrm) { rapidjson::StringBuffer buffer; rapidjson::Writer writer(buffer); nrmExtensionJson.Accept(writer); outputMaterial.extensions.insert(std::pair(EXTENSION_MSFT_PACKING_NRM, buffer.GetString())); outputDoc.extensionsUsed.insert(EXTENSION_MSFT_PACKING_NRM); } outputDoc.materials.Replace(outputMaterial); return outputDoc; } Document GLTFTexturePackingUtils::PackAllMaterialsForWindowsMR(std::shared_ptr streamReader, const Document & doc, TexturePacking packing, const std::string& outputDirectory) { Document outputDoc(doc); // No packing requested, return copy of document if (packing == TexturePacking::None) { return outputDoc; } for (auto material : doc.materials.Elements()) { outputDoc = PackMaterialForWindowsMR(streamReader, outputDoc, material, packing, outputDirectory); } return outputDoc; } ================================================ FILE: glTF-Toolkit/src/GLTFTextureUtils.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" #include "GLTFTextureUtils.h" #include #include "GLTFTextureCompressionUtils.h" #include "GLTFTexturePackingUtils.h" using namespace Microsoft::glTF; using namespace Microsoft::glTF::Toolkit; DirectX::ScratchImage GLTFTextureUtils::LoadTexture(std::shared_ptr streamReader, const Document& doc, const std::string& textureId, bool treatAsLinear) { DirectX::ScratchImage output; const Texture& texture = doc.textures.Get(textureId); GLTFResourceReader gltfResourceReader(streamReader); const Image& image = doc.images.Get(texture.imageId); std::vector imageData = gltfResourceReader.ReadBinaryData(doc, image); DirectX::TexMetadata info; if (FAILED(DirectX::LoadFromDDSMemory(imageData.data(), imageData.size(), DirectX::DDS_FLAGS_NONE, &info, output))) { // DDS failed, try WIC // Note: try DDS first since WIC can load some DDS (but not all), so we wouldn't want to get // a partial or invalid DDS loaded from WIC. if (FAILED(DirectX::LoadFromWICMemory(imageData.data(), imageData.size(), treatAsLinear ? DirectX::WIC_FLAGS_IGNORE_SRGB : DirectX::WIC_FLAGS_NONE, &info, output))) { throw GLTFException("Failed to load image - Image could not be loaded as DDS or read by WIC."); } } if (info.format == DXGI_FORMAT_R32G32B32A32_FLOAT && treatAsLinear) { return output; } else { DirectX::ScratchImage converted; if (FAILED(DirectX::Convert(*output.GetImage(0, 0, 0), DXGI_FORMAT_R32G32B32A32_FLOAT, treatAsLinear ? DirectX::TEX_FILTER_DEFAULT : DirectX::TEX_FILTER_SRGB_IN, DirectX::TEX_THRESHOLD_DEFAULT, converted))) { throw GLTFException("Failed to convert texture to DXGI_FORMAT_R32G32B32A32_FLOAT for processing."); } return converted; } } // Constants for the format DXGI_FORMAT_R32G32B32A32_FLOAT constexpr size_t DXGI_FORMAT_R32G32B32A32_FLOAT_STRIDE = 16; float* GLTFTextureUtils::GetChannelValue(uint8_t * imageData, size_t offset, Channel channel) { return reinterpret_cast(imageData + offset * DXGI_FORMAT_R32G32B32A32_FLOAT_STRIDE + channel); } std::string GLTFTextureUtils::SaveAsPng(DirectX::ScratchImage* image, const std::string& fileName, const std::string& directory, const GUID* targetFormat) { wchar_t outputImageFullPath[MAX_PATH]; auto fileNameW = std::wstring(fileName.begin(), fileName.end()); auto directoryW = std::wstring(directory.begin(), directory.end()); if (FAILED(::PathCchCombine(outputImageFullPath, ARRAYSIZE(outputImageFullPath), directoryW.c_str(), fileNameW.c_str()))) { throw GLTFException("Failed to compose output file path."); } const DirectX::Image* img = image->GetImage(0, 0, 0); if (FAILED(SaveToWICFile(*img, DirectX::WIC_FLAGS::WIC_FLAGS_NONE, GUID_ContainerFormatPng, outputImageFullPath, targetFormat))) { throw GLTFException("Failed to save file."); } std::wstring outputImageFullPathStr(outputImageFullPath); return std::string(outputImageFullPathStr.begin(), outputImageFullPathStr.end()); } std::string GLTFTextureUtils::AddImageToDocument(Document& doc, const std::string& imageUri) { Image image; image.uri = imageUri; return doc.images.Append(std::move(image), AppendIdPolicy::GenerateOnEmpty).id; } void GLTFTextureUtils::ResizeIfNeeded(const std::unique_ptr& image, size_t resizedWidth, size_t resizedHeight) { auto metadata = image->GetMetadata(); if (resizedWidth != metadata.width || resizedHeight != metadata.height) { DirectX::ScratchImage resized; if (FAILED(DirectX::Resize(image->GetImages(), image->GetImageCount(), metadata, resizedWidth, resizedHeight, DirectX::TEX_FILTER_DEFAULT, resized))) { throw GLTFException("Failed to resize image while packing."); } *image = std::move(resized); } } Document GLTFTextureUtils::RemoveRedundantTexturesAndImages(const Document& doc) { Document resultDocument(doc); // 1. Find used textures std::unordered_set usedTextureIds; for (const auto& material : doc.materials.Elements()) { std::unordered_set textureIds = { material.metallicRoughness.baseColorTexture.textureId, material.metallicRoughness.metallicRoughnessTexture.textureId, material.normalTexture.textureId, material.occlusionTexture.textureId, material.emissiveTexture.textureId }; if (material.HasExtension()) { textureIds.insert(material.GetExtension().diffuseTexture.textureId); textureIds.insert(material.GetExtension().specularGlossinessTexture.textureId); } auto textureIndices = GLTFTexturePackingUtils::GetTextureIndicesFromMsftExtensions(material); for (const auto& textureIndex : textureIndices) { textureIds.insert(doc.textures.Get(textureIndex).id); } for (const auto& textureId : textureIds) { if (!textureId.empty()) { usedTextureIds.insert(textureId); } } } // 2. Find used images and remove unused textures std::unordered_set usedImageIds; for (const auto& texture : doc.textures.Elements()) { const auto textureIsUsed = usedTextureIds.find(texture.id) != usedTextureIds.end(); if (textureIsUsed) { usedImageIds.insert(texture.imageId); // MSFT_texture_dds extension auto ddsExtensionIt = texture.extensions.find(EXTENSION_MSFT_TEXTURE_DDS); if (ddsExtensionIt != texture.extensions.end() && !ddsExtensionIt->second.empty()) { rapidjson::Document ddsJson = RapidJsonUtils::CreateDocumentFromString(ddsExtensionIt->second); if (ddsJson.HasMember("source")) { const auto index = ddsJson["source"].GetInt(); const auto imageId = doc.images.Get(index).id; usedImageIds.insert(imageId); } } } else { resultDocument.textures.Remove(texture.id); } } // 3. Remove unused images for (const auto& image : doc.images.Elements()) { auto imageIsUsed = usedImageIds.find(image.id) != usedImageIds.end(); if (!imageIsUsed) { resultDocument.images.Remove(image.id); } } return resultDocument; } void GLTFTextureUtils::ResizeToLargest(std::unique_ptr& image1, std::unique_ptr& image2) { auto metadata1 = image1->GetMetadata(); auto metadata2 = image2->GetMetadata(); if (metadata1.height != metadata2.height || metadata1.width != metadata2.width) { auto resizedWidth = std::max(metadata1.width, metadata2.width); auto resizedHeight = std::max(metadata1.height, metadata2.height); ResizeIfNeeded(image1, resizedWidth, resizedHeight); ResizeIfNeeded(image2, resizedWidth, resizedHeight); } } ================================================ FILE: glTF-Toolkit/src/SerializeBinary.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" #include "AccessorUtils.h" #include "SerializeBinary.h" #include "GLTFSDK/GLTF.h" #include "GLTFSDK/Document.h" #include "GLTFSDK/GLBResourceReader.h" #include "GLTFSDK/GLBResourceWriter.h" #include "GLTFSDK/Serialize.h" #include "GLTFSDK/BufferBuilder.h" #include "GLTFSDK/ExtensionsKHR.h" using namespace Microsoft::glTF; using namespace Microsoft::glTF::Toolkit; namespace { static std::string MimeTypeFromUri(const std::string& uri) { auto extension = uri.substr(uri.rfind('.') + 1, 3); std::transform(extension.begin(), extension.end(), extension.begin(), [](char c) { return static_cast(::tolower(static_cast(c))); }); if (extension == "dds") { return "image/vnd-ms.dds"; } if (extension == FILE_EXT_JPEG) { return MIMETYPE_JPEG; } if (extension == FILE_EXT_PNG) { return MIMETYPE_PNG; } return "text/plain"; } template void SaveAccessor(const Accessor& accessor, const std::vector accessorContents, BufferBuilder& builder) { auto min = accessor.min; auto max = accessor.max; if ((min.empty() || max.empty()) && !accessorContents.empty()) { auto minmax = AccessorUtils::CalculateMinMax(accessor, accessorContents); min = minmax.first; max = minmax.second; } builder.AddAccessor(accessorContents, AccessorDesc(accessor.type, accessor.componentType, accessor.normalized, min, max)); } template static std::vector vector_static_cast(const std::vector& original) { auto newData = std::vector(original.size()); std::transform(original.begin(), original.end(), newData.begin(), [](const OriginalType& element) { return static_cast(element); }); return newData; } template void ConvertAndSaveAccessor(const Accessor& accessor, const std::vector accessorContents, BufferBuilder& builder) { switch (accessor.componentType) { case COMPONENT_BYTE: SaveAccessor(accessor, vector_static_cast(accessorContents), builder); break; case COMPONENT_UNSIGNED_BYTE: SaveAccessor(accessor, vector_static_cast(accessorContents), builder); break; case COMPONENT_SHORT: SaveAccessor(accessor, vector_static_cast(accessorContents), builder); break; case COMPONENT_UNSIGNED_SHORT: SaveAccessor(accessor, vector_static_cast(accessorContents), builder); break; case COMPONENT_UNSIGNED_INT: SaveAccessor(accessor, vector_static_cast(accessorContents), builder); break; case COMPONENT_FLOAT: SaveAccessor(accessor, vector_static_cast(accessorContents), builder); break; default: throw GLTFException("Unsupported accessor ComponentType"); } } template void SerializeAccessor(const Accessor& accessor, const Document& doc, const GLTFResourceReader& reader, BufferBuilder& builder, const AccessorConversionStrategy& accessorConversion) { builder.AddBufferView(doc.bufferViews.Get(accessor.bufferViewId).target); const std::vector& accessorContents = reader.ReadBinaryData(doc, accessor); if (accessorConversion != nullptr && accessorConversion(accessor) != accessor.componentType) { Accessor updatedAccessor(accessor); updatedAccessor.componentType = accessorConversion(accessor); // Force recalculation of min and max updatedAccessor.min.clear(); updatedAccessor.max.clear(); ConvertAndSaveAccessor(updatedAccessor, accessorContents, builder); } else { SaveAccessor(accessor, accessorContents, builder); } } void SerializeAccessor(const Accessor& accessor, const Document& doc, const GLTFResourceReader& reader, BufferBuilder& builder, const AccessorConversionStrategy& accessorConversion) { switch (accessor.componentType) { case COMPONENT_BYTE: SerializeAccessor(accessor, doc, reader, builder, accessorConversion); break; case COMPONENT_UNSIGNED_BYTE: SerializeAccessor(accessor, doc, reader, builder, accessorConversion); break; case COMPONENT_SHORT: SerializeAccessor(accessor, doc, reader, builder, accessorConversion); break; case COMPONENT_UNSIGNED_SHORT: SerializeAccessor(accessor, doc, reader, builder, accessorConversion); break; case COMPONENT_UNSIGNED_INT: SerializeAccessor(accessor, doc, reader, builder, accessorConversion); break; case COMPONENT_FLOAT: SerializeAccessor(accessor, doc, reader, builder, accessorConversion); break; default: throw GLTFException("Unsupported accessor ComponentType"); } } } void Microsoft::glTF::Toolkit::SerializeBinary(const Document& document, const GLTFResourceReader& resourceReader, std::shared_ptr outputStreamWriter, const AccessorConversionStrategy& accessorConversion) { auto writer = std::make_unique(std::move(outputStreamWriter)); Document outputDoc(document); outputDoc.buffers.Clear(); outputDoc.bufferViews.Clear(); outputDoc.accessors.Clear(); // Get the collection of bufferViews we won't move around IndexedContainer staticBufferViews = document.bufferViews; for (const auto& accessor : document.accessors.Elements()) { if (!accessor.bufferViewId.empty() && staticBufferViews.Has(accessor.bufferViewId)) { staticBufferViews.Remove(accessor.bufferViewId); } } for (const auto& image : outputDoc.images.Elements()) { if (!image.bufferViewId.empty() && staticBufferViews.Has(image.bufferViewId)) { staticBufferViews.Remove(image.bufferViewId); } } size_t currentAccessorId = 0; std::string currentAccessorIdStr = std::to_string(currentAccessorId); size_t currentBufferViewId = 0; std::string currentBufferViewIdStr = std::to_string(currentBufferViewId); auto AdvanceAccessorId = [¤tAccessorId, ¤tAccessorIdStr]() { currentAccessorId++; currentAccessorIdStr = std::to_string(currentAccessorId); }; auto AdvanceBufferViewId = [¤tBufferViewId, ¤tBufferViewIdStr, &staticBufferViews]() { do { currentBufferViewId++; currentBufferViewIdStr = std::to_string(currentBufferViewId); } while (staticBufferViews.Has(currentBufferViewIdStr)); }; std::unique_ptr builder = std::make_unique(std::move(writer), [](const BufferBuilder&) { return GLB_BUFFER_ID; }, [¤tBufferViewIdStr](const BufferBuilder&) { return currentBufferViewIdStr; }, [¤tAccessorIdStr](const BufferBuilder&) { return currentAccessorIdStr; }); // GLB buffer builder->AddBuffer(GLB_BUFFER_ID); // Add those bufferView to the builder. for (const auto& bufferView : staticBufferViews.Elements()) { currentBufferViewIdStr = bufferView.id; auto data = resourceReader.ReadBinaryData(document, bufferView); builder->AddBufferView(data); } // Return value to tracked state currentBufferViewIdStr = std::to_string(currentBufferViewId); if (staticBufferViews.Has(currentBufferViewIdStr)) { AdvanceBufferViewId(); } // Serialize accessors for (const auto& accessor : document.accessors.Elements()) { if (!accessor.bufferViewId.empty() && accessor.count > 0) { SerializeAccessor(accessor, document, resourceReader, *builder, accessorConversion); AdvanceBufferViewId(); } else { outputDoc.accessors.Append(accessor); } AdvanceAccessorId(); } // Serialize images for (const auto& image : outputDoc.images.Elements()) { Image newImage(image); auto data = resourceReader.ReadBinaryData(document, image); auto imageBufferView = builder->AddBufferView(data); AdvanceBufferViewId(); newImage.bufferViewId = imageBufferView.id; if (image.mimeType.empty()) { newImage.mimeType = MimeTypeFromUri(image.uri); } newImage.uri.clear(); outputDoc.images.Replace(newImage); } // Collect anything in extensions that looks like it should to be packed for the GLB. for (auto& extension : outputDoc.extensions) { rapidjson::Document extensionJson; extensionJson.Parse(extension.second.c_str()); if (!extensionJson.IsObject()) { continue; } for (auto& member : extensionJson.GetObject()) { if (!member.value.IsArray()) { continue; } for (auto& possibleBuffer : member.value.GetArray()) { if (!possibleBuffer.IsObject()) { continue; } // Build an Image to object to use to load the data from. Image tmpImg; if (possibleBuffer.HasMember("uri")) { tmpImg.uri = possibleBuffer["uri"].GetString(); } else { continue; } try { auto data = resourceReader.ReadBinaryData(document, tmpImg); auto bufferView = builder->AddBufferView(data); AdvanceBufferViewId(); possibleBuffer.RemoveMember("uri"); possibleBuffer.RemoveMember("bufferView"); possibleBuffer.AddMember("bufferView", rapidjson::Value(std::stoi(bufferView.id)), extensionJson.GetAllocator()); } catch (...) { // Didn't work out. continue; } } } rapidjson::StringBuffer buffer; rapidjson::Writer jsonWriter(buffer); extensionJson.Accept(jsonWriter); extension.second = buffer.GetString(); } // Fill in any gaps in the bufferViewList. for (const auto& bufferView : staticBufferViews.Elements()) { auto bufferViewId = std::stoul(bufferView.id); while (bufferViewId > currentBufferViewId) { std::vector data; data.resize(4); builder->AddBufferView(data); AdvanceBufferViewId(); } } builder->Output(outputDoc); // Add extensions and extras to bufferViews, if any for (auto bufferView : document.bufferViews.Elements()) { auto fixedBufferView = outputDoc.bufferViews.Get(bufferView.id); fixedBufferView.extensions = bufferView.extensions; fixedBufferView.extras = bufferView.extras; outputDoc.bufferViews.Replace(fixedBufferView); } // We may have put the bufferViews in the IndexedContainer out of order sort them now. auto finalBufferViewList = outputDoc.bufferViews; outputDoc.bufferViews.Clear(); for (size_t i = 0; i < finalBufferViewList.Size(); i++) { outputDoc.bufferViews.Append(finalBufferViewList[std::to_string(i)]); } auto manifest = Serialize(outputDoc, KHR::GetKHRExtensionSerializer()); auto outputWriter = dynamic_cast(&builder->GetResourceWriter()); if (outputWriter != nullptr) { outputWriter->Flush(manifest, std::string()); } } void Microsoft::glTF::Toolkit::SerializeBinary(const Document& document, std::shared_ptr inputStreamReader, std::shared_ptr outputStreamWriter, const AccessorConversionStrategy& accessorConversion) { SerializeBinary(document, GLTFResourceReader{inputStreamReader}, std::move(outputStreamWriter), accessorConversion); } ================================================ FILE: glTF-Toolkit/src/pch.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" ================================================ FILE: glTF-Toolkit.Test/GLBSerializerTests.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" #include #include "GLTFSDK/IStreamWriter.h" #include "GLTFSDK/Constants.h" #include "GLTFSDK/Deserialize.h" #include "GLTFSDK/GLBResourceWriter.h" #include "GLTFSDK/GLBResourceReader.h" #include "GLTFSDK/GLTFResourceWriter.h" #include "SerializeBinary.h" #include "Helpers/TestUtils.h" #include "Helpers/WStringUtils.h" #include "Helpers/StreamMock.h" using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace Microsoft::glTF; using namespace Microsoft::glTF::Toolkit; namespace Microsoft::glTF::Toolkit::Test { class InMemoryStream : public Microsoft::glTF::IStreamWriter { public: InMemoryStream(std::shared_ptr stream) : m_stream(stream) { } std::shared_ptr GetOutputStream(const std::string&) const { return m_stream; } private: std::shared_ptr m_stream; }; TEST_CLASS(GLBSerializerTests) { static std::string ReadLocalJson(const char * relativePath) { auto input = TestUtils::ReadLocalAsset(TestUtils::GetAbsolutePath(relativePath)); auto json = std::string(std::istreambuf_iterator(*input), std::istreambuf_iterator()); return json; } static std::shared_ptr ImportGLB(const std::shared_ptr& streamReader, const std::shared_ptr& glbStream) { GLBResourceReader resourceReader(streamReader, glbStream); auto json = resourceReader.GetJson(); auto doc = Deserialize(json); return std::make_shared(doc); } static std::shared_ptr ImportGLTF(const std::shared_ptr& streamReader, const std::shared_ptr& stream) { GLTFResourceReader resourceReader(streamReader); auto json = std::string(std::istreambuf_iterator(*stream), std::istreambuf_iterator()); auto doc = Deserialize(json); return std::make_shared(doc); } const char* c_waterBottleJson = "Resources\\gltf\\WaterBottle\\WaterBottle.gltf"; TEST_METHOD(GLBSerializerTests_RoundTrip_Simple) { auto data = ReadLocalJson(c_waterBottleJson); auto input = std::make_shared(data); try { // Deserialize input json auto inputJson = std::string(std::istreambuf_iterator(*input), std::istreambuf_iterator()); auto doc = Deserialize(inputJson); // Serialize Document to GLB auto streamReader = std::make_shared(TestUtils::GetAbsolutePath(c_waterBottleJson)); auto stream = std::make_shared(std::ios_base::app | std::ios_base::binary | std::ios_base::in | std::ios_base::out); SerializeBinary(doc, streamReader, std::make_shared(stream)); // Deserialize the GLB again auto glbReader = std::make_unique(streamReader, stream); auto outputJson = glbReader->GetJson(); auto outputDoc = Deserialize(outputJson); // Check some structural elements Assert::AreEqual(doc.nodes.Size(), outputDoc.nodes.Size()); Assert::AreEqual(doc.images.Size(), outputDoc.images.Size()); // There must be only one buffer, and it can't have a URI Assert::AreEqual(static_cast(1), outputDoc.buffers.Size()); auto glbBuffer = outputDoc.buffers.Elements()[0]; Assert::IsTrue(glbBuffer.uri.empty()); // Check that the images that were stored as URI are now bufferViews for (auto image : outputDoc.images.Elements()) { // Images in GLB don't have a URI Assert::IsTrue(image.uri.empty()); // Images in GLB are stored in a buffer Assert::IsFalse(image.bufferViewId.empty()); // Images in original GLTF have a URI Assert::IsFalse(doc.images.Get(image.id).uri.empty()); } // All buffer views must point to the GLB buffer for (auto bufferView : outputDoc.bufferViews.Elements()) { Assert::IsTrue(bufferView.bufferId == glbBuffer.id); } // Read one of the images and check it's identical auto gltfReader = std::make_unique(streamReader); std::vector gltfImage = gltfReader->ReadBinaryData(doc, doc.images.Elements()[0]); std::vector glbImage = glbReader->ReadBinaryData(outputDoc, outputDoc.images.Elements()[0]); Assert::IsTrue(gltfImage == glbImage); // Vector comparison } catch (std::exception ex) { std::stringstream ss; ss << "Received exception was unexpected. Got: " << ex.what(); Assert::Fail(WStringUtils::ToWString(ss).c_str()); } } }; } ================================================ FILE: glTF-Toolkit.Test/GLBtoGLTFTests.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" #include #include #include #include #include #include using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace Microsoft::glTF; using namespace Microsoft::glTF::Toolkit; namespace Microsoft::glTF::Toolkit::Test { std::wstring utf8Decode(const std::string& s) { std::wstring ret; std::for_each(s.begin(), s.end(), [&ret](char c) { ret += (wchar_t)c; }); return ret; } std::string binBufferString(const std::vector& vec) { std::string ret = "{"; for (size_t i = 0; i < vec.size(); i++) { ret += (i ? "," : "") + std::to_string((int)vec[i]); } return ret += "}"; } // Setup a GLTF document with 3 bufferviews and 2 images Document setupGLBDocument1() { Document glbDoc; Scene sc; sc.id = "0"; glbDoc.scenes.Append(std::move(sc)); Accessor acc0; acc0.bufferViewId = "0"; acc0.byteOffset = 0; acc0.id = "0"; Accessor acc1; acc1.bufferViewId = "2"; acc1.byteOffset = 12; acc1.id = "1"; Accessor acc2; acc2.bufferViewId = "1"; acc2.byteOffset = 4; acc2.id = "2"; const std::vector accessors = { acc0, acc1, acc2 }; std::for_each(accessors.begin(), accessors.end(), [&glbDoc](auto a) { glbDoc.accessors.Append(std::move(a)); }); BufferView bv0; bv0.bufferId = "0"; bv0.byteOffset = 0; bv0.byteLength = 8; bv0.id = "0"; BufferView bv1; bv1.bufferId = "0"; bv1.byteOffset = 32; bv1.byteLength = 4; bv1.id = "1"; BufferView bv2; bv2.bufferId = "0"; bv2.byteOffset = 72; bv2.byteLength = 2; bv2.id = "2"; const std::vector bufferViews = { bv0, bv1, bv2 }; std::for_each(bufferViews.begin(), bufferViews.end(), [&glbDoc](auto b) { glbDoc.bufferViews.Append(std::move(b)); }); Buffer b; b.id = "0", b.byteLength = 100; const std::vector buffers = { b }; std::for_each(buffers.begin(), buffers.end(), [&glbDoc](auto b) { glbDoc.buffers.Append(std::move(b)); }); Image img0; img0.id = "0"; img0.mimeType = "image/png"; img0.bufferViewId = "1"; Image img1; img1.id = "1"; img1.mimeType = "image/jpeg"; img1.bufferViewId = "2"; const std::vector images = { img0, img1 }; std::for_each(images.begin(), images.end(), [&glbDoc](auto img) { glbDoc.images.Append(std::move(img)); }); return glbDoc; } //sets up a stream with 'size' number of bytes, where reading the stream k times returns k std::stringstream* setupGLBStream(char size) { std::stringstream* ss = new std::stringstream(); char* inputStream = new char[size]; for (int i = 0; i < size; i++) inputStream[i] = (char)i; ss->write(inputStream, size); return ss; } TEST_CLASS(GLBToGLTFTests) { TEST_METHOD(GLBtoGLTF_NoImagesJSON) { Document glbDoc; Scene s1; s1.id = "0"; glbDoc.scenes.Append(std::move(s1)); Accessor acc; acc.bufferViewId = "0"; acc.byteOffset = 36; acc.id = "0"; const std::vector accessors = {}; std::for_each(accessors.begin(), accessors.end(), [&glbDoc](auto a) { glbDoc.accessors.Append(std::move(a)); }); const std::vector bufferViews = {}; std::for_each(bufferViews.begin(), bufferViews.end(), [&glbDoc](auto b) { glbDoc.bufferViews.Append(std::move(b)); }); const std::vector buffers = {}; std::for_each(buffers.begin(), buffers.end(), [&glbDoc](auto b) { glbDoc.buffers.Append(std::move(b)); }); const std::vector images = {}; std::for_each(images.begin(), images.end(), [&glbDoc](auto img) { glbDoc.images.Append(std::move(img)); }); Document expectedGLTFDoc; Scene s2; s2.id = "0"; expectedGLTFDoc.scenes.Append(std::move(s2)); std::unordered_set unpackedBufferViews; auto actualGLTFDoc = GLBToGLTF::CreateGLTFDocument(glbDoc, "name", unpackedBufferViews); // for debugging const auto expectedJSON = Serialize(expectedGLTFDoc, SerializeFlags::Pretty); const auto actualJSON = Serialize(actualGLTFDoc, SerializeFlags::Pretty); Assert::IsTrue(expectedGLTFDoc == actualGLTFDoc, utf8Decode(expectedJSON + "\n\n" + actualJSON).c_str()); } TEST_METHOD(GLBtoGLTF_ImagesWithOffsetJSON) { Document glbDoc; Scene sc; sc.id = "0"; glbDoc.scenes.Append(std::move(sc)); Accessor acc0; acc0.bufferViewId = "0"; acc0.byteOffset = 0; acc0.id = "0"; Accessor acc1; acc1.bufferViewId = "3"; acc1.byteOffset = 12; acc1.id = "1"; Accessor acc2; acc2.bufferViewId = "1"; acc2.byteOffset = 4; acc2.id = "2"; Accessor acc3; acc3.bufferViewId = "2"; acc3.byteOffset = 4; acc3.id = "3"; const std::vector accessors = { acc0, acc1, acc2, acc3 }; std::for_each(accessors.begin(), accessors.end(), [&glbDoc](auto a) { glbDoc.accessors.Append(std::move(a)); }); BufferView bv0; bv0.bufferId = "0"; bv0.byteOffset = 0; bv0.byteLength = 400; bv0.id = "0"; BufferView bv1; bv1.bufferId = "0"; bv1.byteOffset = 420; bv1.byteLength = 200; bv1.id = "1"; BufferView bv2; bv2.bufferId = "0"; bv2.byteOffset = 620; bv2.byteLength = 320; bv2.id = "2"; BufferView bv3; bv3.bufferId = "0"; bv3.byteOffset = 960; bv3.byteLength = 2000; bv3.id = "3"; const std::vector bufferViews = { bv0, bv1, bv2, bv3 }; std::for_each(bufferViews.begin(), bufferViews.end(), [&glbDoc](auto b) { glbDoc.bufferViews.Append(std::move(b)); }); Buffer b; b.id = "0", b.byteLength = 3000; const std::vector buffers = { b }; std::for_each(buffers.begin(), buffers.end(), [&glbDoc](auto b) { glbDoc.buffers.Append(std::move(b)); }); Image img0; img0.id = "0"; img0.mimeType = "image/png"; img0.bufferViewId = "1"; Image img1; img1.id = "1"; img1.mimeType = "image/jpeg"; img1.bufferViewId = "3"; const std::vector images = { img0, img1 }; std::for_each(images.begin(), images.end(), [&glbDoc](auto img) { glbDoc.images.Append(std::move(img)); }); std::unordered_set unpackedBufferViews; auto actualGLTFDoc = GLBToGLTF::CreateGLTFDocument(glbDoc, "test", unpackedBufferViews); Document expectedGLTFDoc; Accessor exp_acc0; exp_acc0.bufferViewId = "0"; exp_acc0.byteOffset = 0; exp_acc0.id = "0"; Accessor exp_acc1; exp_acc1.bufferViewId = "1"; exp_acc1.byteOffset = 4; exp_acc1.id = "3"; expectedGLTFDoc.accessors.Append(std::move(exp_acc0)); expectedGLTFDoc.accessors.Append(std::move(exp_acc1)); BufferView exp_bv0; exp_bv0.bufferId = "0"; exp_bv0.byteOffset = 0; exp_bv0.byteLength = 400; exp_bv0.id = "0"; BufferView exp_bv1; exp_bv1.bufferId = "0"; exp_bv1.byteOffset = 400; exp_bv1.byteLength = 320; exp_bv1.id = "1"; expectedGLTFDoc.bufferViews.Append(std::move(exp_bv0)); expectedGLTFDoc.bufferViews.Append(std::move(exp_bv1)); Image exp_img0; exp_img0.id = "0"; exp_img0.uri = "test_image0.png"; Image exp_img1; exp_img1.id = "1"; exp_img1.uri = "test_image1.jpg"; expectedGLTFDoc.images.Append(std::move(exp_img0)); expectedGLTFDoc.images.Append(std::move(exp_img1)); Buffer exp_b; exp_b.id = "0"; exp_b.byteLength = 720; exp_b.uri = "test.bin"; expectedGLTFDoc.buffers.Append(std::move(exp_b)); Scene sc2; sc2.id = "0"; expectedGLTFDoc.scenes.Append(std::move(sc2)); // for debugging const auto expectedJSON = Serialize(expectedGLTFDoc, SerializeFlags::Pretty); const auto actualJSON = Serialize(actualGLTFDoc, SerializeFlags::Pretty); Assert::IsTrue(expectedGLTFDoc == actualGLTFDoc, utf8Decode(expectedJSON + "\n\n" + actualJSON).c_str()); } TEST_METHOD(GLBtoGLTF_ImageDataTest) { auto glbDoc = setupGLBDocument1(); auto glbStream = setupGLBStream(100); const std::string TEST_NAME = "test3"; const size_t BYTE_OFFSET = 12; auto imageData = GLBToGLTF::GetImagesData(glbStream, glbDoc, TEST_NAME, BYTE_OFFSET); delete glbStream; // these bytes corresponds to bytes of image0 and image1 in setupGLBDocument1 std::vector> expectedImage = { {BYTE_OFFSET + 32, BYTE_OFFSET + 33, BYTE_OFFSET + 34, BYTE_OFFSET + 35}, {BYTE_OFFSET + 72, BYTE_OFFSET + 73}, }; int imgId = 0; for (auto it = imageData.begin(); it != imageData.end(); it++, imgId++) { Assert::IsTrue(it->second == expectedImage[imgId], utf8Decode(binBufferString(it->second) + '\n' + binBufferString(expectedImage[imgId])).c_str()); } } TEST_METHOD(GLBtoGLTF_MeshDataTest) { auto glbDoc = setupGLBDocument1(); auto glbStream = setupGLBStream(100); const size_t BYTE_OFFSET = 12; std::unordered_set unpackedBufferViews; auto outputDoc = GLBToGLTF::CreateGLTFDocument(glbDoc, "name", unpackedBufferViews); auto actualData = GLBToGLTF::SaveBin(glbStream, glbDoc, BYTE_OFFSET, 8, unpackedBufferViews); //these bytes correspond to bytes of bufferviews in steupGLTFDocument1 which don't belong to any image std::vector expectedData = { BYTE_OFFSET + 0, BYTE_OFFSET + 1, BYTE_OFFSET + 2, BYTE_OFFSET + 3, BYTE_OFFSET + 4, BYTE_OFFSET + 5, BYTE_OFFSET + 6, BYTE_OFFSET + 7 }; Assert::IsTrue(actualData == expectedData, utf8Decode(binBufferString(actualData) + '\n' + binBufferString(expectedData)).c_str()); } }; } ================================================ FILE: glTF-Toolkit.Test/GLTFLODUtilsTests.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" #include #include "GLTFSDK/IStreamWriter.h" #include "GLTFSDK/Constants.h" #include "GLTFSDK/Serialize.h" #include "GLTFSDK/Deserialize.h" #include "GLTFSDK/GLBResourceReader.h" #include "GLTFSDK/GLTFResourceWriter.h" #include "GLTFSDK/RapidJsonUtils.h" #include "GLTFSDK/ExtensionsKHR.h" #include "GLTFLODUtils.h" #include "Helpers/WStringUtils.h" #include "Helpers/TestUtils.h" using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace Microsoft::glTF; using namespace Microsoft::glTF::Toolkit; namespace Microsoft::glTF::Toolkit::Test { TEST_CLASS(GLTFLODUtilsTests) { static void CheckGLTFLODNodeCountAgainstOriginal(Document& doc, Document& docWLod, size_t lodCount) { // All elements in the lod'd doc should be double the original Assert::IsTrue(doc.buffers.Size() * lodCount == docWLod.buffers.Size()); Assert::IsTrue(doc.accessors.Size() * lodCount == docWLod.accessors.Size()); Assert::IsTrue(doc.bufferViews.Size() * lodCount == docWLod.bufferViews.Size()); Assert::IsTrue(doc.materials.Size() * lodCount == docWLod.materials.Size()); Assert::IsTrue(doc.images.Size() * lodCount == docWLod.images.Size()); Assert::IsTrue(doc.meshes.Size() * lodCount == docWLod.meshes.Size()); Assert::IsTrue(doc.nodes.Size() * lodCount == docWLod.nodes.Size()); Assert::IsTrue(doc.textures.Size() * lodCount == docWLod.textures.Size()); Assert::IsTrue(doc.samplers.Size() * lodCount == docWLod.samplers.Size()); // Scene count should be untouched Assert::IsTrue(doc.scenes.Size() == docWLod.scenes.Size()); } static void CheckGLTFLODCount(const char * gltfDocPath, uint32_t expectedNumberOfLods) { auto input = TestUtils::ReadLocalAsset(TestUtils::GetAbsolutePath(gltfDocPath)); auto readwriter = std::make_shared(); try { GLTFResourceReader resourceReader(readwriter); auto inputJson = std::string(std::istreambuf_iterator(*input), std::istreambuf_iterator()); auto doc = Deserialize(inputJson, KHR::GetKHRExtensionDeserializer()); auto lods = GLTFLODUtils::ParseDocumentNodeLODs(doc); Assert::IsTrue(GLTFLODUtils::NumberOfNodeLODLevels(doc, lods) == expectedNumberOfLods); } catch (std::exception ex) { std::stringstream ss; ss << "Received exception was unexpected. Got: " << ex.what(); Assert::Fail(WStringUtils::ToWString(ss).c_str()); } } static std::shared_ptr ImportGLTF(const std::shared_ptr& streamReader, const std::shared_ptr& stream) { GLTFResourceReader resourceReader(streamReader); auto json = std::string(std::istreambuf_iterator(*stream), std::istreambuf_iterator()); auto doc = Deserialize(json, KHR::GetKHRExtensionDeserializer()); return std::make_shared(doc); } const char* c_cubeAsset3DJson = "Resources\\gltf\\cubeAsset3D.gltf"; const char* c_cubeWithLODJson = "Resources\\gltf\\cubeWithLOD.gltf"; TEST_METHOD(GLTFLODUtils_NodeLodCount) { CheckGLTFLODCount(c_cubeAsset3DJson, 0); } TEST_METHOD(GLTFLODUtils_NodeLodCount_DocWithLODs) { CheckGLTFLODCount(c_cubeWithLODJson, 1); } TEST_METHOD(GLTFLODUtils_GLTFNodeLODMerge) { auto input = TestUtils::ReadLocalAsset(TestUtils::GetAbsolutePath(c_cubeAsset3DJson)); auto readwriter = std::make_shared(); try { // Deserialize input json GLTFResourceReader resourceReader(readwriter); auto inputJson = std::string(std::istreambuf_iterator(*input), std::istreambuf_iterator()); auto doc = Deserialize(inputJson, KHR::GetKHRExtensionDeserializer()); std::vector docs; docs.push_back(doc); docs.push_back(doc); auto newlodgltfDoc = GLTFLODUtils::MergeDocumentsAsLODs(docs); // Serialize Document back to json auto outputJson = Serialize(newlodgltfDoc, KHR::GetKHRExtensionSerializer()); CheckGLTFLODNodeCountAgainstOriginal(doc, newlodgltfDoc, 2); // Check Node Lods are correctly stored and labelled in the document auto nodes = newlodgltfDoc.nodes.Elements(); auto lods = GLTFLODUtils::ParseDocumentNodeLODs(newlodgltfDoc); bool validLodExtension = false; bool containsLOD1RootNode = false; bool containsLOD1PolyNode = false; for (auto node : nodes) { if (node.name == "root" && (std::find(lods[node.id]->begin(), lods[node.id]->end(), "3") != lods[node.id]->end())) { validLodExtension = true; } if (node.name == "root_lod1") { containsLOD1RootNode = true; } if (node.name == "polygon_lod1") { containsLOD1PolyNode = true; } } Assert::IsTrue(validLodExtension); Assert::IsTrue(containsLOD1RootNode); Assert::IsTrue(containsLOD1PolyNode); } catch (std::exception ex) { std::stringstream ss; ss << "Received exception was unexpected. Got: " << ex.what(); Assert::Fail(WStringUtils::ToWString(ss).c_str()); } } TEST_METHOD(GLTFLODUTils_GLTFNodeLODMergeMultiple) { auto input = TestUtils::ReadLocalAsset(TestUtils::GetAbsolutePath(c_cubeAsset3DJson)); auto readwriter = std::make_shared(); try { // Deserialize input json GLTFResourceReader resourceReader(readwriter); auto inputJson = std::string(std::istreambuf_iterator(*input), std::istreambuf_iterator()); auto doc = Deserialize(inputJson, KHR::GetKHRExtensionDeserializer()); std::vector docs; docs.push_back(doc); docs.push_back(doc); docs.push_back(doc); auto newlodgltfDoc = GLTFLODUtils::MergeDocumentsAsLODs(docs); CheckGLTFLODNodeCountAgainstOriginal(doc, newlodgltfDoc, 3); // Check Node Lods are correctly stored and labelled in the document auto nodes = newlodgltfDoc.nodes.Elements(); auto lods = GLTFLODUtils::ParseDocumentNodeLODs(newlodgltfDoc); bool validLodExtension = false; bool containsLOD1RootNode = false; bool containsLOD1PolyNode = false; bool containsLOD2RootNode = false; bool containsLOD2PolyNode = false; for (auto node : nodes) { if (node.name == "root" && (std::find(lods[node.id]->begin(), lods[node.id]->end(), "3") != lods[node.id]->end()) && (std::find(lods[node.id]->begin(), lods[node.id]->end(), "5") != lods[node.id]->end()) ) { validLodExtension = true; } if (node.name == "root_lod1") containsLOD1RootNode = true; if (node.name == "polygon_lod1") containsLOD1PolyNode = true; if (node.name == "root_lod2") containsLOD2RootNode = true; if (node.name == "polygon_lod2") containsLOD2PolyNode = true; } Assert::IsTrue(validLodExtension); Assert::IsTrue(containsLOD1RootNode); Assert::IsTrue(containsLOD1PolyNode); Assert::IsTrue(containsLOD2RootNode); Assert::IsTrue(containsLOD2PolyNode); // Serialize Document back to json auto outputJson = Serialize(newlodgltfDoc, KHR::GetKHRExtensionSerializer()); } catch (std::exception ex) { std::stringstream ss; ss << "Received exception was unexpected. Got: " << ex.what(); Assert::Fail(WStringUtils::ToWString(ss).c_str()); } } TEST_METHOD(GLTFLODUtils_GLTFNodeLODMergeScreenCoverage) { auto input = TestUtils::ReadLocalAsset(TestUtils::GetAbsolutePath(c_cubeAsset3DJson)); auto readwriter = std::make_shared(); try { // Deserialize input json GLTFResourceReader resourceReader(readwriter); auto inputJson = std::string(std::istreambuf_iterator(*input), std::istreambuf_iterator()); auto doc = Deserialize(inputJson, KHR::GetKHRExtensionDeserializer()); std::vector docs; docs.push_back(doc); docs.push_back(doc); docs.push_back(doc); std::vector screenCoverages{ 0.5, 0.2, 0.01 }; auto newlodgltfDoc = GLTFLODUtils::MergeDocumentsAsLODs(docs, screenCoverages); CheckGLTFLODNodeCountAgainstOriginal(doc, newlodgltfDoc, 3); // Check Node Lods have correct screen coverage values auto nodes = newlodgltfDoc.nodes.Elements(); bool rootNodeContainsCoverage = false; for (auto node : nodes) { if (node.name == "root" && !node.extras.empty()) { auto extrasJson = RapidJsonUtils::CreateDocumentFromString(node.extras); Assert::IsTrue(extrasJson.IsObject()); Assert::IsTrue(extrasJson["MSFT_screencoverage"].IsArray()); Assert::IsTrue(extrasJson["MSFT_screencoverage"].GetArray().Size() == 3); rootNodeContainsCoverage = true; } } Assert::IsTrue(rootNodeContainsCoverage); // Serialize Document back to json auto outputJson = Serialize(newlodgltfDoc, KHR::GetKHRExtensionSerializer()); } catch (std::exception ex) { std::stringstream ss; ss << "Received exception was unexpected. Got: " << ex.what(); Assert::Fail(WStringUtils::ToWString(ss).c_str()); } } TEST_METHOD(GLTFLODUtils_DeserialiseNodeLODExtension) { auto input = TestUtils::ReadLocalAsset(TestUtils::GetAbsolutePath(c_cubeWithLODJson)); auto readwriter = std::make_shared(); try { auto gltfDoc = ImportGLTF(readwriter, input); auto nodes = gltfDoc->nodes.Elements(); Assert::IsTrue(nodes.size() == 4); auto lods = GLTFLODUtils::ParseDocumentNodeLODs(*gltfDoc); bool validLodExtension = false; for (auto node : nodes) { if (node.name == "root" && (std::find(lods[node.id]->begin(), lods[node.id]->end(), "3") != lods[node.id]->end())) { validLodExtension = true; break; } } Assert::IsTrue(validLodExtension); } catch (std::exception ex) { std::stringstream ss; ss << "Received exception was unexpected. Got: " << ex.what(); Assert::Fail(WStringUtils::ToWString(ss).c_str()); } } TEST_METHOD(GLTFLODUtils_DeserializeSerializeLoopNodeLODExtension) { auto input = TestUtils::ReadLocalAsset(TestUtils::GetAbsolutePath(c_cubeWithLODJson)); auto readwriter = std::make_shared(); try { // Deserialize input json GLTFResourceReader resourceReader(readwriter); auto inputJson = std::string(std::istreambuf_iterator(*input), std::istreambuf_iterator()); auto doc = Deserialize(inputJson, KHR::GetKHRExtensionDeserializer()); // Serialize Document back to json auto outputJson = Serialize(doc, KHR::GetKHRExtensionSerializer()); auto outputDoc = Deserialize(outputJson, KHR::GetKHRExtensionDeserializer()); // Compare input and output GLTFDocuments Assert::AreNotSame(doc == outputDoc, true, L"Input gltf and output gltf are not equal"); // Specifically ensure Node LODs are preserved through de/serialization loop auto nodes = outputDoc.nodes.Elements(); Assert::IsTrue(nodes.size() == 4); auto lods = GLTFLODUtils::ParseDocumentNodeLODs(outputDoc); bool validLodExtension = false; for (auto node : nodes) { if (node.name == "root" && (std::find(lods[node.id]->begin(), lods[node.id]->end(), "3") != lods[node.id]->end())) { validLodExtension = true; break; } } Assert::IsTrue(validLodExtension); } catch (std::exception ex) { std::stringstream ss; ss << "Received exception was unexpected. Got: " << ex.what(); Assert::Fail(WStringUtils::ToWString(ss).c_str()); } } }; } ================================================ FILE: glTF-Toolkit.Test/GLTFTextureCompressionUtilsTests.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" #include #include "GLTFSDK/IStreamWriter.h" #include "GLTFSDK/Constants.h" #include "GLTFSDK/Serialize.h" #include "GLTFSDK/Deserialize.h" #include "GLTFSDK/GLBResourceReader.h" #include "GLTFSDK/GLTFResourceWriter.h" #include "GLTFTextureCompressionUtils.h" #include "Helpers/WStringUtils.h" #include "Helpers/StreamMock.h" #include "Helpers/TestUtils.h" #include using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace Microsoft::glTF; using namespace Microsoft::glTF::Toolkit; namespace Microsoft::glTF::Toolkit::Test { // Note: some tests are using BC3 since it's faster to run that algorithm vs. BC7 TEST_CLASS(GLTFTextureCompressionUtilsTests) { const char* c_baseColorPng = "Resources\\gltf\\WaterBottle_ORM\\WaterBottle_baseColor.png"; const char* c_baseColorBC7 = "Resources\\gltf\\WaterBottle_ORM\\WaterBottle_baseColor.DDS"; const char* c_waterBottleORMJson = "Resources\\gltf\\WaterBottle_ORM\\WaterBottle.gltf"; const char* c_nonMultipleOf4TextureJson = "Resources\\gltf\\TextureTest\\TextureTest.gltf"; TEST_METHOD(GLTFTextureCompressionUtils_CompressImage_BC7) { // Load png auto png = TestUtils::ReadLocalAsset(TestUtils::GetAbsolutePath(c_baseColorPng)); std::vector pngData = StreamUtils::ReadBinaryFull(*png); // ddsImage <= load DDS DirectX::ScratchImage ddsImage; DirectX::TexMetadata info; DirectX::LoadFromDDSFile(TestUtils::GetAbsolutePathW(c_baseColorBC7).c_str(), DirectX::WIC_FLAGS_NONE, &info, ddsImage); // compressedPng <= convert using BC7 DirectX::ScratchImage compressedPng; DirectX::LoadFromWICMemory(pngData.data(), pngData.size(), DirectX::WIC_FLAGS_NONE, &info, compressedPng); GLTFTextureCompressionUtils::CompressImage(compressedPng, TextureCompression::BC7); auto ddsMip0 = ddsImage.GetImage(0, 0, 0); size_t ddsImageSize = ddsMip0->height * ddsMip0->width; Assert::AreEqual(ddsImageSize, compressedPng.GetPixelsSize(), L"ddsImage and compressedPng lengths are not the same"); Assert::IsTrue(memcmp(ddsMip0->pixels, compressedPng.GetPixels(), ddsImageSize), L"ddsImage and compressedPng are not the same"); } TEST_METHOD(GLTFTextureCompressionUtils_CompressTextureAsDDS_NoCompression) { // This asset has all textures TestUtils::LoadAndExecuteGLTFTest(c_waterBottleORMJson, [](auto doc, auto path) { auto compressedDoc = GLTFTextureCompressionUtils::CompressTextureAsDDS(std::make_shared(path), doc, doc.textures.Get("0"), TextureCompression::None, ""); // Check that nothing changed Assert::IsTrue(doc == compressedDoc); }); } TEST_METHOD(GLTFTextureCompressionUtils_CompressTextureAsDDS_CompressBC3_NoMips_Retain) { // This asset has all textures TestUtils::LoadAndExecuteGLTFTest(c_waterBottleORMJson, [](auto doc, auto path) { auto maxTextureSize = std::numeric_limits::max(); auto generateMipMaps = false; auto retainOriginalImages = true; auto compressedDoc = GLTFTextureCompressionUtils::CompressTextureAsDDS(std::make_shared(path), doc, doc.textures.Get("0"), TextureCompression::BC3, "", maxTextureSize, generateMipMaps, retainOriginalImages); auto originalTexture = doc.textures.Get("0"); auto compressedTexture = compressedDoc.textures.Get("0"); // Check that the image has not been replaced Assert::IsTrue(originalTexture.imageId == compressedTexture.imageId); // Check that the image has been added Assert::IsTrue(doc.images.Size() + 1 == compressedDoc.images.Size()); // Check that the texture now has the extension Assert::IsTrue(originalTexture.extensions.size() + 1 == compressedTexture.extensions.size()); // Check the new extension is not empty auto ddsExtension = compressedTexture.extensions.at(std::string(EXTENSION_MSFT_TEXTURE_DDS)); Assert::IsFalse(ddsExtension.empty()); // Check the new extension contains a DDS image rapidjson::Document ddsJson; ddsJson.Parse(ddsExtension.c_str()); Assert::IsTrue(ddsJson["source"].IsInt()); auto ddsImageId = std::to_string(ddsJson["source"].GetInt()); Assert::IsTrue(compressedDoc.images.Get(ddsImageId).mimeType == "image/vnd-ms.dds"); Assert::IsTrue(compressedDoc.images.Get(ddsImageId).uri == "texture_0_nomips_BC3.dds"); }); } TEST_METHOD(GLTFTextureCompressionUtils_CompressTextureAsDDS_CompressBC3_NoMips_Replace) { // This asset has all textures TestUtils::LoadAndExecuteGLTFTest(c_waterBottleORMJson, [](auto doc, auto path) { auto maxTextureSize = std::numeric_limits::max(); auto generateMipMaps = false; auto retainOriginalImages = false; auto compressedDoc = GLTFTextureCompressionUtils::CompressTextureAsDDS(std::make_shared(path), doc, doc.textures.Get("0"), TextureCompression::BC3, "", maxTextureSize, generateMipMaps, retainOriginalImages); auto originalTexture = doc.textures.Get("0"); auto compressedTexture = compressedDoc.textures.Get("0"); // Check that the texture is still pointing to the same image Assert::IsTrue(originalTexture.imageId == compressedTexture.imageId); // Check that an image has not been added Assert::IsTrue(doc.images.Size() == compressedDoc.images.Size()); // Check that the texture now has the extension Assert::IsTrue(originalTexture.extensions.size() + 1 == compressedTexture.extensions.size()); // Check the new extension is not empty auto ddsExtension = compressedTexture.extensions.at(std::string(EXTENSION_MSFT_TEXTURE_DDS)); Assert::IsFalse(ddsExtension.empty()); // Check the new extension contains a DDS image rapidjson::Document ddsJson; ddsJson.Parse(ddsExtension.c_str()); Assert::IsTrue(ddsJson["source"].IsInt()); auto ddsImageId = std::to_string(ddsJson["source"].GetInt()); Assert::IsTrue(compressedDoc.images.Get(ddsImageId).mimeType == "image/vnd-ms.dds"); Assert::IsTrue(compressedDoc.images.Get(ddsImageId).uri == "texture_0_nomips_BC3.dds"); // Check the extension points to the same image as the source (image was replaced) Assert::AreEqual(compressedTexture.imageId, ddsImageId); }); } TEST_METHOD(GLTFTextureCompressionUtils_CompressTextureAsDDS_CompressBC7_Mips_Retain) { // This asset has all textures TestUtils::LoadAndExecuteGLTFTest(c_waterBottleORMJson, [](auto doc, auto path) { auto maxTextureSize = std::numeric_limits::max(); auto generateMipMaps = true; auto retainOriginalImages = true; auto compressedDoc = GLTFTextureCompressionUtils::CompressTextureAsDDS(std::make_shared(path), doc, doc.textures.Get("0"), TextureCompression::BC7, "", maxTextureSize, generateMipMaps, retainOriginalImages); auto originalTexture = doc.textures.Get("0"); auto compressedTexture = compressedDoc.textures.Get("0"); // Check that the image has not been replaced Assert::IsTrue(originalTexture.imageId == compressedTexture.imageId); // Check that the image has been added Assert::IsTrue(doc.images.Size() + 1 == compressedDoc.images.Size()); // Check that the texture now has the extension Assert::IsTrue(originalTexture.extensions.size() + 1 == compressedTexture.extensions.size()); // Check the new extension is not empty auto ddsExtension = compressedTexture.extensions.at(std::string(EXTENSION_MSFT_TEXTURE_DDS)); Assert::IsFalse(ddsExtension.empty()); // Check the new extension contains a DDS image rapidjson::Document ddsJson; ddsJson.Parse(ddsExtension.c_str()); Assert::IsTrue(ddsJson["source"].IsInt()); auto ddsImageId = std::to_string(ddsJson["source"].GetInt()); Assert::IsTrue(compressedDoc.images.Get(ddsImageId).mimeType == "image/vnd-ms.dds"); Assert::IsTrue(compressedDoc.images.Get(ddsImageId).uri == "texture_0_BC7.dds"); }); } TEST_METHOD(GLTFTextureCompressionUtils_CompressAllTexturesForWindowsMR_Retain) { // This asset has all textures TestUtils::LoadAndExecuteGLTFTest(c_waterBottleORMJson, [](auto doc, auto path) { auto maxTextureSize = std::numeric_limits::max(); auto retainOriginalImages = true; auto compressedDoc = GLTFTextureCompressionUtils::CompressAllTexturesForWindowsMR(std::make_shared(path), doc, "", maxTextureSize, retainOriginalImages); // Check that the materials and textures have not been replaced // Check that the textures has not been replaced Assert::IsTrue(doc.textures.Size() == compressedDoc.textures.Size()); Assert::IsTrue(doc.materials.Size() == compressedDoc.materials.Size()); // Check that the images have been added (base, emissive, RMO and normal) Assert::AreEqual(doc.images.Size() + 4, compressedDoc.images.Size()); auto originalMaterial = doc.materials.Get("0"); auto compressedMaterial = compressedDoc.materials.Get("0"); // Check that all relevant textures now have the extension Assert::IsTrue(doc.textures.Get(originalMaterial.metallicRoughness.baseColorTexture.textureId).extensions.size() + 1 == compressedDoc.textures.Get(compressedMaterial.metallicRoughness.baseColorTexture.textureId).extensions.size()); Assert::IsTrue(doc.textures.Get(originalMaterial.emissiveTexture.textureId).extensions.size() + 1 == compressedDoc.textures.Get(compressedMaterial.emissiveTexture.textureId).extensions.size()); // TODO: read the WMR (MSFT_packing...) textures as well // Check the new extension is not empty auto ddsExtension = compressedDoc.textures.Get(compressedMaterial.emissiveTexture.textureId).extensions.at(std::string(EXTENSION_MSFT_TEXTURE_DDS)); Assert::IsFalse(ddsExtension.empty()); // Check the new extension contains a DDS image rapidjson::Document ddsJson; ddsJson.Parse(ddsExtension.c_str()); Assert::IsTrue(ddsJson["source"].IsInt()); auto ddsImageId = std::to_string(ddsJson["source"].GetInt()); Assert::IsTrue(compressedDoc.images.Get(ddsImageId).mimeType == "image/vnd-ms.dds"); std::string expectedSuffix = "_BC7.dds"; Assert::IsTrue(compressedDoc.images.Get(ddsImageId).uri.compare(9, expectedSuffix.size(), expectedSuffix) == 0); // The emissive texture should have mips and be BC7 }); } TEST_METHOD(GLTFTextureCompressionUtils_CompressTextureAsDDS_NotMultipleOf4) { // This asset has all textures TestUtils::LoadAndExecuteGLTFTest(c_nonMultipleOf4TextureJson, [](auto doc, auto path) { auto maxTextureSize = std::numeric_limits::max(); auto generateMipMaps = false; auto retainOriginalImages = true; auto compressedDoc = GLTFTextureCompressionUtils::CompressTextureAsDDS(std::make_shared(path), doc, doc.textures.Get("0"), TextureCompression::BC3, "", maxTextureSize, generateMipMaps, retainOriginalImages); auto originalUri = compressedDoc.images.Get("0").uri; auto compressedUri = compressedDoc.images.Get("1").uri; auto basePath = TestUtils::GetBasePath(path.c_str()); // load original DirectX::ScratchImage originalImage; DirectX::TexMetadata originalInfo; DirectX::LoadFromWICFile(WStringUtils::ToWString(basePath + originalUri).c_str(), DirectX::WIC_FLAGS_NONE, &originalInfo, originalImage); Assert::IsTrue(101 == originalInfo.width); Assert::IsTrue(51 == originalInfo.height); // load compressed DirectX::ScratchImage compressedImage; DirectX::TexMetadata compressedInfo; DirectX::LoadFromDDSFile(WStringUtils::ToWString(compressedUri).c_str(), DirectX::DDS_FLAGS_NONE, &compressedInfo, compressedImage); // Check resize Assert::IsTrue(104 == compressedInfo.width); Assert::IsTrue(52 == compressedInfo.height); }); } }; } ================================================ FILE: glTF-Toolkit.Test/GLTFTexturePackingUtilsTests.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" #include #include "GLTFSDK/IStreamWriter.h" #include "GLTFSDK/Constants.h" #include "GLTFSDK/Serialize.h" #include "GLTFSDK/Deserialize.h" #include "GLTFSDK/GLBResourceReader.h" #include "GLTFSDK/GLTFResourceWriter.h" #include "GLTFTexturePackingUtils.h" #include "Helpers/WStringUtils.h" #include "Helpers/TestUtils.h" using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace Microsoft::glTF; using namespace Microsoft::glTF::Toolkit; namespace Microsoft::glTF::Toolkit::Test { TEST_CLASS(GLTFTexturePackingUtilsTests) { // Asset with no materials const char* c_cubeAsset3DJson = "Resources\\gltf\\cubeAsset3D.gltf"; // Asset with loose images and all textures const char* c_waterBottleJson = "Resources\\gltf\\WaterBottle\\WaterBottle.gltf"; // Loads and packs a complex asset with the specified packing void ExecutePackingTest(const char* gltfRelativePath, TexturePacking packing) { // This asset has all textures TestUtils::LoadAndExecuteGLTFTest(gltfRelativePath, [packing](auto doc, auto path) { auto material = doc.materials.Elements()[0]; auto packedDoc = GLTFTexturePackingUtils::PackMaterialForWindowsMR(std::make_shared(path), doc, material, packing, ""); auto packedMaterial = packedDoc.materials.Elements()[0]; // Check that the material changed Assert::IsTrue(material != packedMaterial); // Check that the new material replaces the old one Assert::IsTrue(material.id == packedMaterial.id); Assert::IsTrue(doc.materials.Size() == packedDoc.materials.Size()); size_t expectedExtensionsSize = material.extensions.size(); // Check the new extension is not empty if (packing & (TexturePacking::OcclusionRoughnessMetallic | TexturePacking::RoughnessMetallicOcclusion)) { expectedExtensionsSize++; auto packingOrmExtension = packedMaterial.extensions.at(std::string(EXTENSION_MSFT_PACKING_ORM)); Assert::IsFalse(packingOrmExtension.empty()); // Check the new extension contains an ORM texture rapidjson::Document ormJson; ormJson.Parse(packingOrmExtension.c_str()); if (packing & TexturePacking::OcclusionRoughnessMetallic) { Assert::IsTrue(ormJson[MSFT_PACKING_ORM_ORMTEXTURE_KEY].IsObject()); Assert::IsTrue(ormJson[MSFT_PACKING_ORM_ORMTEXTURE_KEY].HasMember(MSFT_PACKING_INDEX_KEY)); } if (packing & TexturePacking::RoughnessMetallicOcclusion) { Assert::IsTrue(ormJson[MSFT_PACKING_ORM_RMOTEXTURE_KEY].IsObject()); Assert::IsTrue(ormJson[MSFT_PACKING_ORM_RMOTEXTURE_KEY].HasMember(MSFT_PACKING_INDEX_KEY)); } if (!material.normalTexture.textureId.empty()) { // Check the new extension contains a normal texture Assert::IsTrue(ormJson[MSFT_PACKING_ORM_NORMALTEXTURE_KEY].IsObject()); Assert::IsTrue(ormJson[MSFT_PACKING_ORM_NORMALTEXTURE_KEY].HasMember(MSFT_PACKING_INDEX_KEY)); } } if (packing & TexturePacking::NormalRoughnessMetallic) { expectedExtensionsSize++; auto packingNrmExtension = packedMaterial.extensions.at(std::string(EXTENSION_MSFT_PACKING_NRM)); Assert::IsFalse(packingNrmExtension.empty()); // Check the new extension contains an NRM texture rapidjson::Document nrmJson; nrmJson.Parse(packingNrmExtension.c_str()); Assert::IsTrue(nrmJson[MSFT_PACKING_NRM_KEY].IsObject()); Assert::IsTrue(nrmJson[MSFT_PACKING_NRM_KEY].HasMember(MSFT_PACKING_INDEX_KEY)); } // Check that the packed material has the new extension Assert::IsTrue(expectedExtensionsSize == packedMaterial.extensions.size()); }); } TEST_METHOD(GLTFTexturePackingUtils_NoMaterials) { // This asset has no textures TestUtils::LoadAndExecuteGLTFTest(c_cubeAsset3DJson, [](auto doc, auto path) { auto material = doc.materials.Elements()[0]; auto packedDoc = GLTFTexturePackingUtils::PackMaterialForWindowsMR(std::make_shared(path), doc, material, TexturePacking::OcclusionRoughnessMetallic, ""); // Check that nothing changed Assert::IsTrue(doc == packedDoc); }); } TEST_METHOD(GLTFTexturePackingUtils_NoPacking) { // This asset has all textures TestUtils::LoadAndExecuteGLTFTest(c_waterBottleJson, [](auto doc, auto path) { auto material = doc.materials.Elements()[0]; auto packedDoc = GLTFTexturePackingUtils::PackMaterialForWindowsMR(std::make_shared(path), doc, material, TexturePacking::None, ""); // Check that nothing changed Assert::IsTrue(doc == packedDoc); }); } TEST_METHOD(GLTFTexturePackingUtils_PackORM) { ExecutePackingTest(c_waterBottleJson, TexturePacking::OcclusionRoughnessMetallic); } TEST_METHOD(GLTFTexturePackingUtils_PackRMO) { ExecutePackingTest(c_waterBottleJson, TexturePacking::RoughnessMetallicOcclusion); } TEST_METHOD(GLTFTexturePackingUtils_PackORMandRMO) { ExecutePackingTest(c_waterBottleJson, (TexturePacking)(TexturePacking::OcclusionRoughnessMetallic | TexturePacking::RoughnessMetallicOcclusion)); } TEST_METHOD(GLTFTexturePackingUtils_PackNRM) { ExecutePackingTest(c_waterBottleJson, TexturePacking::NormalRoughnessMetallic); } TEST_METHOD(GLTFTexturePackingUtils_PackNRMandORM) { // Default for RS4+ compatible with both HoloLens and Desktop ExecutePackingTest(c_waterBottleJson, (TexturePacking)(TexturePacking::OcclusionRoughnessMetallic | TexturePacking::NormalRoughnessMetallic)); } TEST_METHOD(GLTFTexturePackingUtils_PackNRMandORMandRMO) { // Maximum compatibility: all packings ExecutePackingTest(c_waterBottleJson, (TexturePacking)(TexturePacking::OcclusionRoughnessMetallic | TexturePacking::NormalRoughnessMetallic | TexturePacking::RoughnessMetallicOcclusion)); } TEST_METHOD(GLTFTexturePackingUtils_PackAllWithNoMaterials) { // This asset has no materials TestUtils::LoadAndExecuteGLTFTest(c_cubeAsset3DJson, [](auto doc, auto path) { auto packedDoc = GLTFTexturePackingUtils::PackAllMaterialsForWindowsMR(std::make_shared(path), doc, TexturePacking::OcclusionRoughnessMetallic, ""); // Check that nothing changed Assert::IsTrue(doc == packedDoc); }); } TEST_METHOD(GLTFTexturePackingUtils_PackAllWithPackingNone) { // This asset has all textures TestUtils::LoadAndExecuteGLTFTest(c_waterBottleJson, [](auto doc, auto path) { auto packedDoc = GLTFTexturePackingUtils::PackAllMaterialsForWindowsMR(std::make_shared(path), doc, TexturePacking::None, ""); // Check that nothing changed Assert::IsTrue(doc == packedDoc); }); } TEST_METHOD(GLTFTexturePackingUtils_PackAllWithOneMaterial) { std::unique_ptr documentPackedSingleTexture; std::unique_ptr documentPackedAllTextures; // This asset has all textures TestUtils::LoadAndExecuteGLTFTest(c_waterBottleJson, [&documentPackedSingleTexture](auto doc, auto path) { documentPackedSingleTexture = std::make_unique(GLTFTexturePackingUtils::PackMaterialForWindowsMR(std::make_shared(path), doc, doc.materials.Elements()[0], TexturePacking::OcclusionRoughnessMetallic, "")); }); TestUtils::LoadAndExecuteGLTFTest(c_waterBottleJson, [&documentPackedAllTextures](auto doc, auto path) { documentPackedAllTextures = std::make_unique(GLTFTexturePackingUtils::PackAllMaterialsForWindowsMR(std::make_shared(path), doc, TexturePacking::OcclusionRoughnessMetallic, "")); }); // Assert there's one material Assert::IsTrue(documentPackedSingleTexture->materials.Size() == 1); Assert::IsTrue(documentPackedAllTextures->materials.Size() == 1); // Check that they're the same when there's one material Assert::IsTrue(*documentPackedSingleTexture == *documentPackedAllTextures); } }; } ================================================ FILE: glTF-Toolkit.Test/Helpers/StreamMock.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once #include #include #include #include #include namespace Microsoft::glTF::Toolkit::Test { class StreamMock : public Microsoft::glTF::IStreamWriter, public Microsoft::glTF::IStreamReader { public: StreamMock() : m_stream(std::make_shared(std::ios_base::app | std::ios_base::binary | std::ios_base::in | std::ios_base::out)) { } std::shared_ptr GetOutputStream(const std::string&) const override { return m_stream; } std::shared_ptr GetInputStream(const std::string&) const override { return m_stream; } private: std::shared_ptr m_stream; }; } ================================================ FILE: glTF-Toolkit.Test/Helpers/TestUtils.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once #include #include #include "GLTFSDK/IStreamWriter.h" #include "GLTFSDK/Constants.h" #include "GLTFSDK/Serialize.h" #include "GLTFSDK/Deserialize.h" #include #include #include #include "WStringUtils.h" #include "StreamMock.h" #include #include #include #include #include namespace Microsoft::glTF::Toolkit::Test { class TestUtils { public: static std::string GetBasePath(const char * absolutePath) { std::string path(absolutePath); #ifdef __APPLE__ return path.substr(0, path.rfind('/') + 1); #else return path.substr(0, path.rfind('\\') + 1); #endif } static std::string GetAbsolutePath(const char * relativePath) { #ifdef __APPLE__ // Leaving Win32 alone (below), but macOS requires working directory to be set std::string finalPath(relativePath); std::replace(finalPath.begin(), finalPath.end(), '\\', '/'); return finalPath; #else std::string currentPath = __FILE__; std::string sourcePath = currentPath.substr(0, currentPath.rfind('\\')); std::string resourcePath = sourcePath.substr(0, sourcePath.rfind('\\')); return resourcePath + "\\" + relativePath; #endif } static std::wstring GetAbsolutePathW(const char * relativePath) { std::string absolutePath = GetAbsolutePath(relativePath); std::wstringstream wss; wss << absolutePath.c_str(); return wss.str(); } static std::shared_ptr ReadLocalAsset(const std::string& filename) { // Read local file int64_t m_readPosition = 0; std::shared_ptr> m_buffer; std::ifstream ifs; ifs.open(filename.c_str(), std::ifstream::in | std::ifstream::binary); if (ifs.is_open()) { std::streampos start = ifs.tellg(); ifs.seekg(0, std::ios::end); m_buffer = std::make_shared>(static_cast(ifs.tellg() - start)); ifs.seekg(0, std::ios::beg); ifs.read(reinterpret_cast(const_cast(m_buffer->data())), m_buffer->size()); ifs.close(); } else { throw std::runtime_error("Could not open the file for reading"); } // To IStream unsigned long writeBufferLength = 4096L * 1024L; auto tempStream = std::make_shared(); auto tempBuffer = new char[writeBufferLength]; // Read the file for as long as we can fill the buffer completely. // This means there is more content to be read. unsigned long bytesRead; do { auto bytesAvailable = m_buffer->size() - m_readPosition; unsigned long br = std::min(static_cast(bytesAvailable), writeBufferLength); #ifdef _WIN32 memcpy_s(tempBuffer, br, m_buffer->data() + m_readPosition, br); #else memcpy(tempBuffer, m_buffer->data() + m_readPosition, br); #endif m_readPosition += br; bytesRead = br; tempStream->write(tempBuffer, bytesRead); } while (bytesRead == writeBufferLength); delete[] tempBuffer; if (tempStream.get()->bad()) { throw std::runtime_error("Bad std::stringstream after copying the file"); } return tempStream; } typedef std::function GLTFAction; static void LoadAndExecuteGLTFTest(const char * gltfRelativePath, GLTFAction action) { // This asset has all textures auto absolutePath = TestUtils::GetAbsolutePath(gltfRelativePath); auto input = TestUtils::ReadLocalAsset(absolutePath); try { // Deserialize input json auto inputJson = std::string(std::istreambuf_iterator(*input), std::istreambuf_iterator()); auto doc = Deserialize(inputJson); action(doc, absolutePath); } catch (std::exception ex) { std::stringstream ss; ss << "Received exception was unexpected. Got: " << ex.what(); Microsoft::VisualStudio::CppUnitTestFramework::Assert::Fail(WStringUtils::ToWString(ss).c_str()); } } }; class TestStreamReader : public IStreamReader { public: TestStreamReader(std::string gltfAbsolutePath) : m_basePath(TestUtils::GetBasePath(gltfAbsolutePath.c_str())) {} virtual ~TestStreamReader() override {} virtual std::shared_ptr GetInputStream(const std::string& filename) const override { auto path = m_basePath; #ifdef __APPLE__ path += "/" + filename; #else path += "\\" + filename; #endif return std::make_shared(path, std::ios::binary); } private: const std::string m_basePath; }; } ================================================ FILE: glTF-Toolkit.Test/Helpers/WStringUtils.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once #include #include #include #include namespace Microsoft::glTF::Toolkit::Test { class WStringUtils { public: static std::wstring ToWString(const std::string& str) { return ToWString(str.c_str()); } static std::wstring ToWString(const std::stringstream& ss) { return ToWString(ss.str()); } static std::wstring ToWString(const char* str) { std::wstring_convert> converter; return converter.from_bytes(str); } }; } ================================================ FILE: glTF-Toolkit.Test/Resources/gltf/CubeAsset3D.gltf ================================================ { "asset": { "version": "2.0", "generator": "Microsoft GLTF Exporter 2.1.2-b21" }, "accessors": [{ "bufferView": 0, "byteOffset": 0, "componentType": 5123, "count": 36, "type": "SCALAR", "max": [23.0], "min": [0.0] }, { "bufferView": 1, "byteOffset": 0, "componentType": 5126, "count": 24, "type": "VEC3", "max": [1.0, 1.0, 1.0], "min": [0.0, 0.0, 0.0] }, { "bufferView": 2, "byteOffset": 0, "componentType": 5126, "count": 24, "type": "VEC3", "max": [1.0, 1.0, 0.0], "min": [-1.0, -1.0, -1.0] } ], "bufferViews": [{ "buffer": 0, "byteLength": 72, "byteOffset": 0, "target": 34963 }, { "buffer": 0, "byteLength": 288, "byteOffset": 72, "target": 34962 }, { "buffer": 0, "byteLength": 288, "byteOffset": 360, "target": 34962 } ], "buffers": [{ "byteLength": 648 } ], "materials": [{ "pbrMetallicRoughness": { "baseColorFactor": [0.5187909007072449, 0.5187909007072449, 0.5187909007072449, 1.0], "metallicFactor": 0.0, "roughnessFactor": 0.0 }, "name": "DefaultMaterial", "extensions": { "KHR_materials_pbrSpecularGlossiness": { "diffuseFactor": [0.49803921580314639, 0.49803921580314639, 0.49803921580314639, 1.0], "specularFactor": [0.0, 0.0, 0.0] } } }, { "pbrMetallicRoughness": { "baseColorFactor": [0.5187909007072449, 0.5187909007072449, 0.5187909007072449, 1.0], "metallicFactor": 0.0, "roughnessFactor": 0.0 }, "name": "DefaultMaterial", "extensions": { "KHR_materials_pbrSpecularGlossiness": { "diffuseFactor": [0.49803921580314639, 0.49803921580314639, 0.49803921580314639, 1.0], "specularFactor": [0.0, 0.0, 0.0] } } }, { "pbrMetallicRoughness": { "baseColorFactor": [0.5187909007072449, 0.5187909007072449, 0.5187909007072449, 1.0], "metallicFactor": 0.0, "roughnessFactor": 0.0 }, "name": "DefaultMaterial", "extensions": { "KHR_materials_pbrSpecularGlossiness": { "diffuseFactor": [0.49803921580314639, 0.49803921580314639, 0.49803921580314639, 1.0], "specularFactor": [0.0, 0.0, 0.0] } } } ], "meshes": [{ "name": "polygon", "primitives": [{ "attributes": { "NORMAL": 2, "POSITION": 1 }, "indices": 0, "material": 0, "mode": 4 } ] } ], "nodes": [{ "matrix": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0], "mesh": 0, "name": "polygon" }, { "children": [0], "matrix": [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0], "name": "root" } ], "scenes": [{ "nodes": [1] } ], "scene": 0, "extensionsUsed": ["KHR_materials_pbrSpecularGlossiness"] } ================================================ FILE: glTF-Toolkit.Test/Resources/gltf/CubeWithLOD.gltf ================================================ { "asset": { "version": "2.0", "generator": "Microsoft GLTF Exporter 2.1.2-b21" }, "accessors": [{ "bufferView": 0, "byteOffset": 0, "componentType": 5123, "count": 36, "type": "SCALAR" }, { "bufferView": 1, "byteOffset": 0, "componentType": 5126, "count": 24, "type": "VEC3" }, { "bufferView": 2, "byteOffset": 0, "componentType": 5126, "count": 24, "type": "VEC3" } ], "bufferViews": [{ "buffer": 0, "byteLength": 72, "byteOffset": 0, "target": 34963 }, { "buffer": 0, "byteLength": 288, "byteOffset": 72, "target": 34962 }, { "buffer": 0, "byteLength": 288, "byteOffset": 360, "target": 34962 } ], "buffers": [{ "byteLength": 648 } ], "materials": [{ "pbrMetallicRoughness": { "baseColorFactor": [0.5187909007072449, 0.5187909007072449, 0.5187909007072449, 1.0], "metallicFactor": 0.0, "roughnessFactor": 0.0 }, "name": "DefaultMaterial", "extensions": { "KHR_materials_pbrSpecularGlossiness": { "diffuseFactor": [0.49803921580314639, 0.49803921580314639, 0.49803921580314639, 1.0], "specularFactor": [0.0, 0.0, 0.0] } } } ], "meshes": [{ "name": "polygon", "primitives": [{ "attributes": { "NORMAL": 2, "POSITION": 1 }, "indices": 0, "material": 0, "mode": 4 } ] } ], "nodes": [{ "mesh": 0, "name": "polygon" }, { "children": [0], "name": "root", "extensions": { "MSFT_lod": { "ids": [ 3 ] } } }, { "mesh": 0, "name": "polygon_lod1" }, { "children": [2], "name": "root_lod1" } ], "scenes": [{ "nodes": [1] } ], "scene": 0, "extensionsUsed": ["KHR_materials_pbrSpecularGlossiness", "MSFT_lod"] } ================================================ FILE: glTF-Toolkit.Test/Resources/gltf/TextureTest/TextureTest.gltf ================================================ { "asset" : { "version" : "2.0" }, "textures": [ { "sampler": 0, "source": 0 } ], "images": [ { "uri": "0.png" } ] } ================================================ FILE: glTF-Toolkit.Test/Resources/gltf/WaterBottle/WaterBottle.gltf ================================================ { "accessors": [ { "bufferView": 0, "componentType": 5126, "count": 2549, "type": "VEC2" }, { "bufferView": 1, "componentType": 5126, "count": 2549, "type": "VEC3" }, { "bufferView": 2, "componentType": 5126, "count": 2549, "type": "VEC4" }, { "bufferView": 3, "componentType": 5126, "count": 2549, "type": "VEC3", "max": [ 0.05445001, 0.130220339, 0.0544500239 ], "min": [ -0.05445001, -0.130220339, -0.0544500239 ] }, { "bufferView": 4, "componentType": 5123, "count": 13530, "type": "SCALAR" } ], "asset": { "generator": "glTF Tools for Unity", "version": "2.0" }, "bufferViews": [ { "buffer": 0, "byteLength": 20392 }, { "buffer": 0, "byteOffset": 20392, "byteLength": 30588 }, { "buffer": 0, "byteOffset": 50980, "byteLength": 40784 }, { "buffer": 0, "byteOffset": 91764, "byteLength": 30588 }, { "buffer": 0, "byteOffset": 122352, "byteLength": 27060 } ], "buffers": [ { "uri": "WaterBottle.bin", "byteLength": 149412 } ], "extensionsUsed": [ "KHR_materials_pbrSpecularGlossiness" ], "images": [ { "uri": "WaterBottle_baseColor.png" }, { "uri": "WaterBottle_roughnessMetallic.png" }, { "uri": "WaterBottle_normal.png" }, { "uri": "WaterBottle_emissive.png" }, { "uri": "WaterBottle_occlusion.png" }, { "uri": "WaterBottle_diffuse.png" }, { "uri": "WaterBottle_specularGlossiness.png" } ], "meshes": [ { "primitives": [ { "attributes": { "TEXCOORD_0": 0, "NORMAL": 1, "TANGENT": 2, "POSITION": 3 }, "indices": 4, "material": 0 } ], "name": "WaterBottle" } ], "materials": [ { "pbrMetallicRoughness": { "baseColorTexture": { "index": 0 }, "metallicRoughnessTexture": { "index": 1 } }, "normalTexture": { "index": 2 }, "occlusionTexture": { "index": 4 }, "emissiveFactor": [ 1.0, 1.0, 1.0 ], "emissiveTexture": { "index": 3 }, "name": "BottleMat", "extensions": { "KHR_materials_pbrSpecularGlossiness": { "diffuseTexture": { "index": 5 }, "specularGlossinessTexture": { "index": 6 } } } } ], "nodes": [ { "mesh": 0, "name": "WaterBottle" } ], "scene": 0, "scenes": [ { "nodes": [ 0 ] } ], "textures": [ { "source": 0 }, { "source": 1 }, { "source": 2 }, { "source": 3 }, { "source": 4 }, { "source": 5 }, { "source": 6 } ] } ================================================ FILE: glTF-Toolkit.Test/Resources/gltf/WaterBottle_ORM/WaterBottle.gltf ================================================ { "accessors": [ { "bufferView": 0, "componentType": 5126, "count": 2549, "type": "VEC2" }, { "bufferView": 1, "componentType": 5126, "count": 2549, "type": "VEC3" }, { "bufferView": 2, "componentType": 5126, "count": 2549, "type": "VEC4" }, { "bufferView": 3, "componentType": 5126, "count": 2549, "type": "VEC3", "max": [ 0.05445001, 0.130220339, 0.0544500239 ], "min": [ -0.05445001, -0.130220339, -0.0544500239 ] }, { "bufferView": 4, "componentType": 5123, "count": 13530, "type": "SCALAR" } ], "asset": { "generator": "glTF Tools for Unity", "version": "2.0" }, "bufferViews": [ { "buffer": 0, "byteLength": 20392 }, { "buffer": 0, "byteOffset": 20392, "byteLength": 30588 }, { "buffer": 0, "byteOffset": 50980, "byteLength": 40784 }, { "buffer": 0, "byteOffset": 91764, "byteLength": 30588 }, { "buffer": 0, "byteOffset": 122352, "byteLength": 27060 } ], "buffers": [ { "uri": "WaterBottle.bin", "byteLength": 149412 } ], "extensionsUsed": [ "KHR_materials_pbrSpecularGlossiness", "MSFT_packing_occlusionRoughnessMetallic" ], "images": [ { "uri": "WaterBottle_baseColor.png" }, { "uri": "WaterBottle_roughnessMetallic.png" }, { "uri": "WaterBottle_normal.png" }, { "uri": "WaterBottle_emissive.png" }, { "uri": "WaterBottle_occlusion.png" }, { "uri": "WaterBottle_diffuse.png" }, { "uri": "WaterBottle_specularGlossiness.png" }, { "uri": "WaterBottle_occlusionRoughnessMetallic.png" } ], "meshes": [ { "primitives": [ { "attributes": { "TEXCOORD_0": 0, "NORMAL": 1, "TANGENT": 2, "POSITION": 3 }, "indices": 4, "material": 0 } ], "name": "WaterBottle" } ], "materials": [ { "pbrMetallicRoughness": { "baseColorTexture": { "index": 0 }, "metallicRoughnessTexture": { "index": 1 } }, "normalTexture": { "index": 2 }, "occlusionTexture": { "index": 4 }, "emissiveFactor": [ 1.0, 1.0, 1.0 ], "emissiveTexture": { "index": 3 }, "name": "BottleMat", "extensions": { "KHR_materials_pbrSpecularGlossiness": { "diffuseTexture": { "index": 5 }, "specularGlossinessTexture": { "index": 6 } }, "MSFT_packing_occlusionRoughnessMetallic": { "occlusionRoughnessMetallicTexture": { "index": 7 }, "normalTexture": { "index": 2 } } } } ], "nodes": [ { "mesh": 0, "name": "WaterBottle" } ], "scene": 0, "scenes": [ { "nodes": [ 0 ] } ], "textures": [ { "source": 0 }, { "source": 1 }, { "source": 2 }, { "source": 3 }, { "source": 4 }, { "source": 5 }, { "source": 6 }, { "source": 7 } ] } ================================================ FILE: glTF-Toolkit.Test/Resources/gltf/WaterBottle_ORM/WaterBottle_WindowsMR.gltf ================================================ { "accessors": [ { "bufferView": 0, "componentType": 5126, "count": 2549, "type": "VEC2" }, { "bufferView": 1, "componentType": 5126, "count": 2549, "type": "VEC3" }, { "bufferView": 2, "componentType": 5126, "count": 2549, "type": "VEC4" }, { "bufferView": 3, "componentType": 5126, "count": 2549, "type": "VEC3", "max": [ 0.05445001, 0.130220339, 0.0544500239 ], "min": [ -0.05445001, -0.130220339, -0.0544500239 ] }, { "bufferView": 4, "componentType": 5123, "count": 13530, "type": "SCALAR" } ], "asset": { "generator": "glTF Tools for Unity", "version": "2.0" }, "bufferViews": [ { "buffer": 0, "byteLength": 20392 }, { "buffer": 0, "byteOffset": 20392, "byteLength": 30588 }, { "buffer": 0, "byteOffset": 50980, "byteLength": 40784 }, { "buffer": 0, "byteOffset": 91764, "byteLength": 30588 }, { "buffer": 0, "byteOffset": 122352, "byteLength": 27060 } ], "buffers": [ { "uri": "WaterBottle.bin", "byteLength": 149412 } ], "extensionsUsed": [ "KHR_materials_pbrSpecularGlossiness", "MSFT_packing_occlusionRoughnessMetallic", "MSFT_texture_dds" ], "images": [ { "uri": "WaterBottle_baseColor.png" }, { "uri": "WaterBottle_roughnessMetallic.png" }, { "uri": "WaterBottle_normal.png" }, { "uri": "WaterBottle_emissive.png" }, { "uri": "WaterBottle_occlusion.png" }, { "uri": "WaterBottle_diffuse.png" }, { "uri": "WaterBottle_specularGlossiness.png" }, { "uri": "WaterBottle_occlusionRoughnessMetallic.png" }, { "mimeType": "image/vnd-ms.dds", "uri": "WaterBottle_occlusionRoughnessMetallic.dds" }, { "mimeType": "image/vnd-ms.dds", "uri": "WaterBottle_normal.dds" }, { "mimeType": "image/vnd-ms.dds", "uri": "WaterBottle_baseColor.dds" }, { "mimeType": "image/vnd-ms.dds", "uri": "WaterBottle_emissive.dds" } ], "meshes": [ { "primitives": [ { "attributes": { "TEXCOORD_0": 0, "NORMAL": 1, "TANGENT": 2, "POSITION": 3 }, "indices": 4, "material": 0 } ], "name": "WaterBottle" } ], "materials": [ { "pbrMetallicRoughness": { "baseColorTexture": { "index": 0 }, "metallicRoughnessTexture": { "index": 1 } }, "normalTexture": { "index": 2 }, "occlusionTexture": { "index": 4 }, "emissiveFactor": [ 1.0, 1.0, 1.0 ], "emissiveTexture": { "index": 3 }, "name": "BottleMat", "extensions": { "KHR_materials_pbrSpecularGlossiness": { "diffuseTexture": { "index": 5 }, "specularGlossinessTexture": { "index": 6 } }, "MSFT_packing_occlusionRoughnessMetallic": { "occlusionRoughnessMetallicTexture": { "index": 8 }, "normalTexture": { "index": 2 } } } } ], "nodes": [ { "mesh": 0, "name": "WaterBottle" } ], "scene": 0, "scenes": [ { "nodes": [ 0 ] } ], "textures": [ { "source": 0, "extensions": { "MSFT_texture_dds": { "source": 10 } } }, { "source": 1 }, { "source": 2, "extensions": { "MSFT_texture_dds": { "source": 9 } } }, { "source": 3, "extensions": { "MSFT_texture_dds": { "source": 11 } } }, { "source": 4 }, { "source": 5 }, { "source": 6 }, { "source": 7 }, { "source": 8, "extensions": { "MSFT_texture_dds": { "source": 8 } } } ] } ================================================ FILE: glTF-Toolkit.Test/glTF-Toolkit.Test.vcxproj ================================================ Debug Win32 Release Win32 Debug x64 Release x64 Create Create Create Create {ff0275f1-58cb-4745-ba81-f6c1df66e206} 15.0 {B2AF77B5-8433-46AD-860D-23A4831F6830} Win32Proj glTFToolkitTest 10.0.16299.0 NativeUnitTestProject DynamicLibrary true v141 Unicode false DynamicLibrary false v141 true Unicode false DynamicLibrary true v141 Unicode false DynamicLibrary false v141 true Unicode false true $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(SolutionDir)Built\Int\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ true $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(SolutionDir)Built\Int\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ true $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(SolutionDir)Built\Int\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ true $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(SolutionDir)Built\Int\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ Use Level4 Disabled $(SolutionDir)glTF-Toolkit\inc;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;%(PreprocessorDefinitions) true pch.h stdcpp17 true 4996 Windows d3d11.lib;dxgi.lib;pathcch.lib;%(AdditionalDependencies) Use Level4 Disabled $(SolutionDir)glTF-Toolkit\inc;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) _DEBUG;%(PreprocessorDefinitions) true pch.h stdcpp17 true 4996 Windows d3d11.lib;dxgi.lib;pathcch.lib;%(AdditionalDependencies) Level4 Use MaxSpeed true true $(SolutionDir)glTF-Toolkit\inc;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) WIN32;NDEBUG;%(PreprocessorDefinitions) true pch.h stdcpp17 true 4996 Windows true true d3d11.lib;dxgi.lib;pathcch.lib;%(AdditionalDependencies) Level4 Use MaxSpeed true true $(SolutionDir)glTF-Toolkit\inc;$(VCInstallDir)UnitTest\include;%(AdditionalIncludeDirectories) NDEBUG;%(PreprocessorDefinitions) true pch.h stdcpp17 true 4996 Windows true true d3d11.lib;dxgi.lib;pathcch.lib;%(AdditionalDependencies) This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. ================================================ FILE: glTF-Toolkit.Test/glTF-Toolkit.Test.vcxproj.filters ================================================  {7294793d-6c71-438a-828f-68361c3db329} {53c10f0a-e8fc-42e4-b612-17631dcd7755} {29129a4d-ff37-427e-9047-6d1044de85b0} {c7fc3736-b68e-4f6d-b93b-1bb4eda5b1ff} {e4d4222d-e5af-4dad-b539-28e2dc1c716a} Resources Resources Resources\WaterBottle Resources\WaterBottle Resources\WaterBottle_ORM Resources\WaterBottle_ORM Resources\WaterBottle_ORM Resources\TextureTest Resources\WaterBottle Resources\WaterBottle Resources\WaterBottle Resources\WaterBottle Resources\WaterBottle Resources\WaterBottle Resources\WaterBottle Resources\WaterBottle_ORM Resources\WaterBottle_ORM Resources\WaterBottle_ORM Resources\WaterBottle_ORM Resources\WaterBottle_ORM Resources\WaterBottle_ORM Resources\WaterBottle_ORM Resources\WaterBottle_ORM Resources\WaterBottle_ORM Resources\WaterBottle_ORM Resources\WaterBottle_ORM Resources\WaterBottle_ORM Resources\TextureTest Helpers Helpers Helpers ================================================ FILE: glTF-Toolkit.Test/packages.config ================================================  ================================================ FILE: glTF-Toolkit.UWP/GLTFSerialization.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" #include "GLTFSerialization.h" #include "GLTFStreams.h" #include #include #include using namespace Concurrency; using namespace Microsoft::glTF; using namespace Microsoft::glTF::Toolkit; using namespace Microsoft::glTF::Toolkit::UWP; using namespace Platform; using namespace Windows::Foundation; using namespace Windows::Storage; IAsyncOperation^ GLTFSerialization::UnpackGLBAsync(StorageFile^ glbFile, StorageFolder^ outputFolder) { String^ glbFilePath = glbFile->Path; std::wstring glbPathW = glbFilePath->Data(); std::string glbPathA = std::string(glbPathW.begin(), glbPathW.end()); String^ outputFolderPath = outputFolder->Path + "\\"; std::wstring outputFolderPathW = outputFolderPath->Data(); std::string outputFolderPathA = std::string(outputFolderPathW.begin(), outputFolderPathW.end()); String^ baseFileName = glbFile->DisplayName; std::wstring baseFileNameW = baseFileName->Data(); std::string baseFileNameA = std::string(baseFileNameW.begin(), baseFileNameW.end()); return create_async([glbPathA, outputFolderPathA, baseFileNameA, outputFolder, baseFileNameW] { GLBToGLTF::UnpackGLB(glbPathA, outputFolderPathA, baseFileNameA); return outputFolder->GetFileAsync(ref new String((baseFileNameW + L".gltf").c_str())); }); } IAsyncOperation^ GLTFSerialization::PackGLTFAsync(StorageFile^ sourceGltf, StorageFolder^ outputFolder, String^ glbName) { return create_async([sourceGltf, outputFolder, glbName]() { std::wstring gltfPathW = sourceGltf->Path->Data(); auto stream = std::make_shared(gltfPathW, std::ios::in); return create_task([stream]() { return std::make_shared(Deserialize(*stream)); }) .then([sourceGltf, outputFolder, glbName](std::shared_ptr document) { return create_task(sourceGltf->GetParentAsync()) .then([outputFolder, glbName, document](StorageFolder^ gltfFolder) { auto streamReader = std::make_shared(gltfFolder); String^ outputGlbPath = outputFolder->Path + "\\" + glbName; std::wstring outputGlbPathW = outputGlbPath->Data(); SerializeBinary(*document, streamReader, std::make_shared(outputGlbPathW)); return outputFolder->GetFileAsync(glbName); }); }); }); } ================================================ FILE: glTF-Toolkit.UWP/GLTFSerialization.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once namespace Microsoft::glTF::Toolkit::UWP { public ref class GLTFSerialization sealed { public: /// /// Unpacks a GLB asset into a GLTF manifest and its /// resources (bin files and images). /// /// The GLB file to unpack. The name of the GLB file, without the extension, /// will be used as a prefix to all unpacked resources. /// The output folder to which the glTF manifest and resources will be unpacked. static Windows::Foundation::IAsyncOperation^ UnpackGLBAsync(Windows::Storage::StorageFile^ glbFile, Windows::Storage::StorageFolder^ outputFolder); /// /// Serializes a glTF asset as a glTF binary (GLB) file. /// /// The glTF file to be serialized. /// The output folder where you want the glb file to be placed. /// The glb filename. /// /// The resulting GLB file, named with glbName and located in outputFolder. /// static Windows::Foundation::IAsyncOperation^ PackGLTFAsync(Windows::Storage::StorageFile^ sourceGltf, Windows::Storage::StorageFolder^ outputFolder, Platform::String^ glbName); }; } ================================================ FILE: glTF-Toolkit.UWP/GLTFStreams.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once #include #include #include namespace Microsoft::glTF::Toolkit::UWP { class GLTFStreamReader : public IStreamReader { public: GLTFStreamReader(Windows::Storage::StorageFolder^ gltfFolder) { m_uriBase = std::experimental::filesystem::path(gltfFolder->Path->Data()); } virtual ~GLTFStreamReader() override {} virtual std::shared_ptr GetInputStream(const std::string& filename) const override { std::wstring filenameW(filename.begin(), filename.end()); std::experimental::filesystem::path path(filenameW); auto absolutePath = path.wstring(); if (path.is_relative()) { absolutePath = (m_uriBase / path).wstring(); } return std::make_shared(absolutePath, std::ios::binary); } private: std::experimental::filesystem::path m_uriBase; }; class GLBStreamWriter : public Microsoft::glTF::IStreamWriter { public: GLBStreamWriter(const std::wstring& filename) : m_stream(std::make_shared(filename, std::ios_base::binary | std::ios_base::out)) { } std::shared_ptr GetOutputStream(const std::string&) const override { return m_stream; } private: std::shared_ptr m_stream; }; } ================================================ FILE: glTF-Toolkit.UWP/WindowsMRConversion.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" #include "WindowsMRConversion.h" #include "GLTFSerialization.h" #include "GLTFStreams.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace concurrency; using namespace Platform; using namespace Windows::Foundation; using namespace Windows::Foundation::Metadata; using namespace Windows::Storage; using namespace Windows::System::Profile; using namespace Microsoft::glTF::Toolkit::UWP; IAsyncOperation^ WindowsMRConversion::ConvertAssetForWindowsMR(StorageFile^ gltfOrGlbFile, StorageFolder^ outputFolder) { return ConvertAssetForWindowsMR(gltfOrGlbFile, outputFolder, 512); } IAsyncOperation^ WindowsMRConversion::ConvertAssetForWindowsMR(StorageFile^ gltfOrGlbFile, StorageFolder^ outputFolder, size_t maxTextureSize) { UWP::TexturePacking detectedPacking = UWP::TexturePacking::None; if (AnalyticsInfo::VersionInfo->DeviceFamily == "Windows.Holographic") { detectedPacking = UWP::TexturePacking::NormalRoughnessMetallic; } else { bool isVersion1803OrNewer = ApiInformation::IsApiContractPresent("Windows.Foundation.UniversalApiContract", 6); detectedPacking = isVersion1803OrNewer ? UWP::TexturePacking::OcclusionRoughnessMetallic : UWP::TexturePacking::RoughnessMetallicOcclusion; } return ConvertAssetForWindowsMR(gltfOrGlbFile, outputFolder, maxTextureSize, detectedPacking); } IAsyncOperation^ WindowsMRConversion::ConvertAssetForWindowsMR(StorageFile ^ gltfOrGlbFile, StorageFolder ^ outputFolder, size_t maxTextureSize, TexturePacking packing) { return ConvertAssetForWindowsMR(gltfOrGlbFile, outputFolder, maxTextureSize, packing, false); } IAsyncOperation^ WindowsMRConversion::ConvertAssetForWindowsMR(StorageFile ^ gltfOrGlbFile, StorageFolder ^ outputFolder, size_t maxTextureSize, TexturePacking packing, bool meshCompression) { auto isGlb = gltfOrGlbFile->FileType == L".glb"; return create_async([gltfOrGlbFile, maxTextureSize, outputFolder, isGlb, packing, meshCompression]() { return create_task([gltfOrGlbFile, isGlb]() { if (isGlb) { return create_task(GLTFSerialization::UnpackGLBAsync(gltfOrGlbFile, ApplicationData::Current->TemporaryFolder)); } else { return task_from_result(gltfOrGlbFile); } }) .then([maxTextureSize, outputFolder, isGlb, packing, meshCompression](StorageFile^ gltfFile) { auto stream = std::make_shared(gltfFile->Path->Data(), std::ios::in); Document document = Deserialize(*stream, KHR::GetKHRExtensionDeserializer()); return create_task(gltfFile->GetParentAsync()) .then([document, maxTextureSize, outputFolder, gltfFile, isGlb, packing, meshCompression](StorageFolder^ baseFolder) { auto streamReader = std::make_shared(baseFolder); auto tempDirectory = std::wstring(ApplicationData::Current->TemporaryFolder->Path->Data()); auto tempDirectoryA = std::string(tempDirectory.begin(), tempDirectory.end()); // 0. Specular Glossiness conversion auto convertedDoc = GLTFSpecularGlossinessUtils::ConvertMaterials(streamReader, document, tempDirectoryA); // 1. Remove redundant textures and images convertedDoc = GLTFTextureUtils::RemoveRedundantTexturesAndImages(convertedDoc); // 2. Texture Packing convertedDoc = GLTFTexturePackingUtils::PackAllMaterialsForWindowsMR(streamReader, convertedDoc, static_cast(packing), tempDirectoryA); // 3. Texture Compression convertedDoc = GLTFTextureCompressionUtils::CompressAllTexturesForWindowsMR(streamReader, convertedDoc, tempDirectoryA, maxTextureSize, false /* retainOriginalImages */); // 4. Make sure there's a default scene set if (!convertedDoc.HasDefaultScene()) { convertedDoc.defaultSceneId = convertedDoc.scenes.Elements()[0].id; } // 5. Compress the meshes if (meshCompression) { convertedDoc = GLTFMeshCompressionUtils::CompressMeshes(streamReader, convertedDoc, {}, tempDirectoryA); } // 6. GLB Export // The Windows MR Fall Creators update has restrictions on the supported // component types of accessors. AccessorConversionStrategy accessorConversion = [](const Accessor& accessor) { if (accessor.type == AccessorType::TYPE_SCALAR) { switch (accessor.componentType) { case ComponentType::COMPONENT_BYTE: case ComponentType::COMPONENT_UNSIGNED_BYTE: case ComponentType::COMPONENT_SHORT: return ComponentType::COMPONENT_UNSIGNED_SHORT; default: return accessor.componentType; } } else if (accessor.type == AccessorType::TYPE_VEC2 || accessor.type == AccessorType::TYPE_VEC3) { return ComponentType::COMPONENT_FLOAT; } return accessor.componentType; }; auto glbName = std::wstring(gltfFile->Name->Data()); glbName = glbName.substr(0, glbName.rfind(gltfFile->FileType->Data())); if (isGlb) { glbName += L"_converted"; } glbName += L".glb"; std::wstring outputGlbPathW = std::wstring(outputFolder->Path->Data()) + L"\\" + glbName; SerializeBinary(convertedDoc, streamReader, std::make_shared(outputGlbPathW), accessorConversion); return create_task(outputFolder->GetFileAsync(ref new String(glbName.c_str()))); }); }); }); } ================================================ FILE: glTF-Toolkit.UWP/WindowsMRConversion.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once #include namespace Microsoft::glTF::Toolkit::UWP { [Platform::Metadata::Flags] public enum class TexturePacking : unsigned int { None = Toolkit::TexturePacking::None, OcclusionRoughnessMetallic = Toolkit::TexturePacking::OcclusionRoughnessMetallic, RoughnessMetallicOcclusion = Toolkit::TexturePacking::RoughnessMetallicOcclusion, NormalRoughnessMetallic = Toolkit::TexturePacking::NormalRoughnessMetallic }; public ref class WindowsMRConversion sealed { public: static Windows::Foundation::IAsyncOperation^ ConvertAssetForWindowsMR(Windows::Storage::StorageFile^ gltfOrGlbFile, Windows::Storage::StorageFolder^ outputFolder); static Windows::Foundation::IAsyncOperation^ ConvertAssetForWindowsMR(Windows::Storage::StorageFile^ gltfOrGlbFile, Windows::Storage::StorageFolder^ outputFolder, size_t maxTextureSize); static Windows::Foundation::IAsyncOperation^ ConvertAssetForWindowsMR(Windows::Storage::StorageFile^ gltfOrGlbFile, Windows::Storage::StorageFolder^ outputFolder, size_t maxTextureSize, UWP::TexturePacking packing); static Windows::Foundation::IAsyncOperation^ ConvertAssetForWindowsMR(Windows::Storage::StorageFile^ gltfOrGlbFile, Windows::Storage::StorageFolder^ outputFolder, size_t maxTextureSize, UWP::TexturePacking packing, bool meshCompression); }; } ================================================ FILE: glTF-Toolkit.UWP/glTF-Toolkit.UWP.vcxproj ================================================ Debug ARM Debug Win32 Debug x64 Release ARM Release Win32 Release x64 {697462fb-c8e9-4ea5-a499-033df330e212} WindowsRuntimeComponent Microsoft.glTF.Toolkit.UWP en-US 14.0 true Windows Store 10.0.16299.0 10.0.16299.0 10.0 DynamicLibrary true v141 DynamicLibrary true v141 DynamicLibrary true v141 DynamicLibrary false true v141 DynamicLibrary false true v141 DynamicLibrary false true v141 false $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(SolutionDir)Built\Int\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(RootNamespace) false $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(SolutionDir)Built\Int\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(RootNamespace) false $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(SolutionDir)Built\Int\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(RootNamespace) false $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(SolutionDir)Built\Int\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(RootNamespace) false $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(SolutionDir)Built\Int\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(RootNamespace) false $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(SolutionDir)Built\Int\$(PlatformToolset)\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(RootNamespace) Use _WINRT_DLL;%(PreprocessorDefinitions) pch.h $(IntDir)pch.pch $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) /bigobj %(AdditionalOptions) 28204;4634;4996 stdcpp17 $(SolutionDir)glTF-Toolkit\inc;%(AdditionalIncludeDirectories) EditAndContinue true Sync Level4 Console false $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\glTF-Toolkit\glTF-Toolkit.lib;%(AdditionalDependencies) Use _WINRT_DLL;NDEBUG;%(PreprocessorDefinitions) pch.h $(IntDir)pch.pch $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) /bigobj %(AdditionalOptions) 28204;4634;4996 stdcpp17 $(SolutionDir)glTF-Toolkit\inc;%(AdditionalIncludeDirectories) Level4 Console false $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\glTF-Toolkit\glTF-Toolkit.lib;%(AdditionalDependencies) Use _WINRT_DLL;%(PreprocessorDefinitions) pch.h $(IntDir)pch.pch $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) /bigobj %(AdditionalOptions) 28204;4634;4996 stdcpp17 $(SolutionDir)glTF-Toolkit\inc;%(AdditionalIncludeDirectories) Level4 Console false $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\glTF-Toolkit\glTF-Toolkit.lib;%(AdditionalDependencies) Use _WINRT_DLL;%(PreprocessorDefinitions) pch.h $(IntDir)pch.pch $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) /bigobj %(AdditionalOptions) 28204;4634;4996 stdcpp17 $(SolutionDir)glTF-Toolkit\inc;%(AdditionalIncludeDirectories) Level4 Console false $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\glTF-Toolkit\glTF-Toolkit.lib;%(AdditionalDependencies) Use _WINRT_DLL;NDEBUG;%(PreprocessorDefinitions) pch.h $(IntDir)pch.pch $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) /bigobj %(AdditionalOptions) 28204;4634;4996 stdcpp17 $(SolutionDir)glTF-Toolkit\inc;%(AdditionalIncludeDirectories) Level4 Console false $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\glTF-Toolkit\glTF-Toolkit.lib;%(AdditionalDependencies) Use _WINRT_DLL;NDEBUG;%(PreprocessorDefinitions) pch.h $(IntDir)pch.pch $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) /bigobj %(AdditionalOptions) 28204;4634;4996 stdcpp17 $(SolutionDir)glTF-Toolkit\inc;%(AdditionalIncludeDirectories) Level4 Console false $(SolutionDir)Built\Out\$(PlatformToolset)\$(Platform)\$(Configuration)\glTF-Toolkit\glTF-Toolkit.lib;%(AdditionalDependencies) Create Create Create Create Create Create Designer This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. ================================================ FILE: glTF-Toolkit.UWP/glTF-Toolkit.UWP.vcxproj.filters ================================================  580617e3-8f49-420a-8fbd-2f691cec17ac rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tga;tiff;tif;png;wav;mfcribbon-ms ================================================ FILE: glTF-Toolkit.UWP/packages.config ================================================  ================================================ FILE: glTF-Toolkit.UWP/pch.cpp ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #include "pch.h" ================================================ FILE: glTF-Toolkit.UWP/pch.h ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. #pragma once // Use the C++ standard templated min/max #define NOMINMAX // DirectX apps don't need GDI #define NODRAWTEXT #define NOGDI #define NOBITMAP // Include if you need this #define NOMCX // Include if you need this #define NOSERVICE // WinHelp is deprecated #define NOHELP #include #include #include #include #include ================================================ FILE: glTF-Toolkit.UWP.Test/Assets/3DModels/WaterBottle.glb ================================================ [File too large to display: 16.6 MB] ================================================ FILE: glTF-Toolkit.UWP.Test/Package.appxmanifest ================================================  glTF-Toolkit.UWP.Test jofalcon Assets\StoreLogo.png ================================================ FILE: glTF-Toolkit.UWP.Test/Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; [assembly: AssemblyTitle("glTF-Toolkit.UWP.Test")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("glTF-Toolkit.UWP.Test")] [assembly: AssemblyCopyright("Copyright © 2017")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] [assembly: AssemblyMetadata("TargetPlatform","UAP")] // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: ComVisible(false)] ================================================ FILE: glTF-Toolkit.UWP.Test/Properties/UnitTestApp.rd.xml ================================================ ================================================ FILE: glTF-Toolkit.UWP.Test/UWPTest.cs ================================================ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using System; using System.Threading.Tasks; using Microsoft.VisualStudio.TestTools.UnitTesting; using Windows.Foundation; using Windows.Security.Cryptography; using Windows.Storage; namespace Microsoft.glTF.Toolkit.UWP.Test { [TestClass] public class UWPTest { private async Task CopyFileToTempFolderAsync(Uri uri) { StorageFile appxAssetFile = await StorageFile.GetFileFromApplicationUriAsync(uri); StorageFile destinationFile = await appxAssetFile.CopyAsync(ApplicationData.Current.TemporaryFolder, appxAssetFile.Name, NameCollisionOption.ReplaceExisting); return destinationFile; } private IAsyncOperation CreateTemporaryOutputFolderAsync(string folderName) { return ApplicationData.Current.TemporaryFolder.CreateFolderAsync(folderName, CreationCollisionOption.ReplaceExisting); } private async Task CompareFilesAsync(StorageFile file1, StorageFile file2) { var buffer1 = await FileIO.ReadBufferAsync(file1); var buffer2 = await FileIO.ReadBufferAsync(file2); return CryptographicBuffer.Compare(buffer1, buffer2); } [TestMethod] public async Task GLBDeserializeSerialize() { const string glbBaseName = "WaterBottle"; const string glbFileName = glbBaseName + ".glb"; StorageFile sourceGlbFile = await CopyFileToTempFolderAsync(new Uri("ms-appx:///Assets/3DModels/" + glbFileName)); StorageFolder outputFolder = await CreateTemporaryOutputFolderAsync("Out_" + glbBaseName); // unpack the glb into gltf and all its companion files await GLTFSerialization.UnpackGLBAsync(sourceGlbFile, outputFolder); // compare one of the extracted images to the source images StorageFile sourceImageFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/3DModels/WaterBottle_diffuse.png")); StorageFile outputImageFile = await outputFolder.GetFileAsync(glbBaseName + "_image5.png"); Assert.IsTrue(await CompareFilesAsync(sourceImageFile, outputImageFile), "images"); // compare the extracted model (.bin) to the source model (.bin) file StorageFile sourceBinFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/3DModels/" + glbBaseName + ".bin")); StorageFile outputBinFile = await outputFolder.GetFileAsync(glbBaseName + ".bin"); Assert.IsTrue(await CompareFilesAsync(sourceBinFile, outputBinFile), "bins"); // Pack the gltf back into a glb file StorageFile gltfFile = await outputFolder.GetFileAsync(glbBaseName + ".gltf"); StorageFile outputGlbFile = await GLTFSerialization.PackGLTFAsync(gltfFile, outputFolder, glbFileName); // compare the new glb to the old glb Assert.IsTrue(await CompareFilesAsync(sourceGlbFile, outputGlbFile), "glb"); } [TestMethod] public async Task GLBConvertToWindowsMR() { const string glbBaseName = "WaterBottle"; const string glbFileName = glbBaseName + ".glb"; StorageFile sourceGlbFile = await CopyFileToTempFolderAsync(new Uri("ms-appx:///Assets/3DModels/" + glbFileName)); StorageFolder outputFolder = await CreateTemporaryOutputFolderAsync("Out_" + glbBaseName); var converted = await WindowsMRConversion.ConvertAssetForWindowsMR(sourceGlbFile, outputFolder, 512, TexturePacking.OcclusionRoughnessMetallic, true); Assert.IsTrue(converted.Name == "WaterBottle_converted.glb"); } } } ================================================ FILE: glTF-Toolkit.UWP.Test/UnitTestApp.xaml ================================================  ================================================ FILE: glTF-Toolkit.UWP.Test/UnitTestApp.xaml.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; using Windows.ApplicationModel; using Windows.ApplicationModel.Activation; using Windows.Foundation; using Windows.Foundation.Collections; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; using Windows.UI.Xaml.Data; using Windows.UI.Xaml.Input; using Windows.UI.Xaml.Media; using Windows.UI.Xaml.Navigation; namespace glTF_Toolkit.UWP.Test { /// /// Provides application-specific behavior to supplement the default Application class. /// sealed partial class App : Application { /// /// Initializes the singleton application object. This is the first line of authored code /// executed, and as such is the logical equivalent of main() or WinMain(). /// public App() { this.InitializeComponent(); this.Suspending += OnSuspending; } /// /// Invoked when the application is launched normally by the end user. Other entry points /// will be used such as when the application is launched to open a specific file. /// /// Details about the launch request and process. protected override void OnLaunched(LaunchActivatedEventArgs e) { #if DEBUG if (System.Diagnostics.Debugger.IsAttached) { this.DebugSettings.EnableFrameRateCounter = true; } #endif Frame rootFrame = Window.Current.Content as Frame; // Do not repeat app initialization when the Window already has content, // just ensure that the window is active if (rootFrame == null) { // Create a Frame to act as the navigation context and navigate to the first page rootFrame = new Frame(); rootFrame.NavigationFailed += OnNavigationFailed; if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) { //TODO: Load state from previously suspended application } // Place the frame in the current Window Window.Current.Content = rootFrame; } Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.CreateDefaultUI(); // Ensure the current window is active Window.Current.Activate(); Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.Run(e.Arguments); } /// /// Invoked when Navigation to a certain page fails /// /// The Frame which failed navigation /// Details about the navigation failure void OnNavigationFailed(object sender, NavigationFailedEventArgs e) { throw new Exception("Failed to load Page " + e.SourcePageType.FullName); } /// /// Invoked when application execution is being suspended. Application state is saved /// without knowing whether the application will be terminated or resumed with the contents /// of memory still intact. /// /// The source of the suspend request. /// Details about the suspend request. private void OnSuspending(object sender, SuspendingEventArgs e) { var deferral = e.SuspendingOperation.GetDeferral(); //TODO: Save application state and stop any background activity deferral.Complete(); } } } ================================================ FILE: glTF-Toolkit.UWP.Test/glTF-Toolkit.UWP.Test.csproj ================================================  Debug x86 {5365C33C-A3F9-4169-8C6F-A2DD3FABA575} AppContainerExe Properties Microsoft.glTF.Toolkit.UWP.Test glTF-Toolkit.UWP.Test en-US UAP 10.0.16299.0 10.0.16299.0 14 512 {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} $(VisualStudioVersion) $(SolutionDir)Built\Out\v141\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ $(SolutionDir)Built\Int\v141\$(Platform)\$(Configuration)\$(MSBuildProjectName)\ false true bin\x86\Debug\ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP ;2008 full x86 false prompt true bin\x86\Release\ TRACE;NETFX_CORE;WINDOWS_UWP true ;2008 pdbonly x86 false prompt true true true bin\ARM\Debug\ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP ;2008 full ARM false prompt true bin\ARM\Release\ TRACE;NETFX_CORE;WINDOWS_UWP true ;2008 pdbonly ARM false prompt true true true bin\x64\Debug\ DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP ;2008 full x64 false prompt true bin\x64\Release\ TRACE;NETFX_CORE;WINDOWS_UWP true ;2008 pdbonly x64 false prompt true true PackageReference UnitTestApp.xaml MSBuild:Compile Designer Designer 6.2.0-Preview1-26502-02 1.3.2 1.3.2 ..\Built\Out\v141\Win32\$(Configuration)\glTF-Toolkit.UWP\Microsoft.glTF.Toolkit.UWP.winmd ..\Built\Out\v141\$(Platform)\$(Configuration)\glTF-Toolkit.UWP\Microsoft.glTF.Toolkit.UWP.winmd 14.0 ================================================ FILE: glTF-Toolkit.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27004.2010 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "glTF-Toolkit", "glTF-Toolkit\glTF-Toolkit.vcxproj", "{FF0275F1-58CB-4745-BA81-F6C1DF66E206}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "glTF-Toolkit.Test", "glTF-Toolkit.Test\glTF-Toolkit.Test.vcxproj", "{B2AF77B5-8433-46AD-860D-23A4831F6830}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowsMRAssetConverter", "WindowsMRAssetConverter\WindowsMRAssetConverter.vcxproj", "{8A19D99C-78DC-4267-AB57-DB1DDBFBEFDF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E50FE4A3-A16D-4A05-A221-71CA2D972CF7}" ProjectSection(SolutionItems) = preProject .gitignore = .gitignore LICENSE = LICENSE README.md = README.md EndProjectSection EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "glTF-Toolkit.UWP", "glTF-Toolkit.UWP\glTF-Toolkit.UWP.vcxproj", "{697462FB-C8E9-4EA5-A499-033DF330E212}" ProjectSection(ProjectDependencies) = postProject {FF0275F1-58CB-4745-BA81-F6C1DF66E206} = {FF0275F1-58CB-4745-BA81-F6C1DF66E206} EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "glTF-Toolkit.UWP.Test", "glTF-Toolkit.UWP.Test\glTF-Toolkit.UWP.Test.csproj", "{5365C33C-A3F9-4169-8C6F-A2DD3FABA575}" ProjectSection(ProjectDependencies) = postProject {697462FB-C8E9-4EA5-A499-033DF330E212} = {697462FB-C8E9-4EA5-A499-033DF330E212} EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug Static Runtime|ARM = Debug Static Runtime|ARM Debug Static Runtime|x64 = Debug Static Runtime|x64 Debug Static Runtime|x86 = Debug Static Runtime|x86 Debug|ARM = Debug|ARM Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release Static Runtime|ARM = Release Static Runtime|ARM Release Static Runtime|x64 = Release Static Runtime|x64 Release Static Runtime|x86 = Release Static Runtime|x86 Release|ARM = Release|ARM Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Debug Static Runtime|ARM.ActiveCfg = Debug Static Runtime|ARM {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Debug Static Runtime|ARM.Build.0 = Debug Static Runtime|ARM {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Debug Static Runtime|x64.ActiveCfg = Debug Static Runtime|x64 {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Debug Static Runtime|x64.Build.0 = Debug Static Runtime|x64 {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Debug Static Runtime|x86.ActiveCfg = Debug Static Runtime|Win32 {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Debug Static Runtime|x86.Build.0 = Debug Static Runtime|Win32 {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Debug|ARM.ActiveCfg = Debug|ARM {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Debug|ARM.Build.0 = Debug|ARM {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Debug|x64.ActiveCfg = Debug|x64 {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Debug|x64.Build.0 = Debug|x64 {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Debug|x86.ActiveCfg = Debug|Win32 {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Debug|x86.Build.0 = Debug|Win32 {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Release Static Runtime|ARM.ActiveCfg = Release Static Runtime|ARM {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Release Static Runtime|ARM.Build.0 = Release Static Runtime|ARM {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Release Static Runtime|x64.ActiveCfg = Release Static Runtime|x64 {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Release Static Runtime|x64.Build.0 = Release Static Runtime|x64 {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Release Static Runtime|x86.ActiveCfg = Release Static Runtime|Win32 {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Release Static Runtime|x86.Build.0 = Release Static Runtime|Win32 {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Release|ARM.ActiveCfg = Release|ARM {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Release|ARM.Build.0 = Release|ARM {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Release|x64.ActiveCfg = Release|x64 {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Release|x64.Build.0 = Release|x64 {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Release|x86.ActiveCfg = Release|Win32 {FF0275F1-58CB-4745-BA81-F6C1DF66E206}.Release|x86.Build.0 = Release|Win32 {B2AF77B5-8433-46AD-860D-23A4831F6830}.Debug Static Runtime|ARM.ActiveCfg = Release|Win32 {B2AF77B5-8433-46AD-860D-23A4831F6830}.Debug Static Runtime|x64.ActiveCfg = Debug|x64 {B2AF77B5-8433-46AD-860D-23A4831F6830}.Debug Static Runtime|x86.ActiveCfg = Debug|Win32 {B2AF77B5-8433-46AD-860D-23A4831F6830}.Debug|ARM.ActiveCfg = Debug|Win32 {B2AF77B5-8433-46AD-860D-23A4831F6830}.Debug|x64.ActiveCfg = Debug|x64 {B2AF77B5-8433-46AD-860D-23A4831F6830}.Debug|x64.Build.0 = Debug|x64 {B2AF77B5-8433-46AD-860D-23A4831F6830}.Debug|x86.ActiveCfg = Debug|Win32 {B2AF77B5-8433-46AD-860D-23A4831F6830}.Debug|x86.Build.0 = Debug|Win32 {B2AF77B5-8433-46AD-860D-23A4831F6830}.Release Static Runtime|ARM.ActiveCfg = Release|Win32 {B2AF77B5-8433-46AD-860D-23A4831F6830}.Release Static Runtime|x64.ActiveCfg = Release|x64 {B2AF77B5-8433-46AD-860D-23A4831F6830}.Release Static Runtime|x86.ActiveCfg = Release|Win32 {B2AF77B5-8433-46AD-860D-23A4831F6830}.Release|ARM.ActiveCfg = Release|Win32 {B2AF77B5-8433-46AD-860D-23A4831F6830}.Release|x64.ActiveCfg = Release|x64 {B2AF77B5-8433-46AD-860D-23A4831F6830}.Release|x64.Build.0 = Release|x64 {B2AF77B5-8433-46AD-860D-23A4831F6830}.Release|x86.ActiveCfg = Release|Win32 {B2AF77B5-8433-46AD-860D-23A4831F6830}.Release|x86.Build.0 = Release|Win32 {8A19D99C-78DC-4267-AB57-DB1DDBFBEFDF}.Debug Static Runtime|ARM.ActiveCfg = Release|Win32 {8A19D99C-78DC-4267-AB57-DB1DDBFBEFDF}.Debug Static Runtime|x64.ActiveCfg = Debug|x64 {8A19D99C-78DC-4267-AB57-DB1DDBFBEFDF}.Debug Static Runtime|x86.ActiveCfg = Debug|Win32 {8A19D99C-78DC-4267-AB57-DB1DDBFBEFDF}.Debug|ARM.ActiveCfg = Debug|Win32 {8A19D99C-78DC-4267-AB57-DB1DDBFBEFDF}.Debug|x64.ActiveCfg = Debug|x64 {8A19D99C-78DC-4267-AB57-DB1DDBFBEFDF}.Debug|x64.Build.0 = Debug|x64 {8A19D99C-78DC-4267-AB57-DB1DDBFBEFDF}.Debug|x86.ActiveCfg = Debug|Win32 {8A19D99C-78DC-4267-AB57-DB1DDBFBEFDF}.Debug|x86.Build.0 = Debug|Win32 {8A19D99C-78DC-4267-AB57-DB1DDBFBEFDF}.Release Static Runtime|ARM.ActiveCfg = Release|Win32 {8A19D99C-78DC-4267-AB57-DB1DDBFBEFDF}.Release Static Runtime|x64.ActiveCfg = Release|x64 {8A19D99C-78DC-4267-AB57-DB1DDBFBEFDF}.Release Static Runtime|x86.ActiveCfg = Release|Win32 {8A19D99C-78DC-4267-AB57-DB1DDBFBEFDF}.Release|ARM.ActiveCfg = Release|Win32 {8A19D99C-78DC-4267-AB57-DB1DDBFBEFDF}.Release|x64.ActiveCfg = Release|x64 {8A19D99C-78DC-4267-AB57-DB1DDBFBEFDF}.Release|x64.Build.0 = Release|x64 {8A19D99C-78DC-4267-AB57-DB1DDBFBEFDF}.Release|x86.ActiveCfg = Release|Win32 {8A19D99C-78DC-4267-AB57-DB1DDBFBEFDF}.Release|x86.Build.0 = Release|Win32 {697462FB-C8E9-4EA5-A499-033DF330E212}.Debug Static Runtime|ARM.ActiveCfg = Debug|ARM {697462FB-C8E9-4EA5-A499-033DF330E212}.Debug Static Runtime|x64.ActiveCfg = Debug|x64 {697462FB-C8E9-4EA5-A499-033DF330E212}.Debug Static Runtime|x86.ActiveCfg = Debug|Win32 {697462FB-C8E9-4EA5-A499-033DF330E212}.Debug|ARM.ActiveCfg = Debug|ARM {697462FB-C8E9-4EA5-A499-033DF330E212}.Debug|ARM.Build.0 = Debug|ARM {697462FB-C8E9-4EA5-A499-033DF330E212}.Debug|x64.ActiveCfg = Debug|x64 {697462FB-C8E9-4EA5-A499-033DF330E212}.Debug|x64.Build.0 = Debug|x64 {697462FB-C8E9-4EA5-A499-033DF330E212}.Debug|x86.ActiveCfg = Debug|Win32 {697462FB-C8E9-4EA5-A499-033DF330E212}.Debug|x86.Build.0 = Debug|Win32 {697462FB-C8E9-4EA5-A499-033DF330E212}.Release Static Runtime|ARM.ActiveCfg = Release|ARM {697462FB-C8E9-4EA5-A499-033DF330E212}.Release Static Runtime|x64.ActiveCfg = Release|x64 {697462FB-C8E9-4EA5-A499-033DF330E212}.Release Static Runtime|x86.ActiveCfg = Release|Win32 {697462FB-C8E9-4EA5-A499-033DF330E212}.Release|ARM.ActiveCfg = Release|ARM {697462FB-C8E9-4EA5-A499-033DF330E212}.Release|ARM.Build.0 = Release|ARM {697462FB-C8E9-4EA5-A499-033DF330E212}.Release|x64.ActiveCfg = Release|x64 {697462FB-C8E9-4EA5-A499-033DF330E212}.Release|x64.Build.0 = Release|x64 {697462FB-C8E9-4EA5-A499-033DF330E212}.Release|x86.ActiveCfg = Release|Win32 {697462FB-C8E9-4EA5-A499-033DF330E212}.Release|x86.Build.0 = Release|Win32 {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Debug Static Runtime|ARM.ActiveCfg = Debug|ARM {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Debug Static Runtime|x64.ActiveCfg = Debug|x64 {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Debug Static Runtime|x86.ActiveCfg = Debug|x86 {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Debug|ARM.ActiveCfg = Debug|ARM {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Debug|ARM.Build.0 = Debug|ARM {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Debug|ARM.Deploy.0 = Debug|ARM {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Debug|x64.ActiveCfg = Debug|x64 {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Debug|x64.Build.0 = Debug|x64 {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Debug|x64.Deploy.0 = Debug|x64 {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Debug|x86.ActiveCfg = Debug|x86 {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Debug|x86.Build.0 = Debug|x86 {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Debug|x86.Deploy.0 = Debug|x86 {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Release Static Runtime|ARM.ActiveCfg = Release|ARM {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Release Static Runtime|x64.ActiveCfg = Release|x64 {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Release Static Runtime|x86.ActiveCfg = Release|x86 {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Release|ARM.ActiveCfg = Release|ARM {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Release|ARM.Build.0 = Release|ARM {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Release|ARM.Deploy.0 = Release|ARM {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Release|x64.ActiveCfg = Release|x64 {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Release|x64.Build.0 = Release|x64 {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Release|x64.Deploy.0 = Release|x64 {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Release|x86.ActiveCfg = Release|x86 {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Release|x86.Build.0 = Release|x86 {5365C33C-A3F9-4169-8C6F-A2DD3FABA575}.Release|x86.Deploy.0 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {04F37F7A-2349-4424-B26C-33237D33B0A4} EndGlobalSection EndGlobal