├── Resources ├── gltf.png ├── Icon128.png └── ButtonIcon_40x.png ├── Binaries └── Win64 │ ├── UE4Editor-GLTFLoader.dll │ └── UE4Editor.modules ├── Source └── GLTFLoader │ ├── Private │ ├── GLTFLoaderPrivatePCH.h │ ├── GLTFFactory.h │ ├── tiny_gltf_loader_license.txt │ ├── GLTFLoaderStyle.cpp │ ├── GLTFFactory.cpp │ ├── GLTFMeshBuilder.h │ ├── GLTFLoader.cpp │ ├── picojson.h │ ├── tiny_gltf_loader.h │ └── GLTFMeshBuilder.cpp │ ├── Public │ ├── GLTFLoaderCommands.cpp │ ├── GLTFLoaderStyle.h │ ├── GLTFLoaderCommands.h │ ├── GLTFImportOptions.h │ └── GLTFLoader.h │ └── GLTFLoader.Build.cs ├── GLTFLoader.uplugin ├── README.md └── LICENSE /Resources/gltf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertPoncelet/UnrealGLTFLoader/HEAD/Resources/gltf.png -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertPoncelet/UnrealGLTFLoader/HEAD/Resources/Icon128.png -------------------------------------------------------------------------------- /Resources/ButtonIcon_40x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertPoncelet/UnrealGLTFLoader/HEAD/Resources/ButtonIcon_40x.png -------------------------------------------------------------------------------- /Binaries/Win64/UE4Editor-GLTFLoader.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertPoncelet/UnrealGLTFLoader/HEAD/Binaries/Win64/UE4Editor-GLTFLoader.dll -------------------------------------------------------------------------------- /Binaries/Win64/UE4Editor.modules: -------------------------------------------------------------------------------- 1 | { 2 | "Changelist" : 3013449, 3 | "BuildId" : "521bff43-3bc1-4230-8558-29af88fc728e", 4 | "Modules" : 5 | { 6 | "GLTFLoader" : "UE4Editor-GLTFLoader.dll" 7 | } 8 | } -------------------------------------------------------------------------------- /Source/GLTFLoader/Private/GLTFLoaderPrivatePCH.h: -------------------------------------------------------------------------------- 1 | // Some copyright should be here... 2 | 3 | #include "SlateBasics.h" 4 | 5 | #include "GLTFLoader.h" 6 | 7 | // You should place include statements to your module's private header files here. You only need to 8 | // add includes for headers that are used in most of your module's source files though. -------------------------------------------------------------------------------- /GLTFLoader.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "GLTFLoader", 6 | "Description": "", 7 | "Category": "Other", 8 | "CreatedBy": "", 9 | "CreatedByURL": "", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "Modules": [ 14 | { 15 | "Name": "GLTFLoader", 16 | "Type": "Developer", 17 | "LoadingPhase": "Default" 18 | } 19 | ], 20 | "EnabledByDefault": false, 21 | "CanContainContent": false, 22 | "IsBetaVersion": false, 23 | "Installed": false 24 | } -------------------------------------------------------------------------------- /Source/GLTFLoader/Public/GLTFLoaderCommands.cpp: -------------------------------------------------------------------------------- 1 | // Some copyright should be here... 2 | 3 | #include "GLTFLoaderPrivatePCH.h" 4 | #include "GLTFLoaderCommands.h" 5 | 6 | #define LOCTEXT_NAMESPACE "FGLTFLoaderModule" 7 | 8 | void FGLTFLoaderCommands::RegisterCommands() 9 | { 10 | UI_COMMAND(OpenPluginWindow, "GLTFLoader", "Bring up GLTFLoader window", EUserInterfaceActionType::Button, FInputGesture()); 11 | UI_COMMAND(OpenImportWindow, "GLTF Import Window", "Choose a GLTF file to import", EUserInterfaceActionType::Button, FInputGesture()); 12 | } 13 | 14 | #undef LOCTEXT_NAMESPACE 15 | -------------------------------------------------------------------------------- /Source/GLTFLoader/Public/GLTFLoaderStyle.h: -------------------------------------------------------------------------------- 1 | /// @file GLTFLoaderStyle.h by Robert Poncelet 2 | #pragma once 3 | 4 | #include "SlateBasics.h" 5 | 6 | /// Boilerplate class provided by Unreal. 7 | class FGLTFLoaderStyle 8 | { 9 | public: 10 | 11 | static void Initialize(); 12 | 13 | static void Shutdown(); 14 | 15 | /** reloads textures used by slate renderer */ 16 | static void ReloadTextures(); 17 | 18 | /** @return The Slate style set for the Shooter game */ 19 | static const ISlateStyle& Get(); 20 | 21 | static FName GetStyleSetName(); 22 | 23 | private: 24 | 25 | static TSharedRef< class FSlateStyleSet > Create(); 26 | 27 | private: 28 | 29 | static TSharedPtr< class FSlateStyleSet > StyleInstance; 30 | }; -------------------------------------------------------------------------------- /Source/GLTFLoader/Public/GLTFLoaderCommands.h: -------------------------------------------------------------------------------- 1 | /// @file GLTFLoaderCommands.h by Robert Poncelet 2 | 3 | #pragma once 4 | 5 | #include "SlateBasics.h" 6 | #include "GLTFLoaderStyle.h" 7 | 8 | /// Boilerplate class provided by Unreal. 9 | class FGLTFLoaderCommands : public TCommands 10 | { 11 | public: 12 | 13 | FGLTFLoaderCommands() 14 | : TCommands(TEXT("GLTFLoader"), NSLOCTEXT("Contexts", "GLTFLoader", "GLTFLoader Plugin"), NAME_None, FGLTFLoaderStyle::GetStyleSetName()) 15 | { 16 | } 17 | 18 | // TCommands<> interface 19 | virtual void RegisterCommands() override; 20 | 21 | public: 22 | TSharedPtr< FUICommandInfo > OpenPluginWindow; 23 | TSharedPtr< FUICommandInfo > OpenImportWindow; 24 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unreal Engine glTF Loader Plugin 2 | 3 | Overview 4 | ------ 5 | This plugin makes use of Tiny glTF Loader [(link)](https://github.com/syoyo/tinygltfloader) to allow the user to import glTF files as static meshes. It has been tested with Unreal Engine version 4.12.3. 6 | 7 | Installation and Usage 8 | ------ 9 | To install, simply copy the repository folder to "Plugins" in your project root. You may need to recompile your project if your engine version differs from 4.12.3. 10 | 11 | To use, firstly enable the plugin in your settings if you don't see the glTF button in the top toolbar. Clicking it will open the plugin window, where you can modify transformation settings applied to the mesh before importing. The "Import File" button here will open a browser to import a glTF file as a static mesh to the current folder in the content browser. 12 | 13 | [Video Link](https://vimeo.com/182935578) 14 | -------------------------------------------------------------------------------- /Source/GLTFLoader/Private/GLTFFactory.h: -------------------------------------------------------------------------------- 1 | /// @file GLTFFactory.h by Robert Poncelet 2 | 3 | #pragma once 4 | 5 | #include "Factories/Factory.h" 6 | #include "GLTFFactory.generated.h" 7 | 8 | UCLASS() 9 | /// The class instantiated by the AssetToolsModule for importing the chosen UAsset into the content browser. Adapted from UFbxFactory. 10 | class UGLTFFactory : public UFactory 11 | { 12 | GENERATED_BODY() 13 | 14 | UGLTFFactory(const FObjectInitializer& ObjectInitializer); 15 | 16 | /// @name UFactory Implementation 17 | ///@{ 18 | virtual bool DoesSupportClass(UClass * Class) override; 19 | virtual UObject* FactoryCreateBinary(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const uint8*& Buffer, const uint8* BufferEnd, FFeedbackContext* Warn, bool& bOutOperationCanceled) override; 20 | virtual bool FactoryCanImport(const FString& Filename) override; 21 | ///@} 22 | 23 | bool bShowOption; 24 | bool bDetectImportTypeOnImport; 25 | 26 | /** true if the import operation was canceled. */ 27 | bool bOperationCanceled; 28 | 29 | }; 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Source/GLTFLoader/Private/tiny_gltf_loader_license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Syoyo Fujita 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 8 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /Source/GLTFLoader/GLTFLoader.Build.cs: -------------------------------------------------------------------------------- 1 | // Some copyright should be here... 2 | 3 | using UnrealBuildTool; 4 | 5 | public class GLTFLoader : ModuleRules 6 | { 7 | public GLTFLoader(TargetInfo Target) 8 | { 9 | 10 | PublicIncludePaths.AddRange( 11 | new string[] { 12 | "GLTFLoader/Public" 13 | 14 | // ... add public include paths required here ... 15 | } 16 | ); 17 | 18 | 19 | PrivateIncludePaths.AddRange( 20 | new string[] { 21 | "GLTFLoader/Private", 22 | 23 | // ... add other private include paths required here ... 24 | } 25 | ); 26 | 27 | 28 | PublicDependencyModuleNames.AddRange( 29 | new string[] 30 | { 31 | "Core", 32 | "CoreUObject", 33 | "Engine", 34 | "UnrealEd", 35 | "RawMesh", 36 | "RenderCore", // For FPackedNormal 37 | "MaterialUtilities", 38 | "MeshUtilities", 39 | "AssetTools" 40 | // ... add other public dependencies that you statically link with here ... 41 | } 42 | ); 43 | 44 | 45 | PrivateDependencyModuleNames.AddRange( 46 | new string[] 47 | { 48 | "Projects", 49 | "InputCore", 50 | "UnrealEd", 51 | "LevelEditor", 52 | "CoreUObject", 53 | "Engine", 54 | "Slate", 55 | "SlateCore", 56 | "RawMesh", 57 | "RenderCore", // For FPackedNormal 58 | "MaterialUtilities", 59 | "MeshUtilities", 60 | "AssetTools", 61 | "PropertyEditor", 62 | "EditorStyle", 63 | "EditorWidgets" 64 | // ... add private dependencies that you statically link with here ... 65 | } 66 | ); 67 | 68 | 69 | DynamicallyLoadedModuleNames.AddRange( 70 | new string[] 71 | { 72 | 73 | // ... add any modules that your module loads dynamically here ... 74 | } 75 | ); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Source/GLTFLoader/Public/GLTFImportOptions.h: -------------------------------------------------------------------------------- 1 | /// @file GLTFImportOptions.h by Robert Poncelet 2 | 3 | #pragma once 4 | 5 | //#include "Editor/UnrealEd/Classes/Factories/FbxStaticMeshImportData.h" 6 | #include "UnrealString.h" 7 | #include "Vector.h" 8 | #include "Rotator.h" 9 | #include "Color.h" 10 | #include "NameTypes.h" 11 | 12 | /// Used to store the information passed from the user to the import process - some of which is unused. This struct is adapted from FBXImportOptions. 13 | struct GLTFImportOptions 14 | { 15 | // General options 16 | bool bImportMaterials; 17 | bool bInvertNormalMap; 18 | bool bImportTextures; 19 | bool bImportLOD; 20 | bool bUsedAsFullName; 21 | bool bConvertScene; 22 | bool bRemoveNameSpace; 23 | bool bPreserveLocalTransform; 24 | FVector ImportTranslation; 25 | FRotator ImportRotation; 26 | float ImportUniformScale; 27 | bool bCorrectUpDirection; 28 | 29 | // Static Mesh options 30 | bool bCombineToSingle; 31 | FColor VertexOverrideColor; 32 | bool bRemoveDegenerates; 33 | bool bBuildAdjacencyBuffer; 34 | bool bGenerateLightmapUVs; 35 | bool bOneConvexHullPerUCX; 36 | bool bAutoGenerateCollision; 37 | 38 | FName StaticMeshLODGroup; 39 | 40 | static GLTFImportOptions Default() 41 | { 42 | GLTFImportOptions ImportOptions; 43 | ImportOptions.bAutoGenerateCollision = false; 44 | ImportOptions.bBuildAdjacencyBuffer = true; 45 | ImportOptions.bCombineToSingle = true; 46 | ImportOptions.bConvertScene = false; 47 | ImportOptions.bGenerateLightmapUVs = false; 48 | ImportOptions.bImportLOD = false; 49 | ImportOptions.bImportMaterials = false; 50 | ImportOptions.bImportTextures = false; 51 | ImportOptions.bInvertNormalMap = false; 52 | ImportOptions.bOneConvexHullPerUCX = true; 53 | ImportOptions.bPreserveLocalTransform = true; 54 | ImportOptions.bRemoveDegenerates = false; 55 | ImportOptions.bRemoveNameSpace = true; 56 | ImportOptions.bUsedAsFullName = false; 57 | ImportOptions.ImportRotation = FRotator(0.0f, 0.0f, 0.0f); 58 | ImportOptions.ImportTranslation = FVector::ZeroVector; 59 | ImportOptions.ImportUniformScale = 1.0f; 60 | ImportOptions.StaticMeshLODGroup = NAME_None; 61 | ImportOptions.VertexOverrideColor = FColor::White; 62 | ImportOptions.bCorrectUpDirection = true; 63 | return ImportOptions; 64 | } 65 | }; 66 | 67 | -------------------------------------------------------------------------------- /Source/GLTFLoader/Private/GLTFLoaderStyle.cpp: -------------------------------------------------------------------------------- 1 | // Some copyright should be here... 2 | #include "GLTFLoaderPrivatePCH.h" 3 | 4 | #include "GLTFLoaderStyle.h" 5 | #include "SlateGameResources.h" 6 | #include "IPluginManager.h" 7 | 8 | TSharedPtr< FSlateStyleSet > FGLTFLoaderStyle::StyleInstance = NULL; 9 | 10 | void FGLTFLoaderStyle::Initialize() 11 | { 12 | if (!StyleInstance.IsValid()) 13 | { 14 | StyleInstance = Create(); 15 | FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance); 16 | } 17 | } 18 | 19 | void FGLTFLoaderStyle::Shutdown() 20 | { 21 | FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance); 22 | ensure(StyleInstance.IsUnique()); 23 | StyleInstance.Reset(); 24 | } 25 | 26 | FName FGLTFLoaderStyle::GetStyleSetName() 27 | { 28 | static FName StyleSetName(TEXT("GLTFLoaderStyle")); 29 | return StyleSetName; 30 | } 31 | 32 | #define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) 33 | #define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) 34 | #define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) 35 | #define TTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) 36 | #define OTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) 37 | 38 | const FVector2D Icon16x16(16.0f, 16.0f); 39 | const FVector2D Icon20x20(20.0f, 20.0f); 40 | const FVector2D Icon40x40(40.0f, 40.0f); 41 | 42 | TSharedRef< FSlateStyleSet > FGLTFLoaderStyle::Create() 43 | { 44 | TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("GLTFLoaderStyle")); 45 | Style->SetContentRoot(IPluginManager::Get().FindPlugin("GLTFLoader")->GetBaseDir() / TEXT("Resources")); 46 | 47 | Style->Set("GLTFLoader.OpenPluginWindow", new IMAGE_BRUSH(TEXT("ButtonIcon_40x"), Icon40x40)); 48 | 49 | return Style; 50 | } 51 | 52 | #undef IMAGE_BRUSH 53 | #undef BOX_BRUSH 54 | #undef BORDER_BRUSH 55 | #undef TTF_FONT 56 | #undef OTF_FONT 57 | 58 | void FGLTFLoaderStyle::ReloadTextures() 59 | { 60 | FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); 61 | } 62 | 63 | const ISlateStyle& FGLTFLoaderStyle::Get() 64 | { 65 | return *StyleInstance; 66 | } 67 | -------------------------------------------------------------------------------- /Source/GLTFLoader/Public/GLTFLoader.h: -------------------------------------------------------------------------------- 1 | /// @file GLTFLoader.h by Robert Poncelet 2 | 3 | #pragma once 4 | 5 | #include "ModuleManager.h" 6 | #include "SlateBasics.h" 7 | #include "GLTFImportOptions.h" 8 | 9 | class FToolBarBuilder; 10 | class FMenuBuilder; 11 | 12 | /// 13 | /// \brief The top-level class in the plugin module. 14 | /// 15 | /// This class handles the UI and the corresponding actions to set off the importing 16 | /// process. Functions/members provided as boilerplate by Unreal's plugin creation 17 | /// wizard are marked with \"(Boilerplate)\". 18 | /// 19 | class FGLTFLoaderModule : public IModuleInterface 20 | { 21 | public: 22 | ///@name (Boilerplate) IModuleInterface implementation. 23 | ///@{ 24 | virtual void StartupModule() override; 25 | virtual void ShutdownModule() override; 26 | ///@} 27 | 28 | /// (Boilerplate) This function will be bound to a TCommand to bring up the plugin window. 29 | void PluginButtonClicked(); 30 | 31 | /// Opens the file browser for the user to select a file to import. 32 | void OpenImportWindow(); 33 | 34 | /// Since this class doesn't instantiate the Factory directly, the import options are provided statically and publicly so that GLTFFactory can access them itself. 35 | static GLTFImportOptions ImportOptions; 36 | 37 | private: 38 | 39 | /// Boilerplate 40 | void AddToolbarExtension(FToolBarBuilder& Builder); 41 | void AddMenuExtension(FMenuBuilder& Builder); 42 | 43 | /// Bound to the import button's OnClicked event which expects an FReply; OpenImportWindow() itself is used for FGLTFLoaderCommands which expects a void. 44 | FReply OpenImportWindowDelegateFunc() { OpenImportWindow(); return FReply::Handled(); } 45 | 46 | /// @name UI Setters 47 | ///@{ 48 | /// These functions are bound to the UI elements when they are created and called to update the options' data when the user modifies the values. 49 | void SetImportTX (float Value); 50 | void SetImportTY (float Value); 51 | void SetImportTZ (float Value); 52 | void SetImportRPitch (float Value); 53 | void SetImportRYaw (float Value); 54 | void SetImportRRoll (float Value); 55 | void SetImportScale (float Value); 56 | void SetCorrectUp (ECheckBoxState Value); 57 | ///@} 58 | 59 | /// @name UI Getters 60 | ///@{ 61 | /// These functions are bound to the UI elements when they are created and called to validate the displayed UI values once the options' data is updated. 62 | TOptional GetImportTX() const; 63 | TOptional GetImportTY() const; 64 | TOptional GetImportTZ() const; 65 | TOptional GetImportRPitch() const; 66 | TOptional GetImportRYaw() const; 67 | TOptional GetImportRRoll() const; 68 | TOptional GetImportScale() const; 69 | ECheckBoxState GetCorrectUp() const; 70 | ///@} 71 | 72 | /// (Boilerplate) Brings up the main plugin window. 73 | TSharedRef OnSpawnPluginTab(const class FSpawnTabArgs& SpawnTabArgs); 74 | 75 | private: 76 | 77 | /// (Boilerplate) Used to link with FGLTFLoaderCommands. 78 | TSharedPtr PluginCommands; 79 | }; -------------------------------------------------------------------------------- /Source/GLTFLoader/Private/GLTFFactory.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #include "GLTFLoaderPrivatePCH.h" 4 | #include "GLTFFactory.h" 5 | 6 | #include "Engine.h" 7 | #include "Editor/UnrealEd/Public/Editor.h" 8 | #include "GLTFMeshBuilder.h" 9 | 10 | #include "UnrealEd.h" 11 | #include "Factories.h" 12 | #include "BusyCursor.h" 13 | #include "SSkeletonWidget.h" 14 | 15 | #include "AssetRegistryModule.h" 16 | #include "Engine/StaticMesh.h" 17 | 18 | #include "FbxErrors.h" 19 | 20 | #define LOCTEXT_NAMESPACE "GLTFFactory" 21 | 22 | UGLTFFactory::UGLTFFactory(const FObjectInitializer& ObjectInitializer) 23 | : Super(ObjectInitializer) 24 | { 25 | SupportedClass = UStaticMesh::StaticClass(); 26 | Formats.Add(TEXT("gltf;GLTF meshes")); 27 | 28 | bCreateNew = false; 29 | bText = false; 30 | bEditorImport = true; 31 | bOperationCanceled = false; 32 | bDetectImportTypeOnImport = false; 33 | } 34 | 35 | //This function is adapted from UFbxFactory::CreateBinary() 36 | UObject* UGLTFFactory::FactoryCreateBinary 37 | ( 38 | UClass* Class, 39 | UObject* InParent, 40 | FName Name, 41 | EObjectFlags Flags, 42 | UObject* Context, 43 | const TCHAR* Type, 44 | const uint8*& Buffer, 45 | const uint8* BufferEnd, 46 | FFeedbackContext* Warn, 47 | bool& bOutOperationCanceled 48 | ) 49 | { 50 | if (bOperationCanceled) 51 | { 52 | bOutOperationCanceled = true; 53 | FEditorDelegates::OnAssetPostImport.Broadcast(this, NULL); 54 | return NULL; 55 | } 56 | 57 | FEditorDelegates::OnAssetPreImport.Broadcast(this, Class, InParent, Name, Type); 58 | 59 | UObject* NewObject = NULL; 60 | 61 | GLTFMeshBuilder Builder(*UFactory::CurrentFilename); 62 | 63 | bool bShowImportDialog = bShowOption && !GIsAutomationTesting; 64 | bool bImportAll = false; 65 | auto ImportOptions = FGLTFLoaderModule::ImportOptions; 66 | bOutOperationCanceled = bOperationCanceled; 67 | 68 | if (bImportAll) 69 | { 70 | // If the user chose to import all, we don't show the dialog again and use the same settings for each object until importing another set of files 71 | bShowOption = false; 72 | } 73 | 74 | // For multiple files, use the same settings 75 | bDetectImportTypeOnImport = false; 76 | 77 | Warn->BeginSlowTask(NSLOCTEXT("GLTFFactory", "BeginImportingGLTFMeshTask", "Importing GLTF mesh"), true); 78 | if (!Builder.LoadedSuccessfully()) 79 | { 80 | // Log the error message and fail the import. 81 | Warn->Log(ELogVerbosity::Error, Builder.GetError()); 82 | } 83 | else 84 | { 85 | // Log the import message and import the mesh. 86 | const FString errorMessage = Builder.GetError(); 87 | if (errorMessage.Len() > 0) 88 | { 89 | Warn->Log(errorMessage); 90 | } 91 | 92 | FString RootNodeToImport = ""; 93 | RootNodeToImport = Builder.GetRootNode(); 94 | 95 | // For animation and static mesh we assume there is at lease one interesting node by default 96 | int32 InterestingNodeCount = 1; 97 | 98 | bool bImportStaticMeshLODs = /*ImportUI->StaticMeshImportData->bImportMeshLODs*/ false; 99 | bool bCombineMeshes = /*ImportUI->bCombineMeshes*/ true; 100 | 101 | if (bCombineMeshes && !bImportStaticMeshLODs) 102 | { 103 | // If Combine meshes and dont import mesh LODs, the interesting node count should be 1 so all the meshes are grouped together into one static mesh 104 | InterestingNodeCount = 1; 105 | } 106 | else 107 | { 108 | // count meshes in lod groups if we dont care about importing LODs 109 | bool bCountLODGroupMeshes = !bImportStaticMeshLODs; 110 | int32 NumLODGroups = 0; 111 | InterestingNodeCount = Builder.GetMeshCount(RootNodeToImport/*, bCountLODGroupMeshes, NumLODGroups*/); 112 | 113 | // if there were LODs in the file, do not combine meshes even if requested 114 | if (bImportStaticMeshLODs && bCombineMeshes) 115 | { 116 | bCombineMeshes = NumLODGroups == 0; 117 | } 118 | } 119 | 120 | const FString Filename(UFactory::CurrentFilename); 121 | if (RootNodeToImport.Len() != 0 && InterestingNodeCount > 0) 122 | { 123 | int32 NodeIndex = 0; 124 | 125 | int32 ImportedMeshCount = 0; 126 | UStaticMesh* NewStaticMesh = NULL; 127 | 128 | if (bCombineMeshes) 129 | { 130 | auto MeshNames = Builder.GetMeshNames(RootNodeToImport); 131 | if (MeshNames.Num() > 0) 132 | { 133 | NewStaticMesh = Builder.ImportStaticMeshAsSingle(InParent, MeshNames, Name, Flags/*, ImportUI->StaticMeshImportData*/, NULL/*, 0*/); 134 | for (auto Mesh : MeshNames) 135 | { 136 | Warn->Log(FString("Found mesh: ") + Mesh); 137 | } 138 | } 139 | 140 | ImportedMeshCount = NewStaticMesh ? 1 : 0; 141 | } 142 | 143 | NewObject = NewStaticMesh; 144 | } 145 | 146 | else 147 | { 148 | if (RootNodeToImport == "") 149 | { 150 | Builder.AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FailedToImport_InvalidRoot", "Could not find root node.")), FFbxErrors::SkeletalMesh_InvalidRoot); 151 | } 152 | else 153 | { 154 | Builder.AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FailedToImport_InvalidNode", "Could not find any node.")), FFbxErrors::SkeletalMesh_InvalidNode); 155 | } 156 | } 157 | } 158 | 159 | if (NewObject == NULL) 160 | { 161 | // Import fail error message 162 | Builder.AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FailedToImport_NoObject", "Import failed.")), FFbxErrors::Generic_ImportingNewObjectFailed); 163 | Warn->Log(ELogVerbosity::Warning, "Failed to import GLTF mesh"); 164 | } 165 | 166 | Warn->EndSlowTask(); 167 | 168 | FEditorDelegates::OnAssetPostImport.Broadcast(this, NewObject); 169 | 170 | return NewObject; 171 | } 172 | 173 | bool UGLTFFactory::DoesSupportClass(UClass * Class) 174 | { 175 | return (Class == UStaticMesh::StaticClass()); 176 | } 177 | 178 | bool UGLTFFactory::FactoryCanImport(const FString& Filename) 179 | { 180 | const FString Extension = FPaths::GetExtension(Filename); 181 | 182 | if (Extension == TEXT("gltf")) 183 | { 184 | return true; 185 | } 186 | return false; 187 | } 188 | 189 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Source/GLTFLoader/Private/GLTFMeshBuilder.h: -------------------------------------------------------------------------------- 1 | /// @file GLTFMeshBuilder.h by Robert Poncelet 2 | 3 | #pragma once 4 | 5 | #include "UnrealString.h" 6 | #include "TokenizedMessage.h" 7 | #include "GLTFImportOptions.h" 8 | 9 | #include 10 | #include 11 | 12 | class UStaticMesh; 13 | class UMaterialInterface; 14 | struct FRawMesh; 15 | 16 | /// Forward-declared TinyGLTF types since its header can only be #included in one source file. 17 | /// This also means that we must use pointers to these types outside of GLTFMeshBuilder.cpp. 18 | namespace tinygltf 19 | { 20 | class TinyGLTFLoader; 21 | class Scene; 22 | class Node; 23 | struct ACCESSOR; 24 | typedef struct ACCESSOR Accessor; 25 | struct PRIMITIVE; 26 | typedef struct PRIMITIVE Primitive; 27 | struct MESH; 28 | typedef struct MESH Mesh; 29 | struct MATERIAL; 30 | typedef struct MATERIAL Material; 31 | } 32 | 33 | /// Works in conjunction with TinyGLTF and Unreal's Static Mesh build system to return a UStaticMesh to the factory. This class is adapted from FbxImporter. 34 | class GLTFMeshBuilder 35 | { 36 | public: 37 | GLTFMeshBuilder(FString FilePath); 38 | ~GLTFMeshBuilder(); 39 | 40 | /// Returns whether we have a valid glTF scene loaded up. For a new MeshBuilder, this should always be queried before calling other functions. 41 | bool LoadedSuccessfully() { return LoadSuccess; } 42 | 43 | /// Returns the number of meshes owned by a given node. 44 | int32 GetMeshCount(FString NodeName); 45 | 46 | /// Returns the name of the scene's root node. 47 | FString GetRootNode(); 48 | 49 | /// Obtains the mesh names of a node (and optionally its children); useful as an argument to ImportStaticMeshAsSingle(). 50 | TArray GetMeshNames(FString NodeName, bool GetChildren = true); 51 | 52 | /// Organises materials and builds the StaticMesh using a RawMesh filled with data using BuildStaticMeshFromGeometry(). 53 | /// This function mirrors that in FFbxImporter of the same name. 54 | /// @param InParent A pointer provided by the system importing the file (i.e. probably AssetImportModule) so we know where the saved package goes. 55 | /// @param MeshNameArray An array of strings used as keys to obtain the actual meshes from the glTF scene. 56 | /// @param InName The name of the package to be saved. 57 | /// @param Flags Metadata used for the creation of the new package. 58 | /// @param InStaticMesh A pointer to the StaticMesh to be built and have this new geometry added to it. 59 | UStaticMesh* ImportStaticMeshAsSingle(UObject* InParent, TArray& MeshNameArray, const FName InName, EObjectFlags Flags, UStaticMesh* InStaticMesh); 60 | 61 | /// Obtains the geometry data from the file and adds it to the RawMesh ready to be built for the StaticMesh. 62 | /// This function mirrors that in FFbxImporter of the same name. 63 | /// @param Mesh The glTF mesh to grab the data from. 64 | /// @param StaticMesh The asset the mesh will be built for. This will eventually be the object serialised and saved in the import process. 65 | /// @param MeshMaterials An array of materials to convert to those used by the engine and send to the build process. 66 | /// @param LODIndex Level of detail for this mesh - currently unused i.e. always 0. 67 | /// @param RawMesh The intermediate container for the data between the external format (glTF in our case) and the built StaticMesh. 68 | bool BuildStaticMeshFromGeometry(tinygltf::Mesh* Mesh, UStaticMesh* StaticMesh, int LODIndex, FRawMesh& RawMesh); 69 | 70 | /// Material/texture system does nothing currently. 71 | UMaterialInterface* ToUMaterial(tinygltf::Material* Material); 72 | void CreateUnrealMaterial(tinygltf::Material* Material, TArray& OutMaterials) { return; }; 73 | int32 CreateNodeMaterials(tinygltf::Node* Node, TArray& OutMaterials) { return 0; }; 74 | void ImportTexturesFromNode(tinygltf::Node* Node) { return; } 75 | 76 | /// Logs an error message. 77 | void AddTokenizedErrorMessage(TSharedRef Error, FName ErrorName); 78 | /// Returns the error message left by TinyGLTFLoader, if any. 79 | FString GetError(); 80 | 81 | private: 82 | // Templated data copy functions, from highest to lowest level: 83 | 84 | /// @name Level 4: ConvertAttrib 85 | ///@{ 86 | /// Fills a TArray with a particular vertex attribute. Set InAttribName to "__WedgeIndex" to use the "indices" accessor for each primitive, or "__MaterialIndices" to obtain the material index. 87 | /// @param OutArray The array to fill with data from the imported file. 88 | /// @param Mesh The glTF from which to convert the specified attribute. 89 | /// @param AttribName The name of the attribute to convert. 90 | /// @param UseWedgeIndices Whether to copy data for each triangle corner ("wedge") or each vertex. 91 | /// @param AutoSetArraySize Whether to resize the array to the number of elements in this attribute (usually false since we may be adding to data from another mesh). 92 | template bool ConvertAttrib(TArray &OutArray, tinygltf::Mesh* Mesh, std::string AttribName, bool UseWedgeIndices = true, bool AutoSetArraySize = false); 93 | ///@} 94 | 95 | /// @name Level 3: GetBufferData 96 | ///@{ 97 | /// Fills a TArray with typed data; works at the glTF Accessor level and figures out which arguments to send to BufferCopy(). 98 | /// @param OutArray The array to fill with data from the imported file. 99 | /// @param Accessor A pointer to a glTF Accessor containing the type data for the geometry attribute. 100 | /// @param Append Whether to add to the array or overwrite the elements currently in it. 101 | template bool GetBufferData (TArray &OutArray, tinygltf::Accessor* Accessor, bool Append = true); 102 | template <> bool GetBufferData (TArray &OutArray, tinygltf::Accessor* Accessor, bool Append ); 103 | template <> bool GetBufferData (TArray &OutArray, tinygltf::Accessor* Accessor, bool Append ); 104 | template <> bool GetBufferData (TArray &OutArray, tinygltf::Accessor* Accessor, bool Append ); 105 | ///@} 106 | 107 | /// @name Level 2: BufferCopy 108 | ///@{ 109 | /// Handles filling the TArray at the data type level. 110 | /// @param OutArray The array to fill with data from the imported file. 111 | /// @param Data A pointer to the raw data to use as the argument to BufferValue(). 112 | /// @param Count The number of elements to add to the array i.e. the number of calls to BufferValue(). 113 | /// @param Stride The number of bytes between the first byte of each element - usually the size of one element. 114 | template void BufferCopy(TArray &OutArray, unsigned char* Data, int32 Count, size_t Stride); 115 | template void BufferCopy(TArray &OutArray, unsigned char* Data, int32 Count, size_t Stride); 116 | template void BufferCopy(TArray &OutArray, unsigned char* Data, int32 Count, size_t Stride); 117 | template void BufferCopy(TArray &OutArray, unsigned char* Data, int32 Count, size_t Stride); 118 | template void BufferCopy(TArray &OutArray, int Type, unsigned char* Data, int32 Count, size_t Stride); 119 | ///@} 120 | 121 | /// @name Level 1: BufferValue 122 | ///@{ 123 | /// Obtains a single value from the geometry data buffer, accounting for endianness. 124 | /// Adapted from http://stackoverflow.com/questions/13001183/how-to-read-little-endian-integers-from-file-in-c 125 | /// @param Data A pointer to the raw data to cast to the desired type. 126 | /// @return The typed data value. 127 | template T BufferValue(void* Data); 128 | ///@} 129 | 130 | /// Separate function to obtain material indices since it is not stored as a buffer. Should be called after MeshMaterials has been filled in. 131 | void GetMaterialIndices(TArray& OutArray, tinygltf::Mesh& Mesh); 132 | 133 | // Miscellaneous helper functions 134 | 135 | /// Reverses the order of every group of 3 elements. 136 | template void ReverseTriDirection(TArray& OutArray); 137 | /// Whether a mesh's geometry has a specified attribute. 138 | bool HasAttribute(tinygltf::Mesh* Mesh, std::string AttribName) const; 139 | /// Similar to TArray's Find() function; returns the array index if the specified object was found, -1 otherwise. 140 | template int32 FindInStdVector(const std::vector &InVector, const T &InElement) const; 141 | /// Returns the transform of a node relative to its parent. 142 | FMatrix GetNodeTransform(tinygltf::Node* Node); 143 | /// Returns the size of the C++ data type given the corresponding glTF type. 144 | size_t TypeSize(int Type) const; 145 | /// Returns the number of triangle corners given a glTF primitive, taking into account its draw mode. 146 | int32 GetNumWedges(tinygltf::Primitive* Prim) const; 147 | /// Returns the owning node of a given mesh. 148 | tinygltf::Node* GetMeshParentNode(tinygltf::Mesh* InMesh); 149 | 150 | /// @name String Conversion 151 | ///@{ 152 | /// Helper functions for converting between Unreal's and STL's strings. 153 | static FString ToFString(std::string InString); 154 | static std::string ToStdString(FString InString); 155 | ///@} 156 | 157 | /// 158 | TWeakObjectPtr Parent; 159 | tinygltf::TinyGLTFLoader* Loader; 160 | tinygltf::Scene* Scene; 161 | TArray MeshMaterials; 162 | bool LoadSuccess; 163 | FString Error; 164 | }; 165 | 166 | -------------------------------------------------------------------------------- /Source/GLTFLoader/Private/GLTFLoader.cpp: -------------------------------------------------------------------------------- 1 | // Some copyright should be here... 2 | 3 | #include "GLTFLoaderPrivatePCH.h" 4 | 5 | #include "SlateBasics.h" 6 | #include "SlateExtras.h" 7 | 8 | #include "Developer/DesktopPlatform/Public/DesktopPlatformModule.h" 9 | #include "Developer/AssetTools/Public/AssetToolsModule.h" 10 | 11 | #include "GLTFLoaderStyle.h" 12 | #include "GLTFLoaderCommands.h" 13 | #include "GLTFFactory.h" 14 | 15 | #include "LevelEditor.h" 16 | 17 | static const FName GLTFLoaderTabName("GLTFLoader"); 18 | 19 | GLTFImportOptions FGLTFLoaderModule::ImportOptions = GLTFImportOptions::Default(); 20 | 21 | #define LOCTEXT_NAMESPACE "FGLTFLoaderModule" 22 | 23 | void FGLTFLoaderModule::StartupModule() 24 | { 25 | FGLTFLoaderStyle::Initialize(); 26 | FGLTFLoaderStyle::ReloadTextures(); 27 | 28 | FGLTFLoaderCommands::Register(); 29 | 30 | PluginCommands = MakeShareable(new FUICommandList); 31 | 32 | PluginCommands->MapAction( 33 | FGLTFLoaderCommands::Get().OpenPluginWindow, 34 | FExecuteAction::CreateRaw(this, &FGLTFLoaderModule::PluginButtonClicked), 35 | FCanExecuteAction()); 36 | 37 | PluginCommands->MapAction( 38 | FGLTFLoaderCommands::Get().OpenImportWindow, 39 | FExecuteAction::CreateRaw(this, &FGLTFLoaderModule::OpenImportWindow), 40 | FCanExecuteAction()); 41 | 42 | FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); 43 | 44 | { 45 | TSharedPtr MenuExtender = MakeShareable(new FExtender()); 46 | MenuExtender->AddMenuExtension("WindowLayout", EExtensionHook::After, PluginCommands, FMenuExtensionDelegate::CreateRaw(this, &FGLTFLoaderModule::AddMenuExtension)); 47 | 48 | LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender); 49 | } 50 | 51 | { 52 | TSharedPtr ToolbarExtender = MakeShareable(new FExtender); 53 | ToolbarExtender->AddToolBarExtension("Settings", EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FGLTFLoaderModule::AddToolbarExtension)); 54 | 55 | LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); 56 | } 57 | 58 | FGlobalTabmanager::Get()->RegisterNomadTabSpawner(GLTFLoaderTabName, FOnSpawnTab::CreateRaw(this, &FGLTFLoaderModule::OnSpawnPluginTab)) 59 | .SetDisplayName(LOCTEXT("FGLTFLoaderTabTitle", "GLTFLoader")) 60 | .SetMenuType(ETabSpawnerMenuType::Hidden); 61 | 62 | UE_LOG(LogInit, Warning, TEXT("GLTFLoader module started successfully.")); 63 | } 64 | 65 | void FGLTFLoaderModule::ShutdownModule() 66 | { 67 | FGLTFLoaderStyle::Shutdown(); 68 | 69 | FGLTFLoaderCommands::Unregister(); 70 | 71 | FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(GLTFLoaderTabName); 72 | } 73 | 74 | TSharedRef FGLTFLoaderModule::OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs) 75 | { 76 | return SNew(SDockTab) 77 | .TabRole(ETabRole::NomadTab) 78 | [ 79 | SNew(SVerticalBox) 80 | + SVerticalBox::Slot() 81 | .Padding(1.0f) 82 | .HAlign(HAlign_Center) 83 | .VAlign(VAlign_Top) 84 | [ 85 | SNew(STextBlock) 86 | .Text(LOCTEXT("TopText", "Import a glTF file")) 87 | ] 88 | + SVerticalBox::Slot() 89 | .Padding(1.0f) 90 | .HAlign(HAlign_Fill) 91 | .VAlign(VAlign_Center) 92 | [ 93 | SNew(SHorizontalBox) 94 | + SHorizontalBox::Slot() 95 | .Padding(1.0f) 96 | .HAlign(HAlign_Center) 97 | .VAlign(VAlign_Center) 98 | [ 99 | SNew(STextBlock) 100 | .Text(LOCTEXT("ImportTranslation", "Import Translation")) 101 | ] 102 | + SHorizontalBox::Slot() 103 | .Padding(1.0f) 104 | .HAlign(HAlign_Center) 105 | .VAlign(VAlign_Center) 106 | [ 107 | SNew(SVectorInputBox) 108 | .X_Raw(this, &FGLTFLoaderModule::GetImportTX) 109 | .Y_Raw(this, &FGLTFLoaderModule::GetImportTY) 110 | .Z_Raw(this, &FGLTFLoaderModule::GetImportTZ) 111 | .bColorAxisLabels(true) 112 | .AllowResponsiveLayout(true) 113 | .OnXChanged_Raw(this, &FGLTFLoaderModule::SetImportTX) 114 | .OnYChanged_Raw(this, &FGLTFLoaderModule::SetImportTY) 115 | .OnZChanged_Raw(this, &FGLTFLoaderModule::SetImportTZ) 116 | ] 117 | ] 118 | + SVerticalBox::Slot() 119 | .Padding(1.0f) 120 | .HAlign(HAlign_Fill) 121 | .VAlign(VAlign_Center) 122 | [ 123 | SNew(SHorizontalBox) 124 | + SHorizontalBox::Slot() 125 | .Padding(1.0f) 126 | .HAlign(HAlign_Center) 127 | .VAlign(VAlign_Center) 128 | [ 129 | SNew(STextBlock) 130 | .Text(LOCTEXT("ImportRotation", "Import Rotation")) 131 | ] 132 | + SHorizontalBox::Slot() 133 | .Padding(1.0f) 134 | .HAlign(HAlign_Center) 135 | .VAlign(VAlign_Center) 136 | [ 137 | SNew(SRotatorInputBox) 138 | .Pitch_Raw(this, &FGLTFLoaderModule::GetImportRPitch) 139 | .Yaw_Raw(this, &FGLTFLoaderModule::GetImportRYaw) 140 | .Roll_Raw(this, &FGLTFLoaderModule::GetImportRRoll) 141 | .bColorAxisLabels(true) 142 | .AllowResponsiveLayout(true) 143 | .OnPitchChanged_Raw(this, &FGLTFLoaderModule::SetImportRPitch) 144 | .OnYawChanged_Raw(this, &FGLTFLoaderModule::SetImportRYaw) 145 | .OnRollChanged_Raw(this, &FGLTFLoaderModule::SetImportRRoll) 146 | ] 147 | ] 148 | + SVerticalBox::Slot() 149 | .Padding(1.0f) 150 | .HAlign(HAlign_Fill) 151 | .VAlign(VAlign_Center) 152 | [ 153 | SNew(SHorizontalBox) 154 | + SHorizontalBox::Slot() 155 | .Padding(1.0f) 156 | .HAlign(HAlign_Center) 157 | .VAlign(VAlign_Center) 158 | [ 159 | SNew(STextBlock) 160 | .Text(LOCTEXT("ImportScale", "Import Scale")) 161 | ] 162 | + SHorizontalBox::Slot() 163 | .Padding(1.0f) 164 | .HAlign(HAlign_Center) 165 | .VAlign(VAlign_Center) 166 | [ 167 | SNew(SNumericEntryBox) 168 | .Value_Raw(this, &FGLTFLoaderModule::GetImportScale) 169 | .OnValueChanged_Raw(this, &FGLTFLoaderModule::SetImportScale) 170 | ] 171 | ] 172 | + SVerticalBox::Slot() 173 | .Padding(1.0f) 174 | .HAlign(HAlign_Fill) 175 | .VAlign(VAlign_Center) 176 | [ 177 | SNew(SHorizontalBox) 178 | + SHorizontalBox::Slot() 179 | .Padding(1.0f) 180 | .HAlign(HAlign_Center) 181 | .VAlign(VAlign_Center) 182 | [ 183 | SNew(STextBlock) 184 | .Text(LOCTEXT("CorrectUp", "Correct Y up to Z up")) 185 | ] 186 | + SHorizontalBox::Slot() 187 | .Padding(1.0f) 188 | .HAlign(HAlign_Center) 189 | .VAlign(VAlign_Center) 190 | [ 191 | SNew(SCheckBox) 192 | .IsChecked_Raw(this, &FGLTFLoaderModule::GetCorrectUp) 193 | .OnCheckStateChanged_Raw(this, &FGLTFLoaderModule::SetCorrectUp) 194 | ] 195 | ] 196 | + SVerticalBox::Slot() 197 | .Padding(1.0f) 198 | .HAlign(HAlign_Center) 199 | [ 200 | SNew(SBox) 201 | .HAlign(HAlign_Center) 202 | .VAlign(VAlign_Center) 203 | [ 204 | SNew(SButton) 205 | .OnClicked_Raw(this, &FGLTFLoaderModule::OpenImportWindowDelegateFunc) 206 | .Content() 207 | [ 208 | SNew(STextBlock) 209 | .Text(LOCTEXT("ImportWindow", "Import File")) 210 | ] 211 | ] 212 | ] 213 | ]; 214 | 215 | } 216 | 217 | void FGLTFLoaderModule::OpenImportWindow() 218 | { 219 | TArray Filenames; 220 | 221 | if (FDesktopPlatformModule::Get()->OpenFileDialog(nullptr, 222 | TEXT("Choose a GLTF file to import"), 223 | TEXT(""), 224 | TEXT(""), 225 | TEXT("GL Transmission Format files (*.gltf)|*.gltf"), 226 | EFileDialogFlags::None, 227 | Filenames)) 228 | { 229 | for (FString File : Filenames) 230 | { 231 | UE_LOG(LogTemp, Log, TEXT("File: %s"), *File); 232 | } 233 | 234 | FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); 235 | 236 | AssetToolsModule.Get().ImportAssets(Filenames, FString("/Game/Content")); 237 | } 238 | } 239 | 240 | void FGLTFLoaderModule::PluginButtonClicked() 241 | { 242 | FGlobalTabmanager::Get()->InvokeTab(GLTFLoaderTabName); 243 | } 244 | 245 | void FGLTFLoaderModule::AddMenuExtension(FMenuBuilder& Builder) 246 | { 247 | Builder.AddMenuEntry(FGLTFLoaderCommands::Get().OpenPluginWindow); 248 | } 249 | 250 | void FGLTFLoaderModule::AddToolbarExtension(FToolBarBuilder& Builder) 251 | { 252 | Builder.AddToolBarButton(FGLTFLoaderCommands::Get().OpenPluginWindow); 253 | } 254 | 255 | // Delegate setters 256 | void FGLTFLoaderModule::SetImportTX(float Value) { ImportOptions.ImportTranslation.X = Value; } 257 | void FGLTFLoaderModule::SetImportTY(float Value) { ImportOptions.ImportTranslation.Y = Value; } 258 | void FGLTFLoaderModule::SetImportTZ(float Value) { ImportOptions.ImportTranslation.Z = Value; } 259 | void FGLTFLoaderModule::SetImportRPitch(float Value) { ImportOptions.ImportRotation.Pitch = Value; } 260 | void FGLTFLoaderModule::SetImportRYaw(float Value) { ImportOptions.ImportRotation.Yaw = Value; } 261 | void FGLTFLoaderModule::SetImportRRoll(float Value) { ImportOptions.ImportRotation.Roll = Value; } 262 | void FGLTFLoaderModule::SetImportScale(float Value) { ImportOptions.ImportUniformScale = Value; } 263 | void FGLTFLoaderModule::SetCorrectUp(ECheckBoxState Value) { ImportOptions.bCorrectUpDirection = (Value == ECheckBoxState::Checked); } 264 | 265 | // Delegate getters 266 | TOptional FGLTFLoaderModule::GetImportTX() const { return ImportOptions.ImportTranslation.X; } 267 | TOptional FGLTFLoaderModule::GetImportTY() const { return ImportOptions.ImportTranslation.Y; } 268 | TOptional FGLTFLoaderModule::GetImportTZ() const { return ImportOptions.ImportTranslation.Z; } 269 | TOptional FGLTFLoaderModule::GetImportRPitch() const { return ImportOptions.ImportRotation.Pitch; } 270 | TOptional FGLTFLoaderModule::GetImportRYaw() const { return ImportOptions.ImportRotation.Yaw; } 271 | TOptional FGLTFLoaderModule::GetImportRRoll() const { return ImportOptions.ImportRotation.Roll; } 272 | TOptional FGLTFLoaderModule::GetImportScale() const { return ImportOptions.ImportUniformScale; } 273 | ECheckBoxState FGLTFLoaderModule::GetCorrectUp() const { return ImportOptions.bCorrectUpDirection ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } 274 | 275 | #undef LOCTEXT_NAMESPACE 276 | 277 | IMPLEMENT_MODULE(FGLTFLoaderModule, GLTFLoader) -------------------------------------------------------------------------------- /Source/GLTFLoader/Private/picojson.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2009-2010 Cybozu Labs, Inc. 3 | * Copyright 2011-2014 Kazuho Oku 4 | * All rights reserved. 5 | * 6 | * Redistribution and use in source and binary forms, with or without 7 | * modification, are permitted provided that the following conditions are met: 8 | * 9 | * 1. Redistributions of source code must retain the above copyright notice, 10 | * this list of conditions and the following disclaimer. 11 | * 12 | * 2. Redistributions in binary form must reproduce the above copyright notice, 13 | * this list of conditions and the following disclaimer in the documentation 14 | * and/or other materials provided with the distribution. 15 | * 16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 | * POSSIBILITY OF SUCH DAMAGE. 27 | */ 28 | #ifndef picojson_h 29 | #define picojson_h 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | 44 | // for isnan/isinf 45 | #if __cplusplus>=201103L 46 | # include 47 | #else 48 | extern "C" { 49 | # ifdef _MSC_VER 50 | # include 51 | # elif defined(__INTEL_COMPILER) 52 | # include 53 | # else 54 | # include 55 | # endif 56 | } 57 | #endif 58 | 59 | #ifndef PICOJSON_USE_RVALUE_REFERENCE 60 | # if (defined(__cpp_rvalue_references) && __cpp_rvalue_references >= 200610) || (defined(_MSC_VER) && _MSC_VER >= 1600) 61 | # define PICOJSON_USE_RVALUE_REFERENCE 1 62 | # else 63 | # define PICOJSON_USE_RVALUE_REFERENCE 0 64 | # endif 65 | #endif//PICOJSON_USE_RVALUE_REFERENCE 66 | 67 | 68 | // experimental support for int64_t (see README.mkdn for detail) 69 | #ifdef PICOJSON_USE_INT64 70 | # define __STDC_FORMAT_MACROS 71 | # include 72 | # include 73 | #endif 74 | 75 | // to disable the use of localeconv(3), set PICOJSON_USE_LOCALE to 0 76 | #ifndef PICOJSON_USE_LOCALE 77 | # define PICOJSON_USE_LOCALE 1 78 | #endif 79 | #if PICOJSON_USE_LOCALE 80 | extern "C" { 81 | # include 82 | } 83 | #endif 84 | 85 | #ifndef PICOJSON_ASSERT 86 | # define PICOJSON_ASSERT(e) do { if (! (e)) throw std::runtime_error(#e); } while (0) 87 | #endif 88 | 89 | #ifdef _MSC_VER 90 | #define SNPRINTF _snprintf_s 91 | #pragma warning(push) 92 | #pragma warning(disable : 4244) // conversion from int to char 93 | #pragma warning(disable : 4127) // conditional expression is constant 94 | #pragma warning(disable : 4702) // unreachable code 95 | #else 96 | #define SNPRINTF snprintf 97 | #endif 98 | 99 | namespace picojson { 100 | 101 | enum { 102 | null_type, 103 | boolean_type, 104 | number_type, 105 | string_type, 106 | array_type, 107 | object_type 108 | #ifdef PICOJSON_USE_INT64 109 | , int64_type 110 | #endif 111 | }; 112 | 113 | enum { 114 | INDENT_WIDTH = 2 115 | }; 116 | 117 | struct null {}; 118 | 119 | class value { 120 | public: 121 | typedef std::vector array; 122 | typedef std::map object; 123 | union _storage { 124 | bool boolean_; 125 | double number_; 126 | #ifdef PICOJSON_USE_INT64 127 | int64_t int64_; 128 | #endif 129 | std::string* string_; 130 | array* array_; 131 | object* object_; 132 | }; 133 | protected: 134 | int type_; 135 | _storage u_; 136 | public: 137 | value(); 138 | value(int type, bool); 139 | explicit value(bool b); 140 | #ifdef PICOJSON_USE_INT64 141 | explicit value(int64_t i); 142 | #endif 143 | explicit value(double n); 144 | explicit value(const std::string& s); 145 | explicit value(const array& a); 146 | explicit value(const object& o); 147 | explicit value(const char* s); 148 | value(const char* s, size_t len); 149 | ~value(); 150 | value(const value& x); 151 | value& operator=(const value& x); 152 | #if PICOJSON_USE_RVALUE_REFERENCE 153 | value(value&& x)throw(); 154 | value& operator=(value&& x)throw(); 155 | #endif 156 | void swap(value& x)throw(); 157 | template bool is() const; 158 | template const T& get() const; 159 | template T& get(); 160 | bool evaluate_as_boolean() const; 161 | const value& get(size_t idx) const; 162 | const value& get(const std::string& key) const; 163 | value& get(size_t idx); 164 | value& get(const std::string& key); 165 | 166 | bool contains(size_t idx) const; 167 | bool contains(const std::string& key) const; 168 | std::string to_str() const; 169 | template void serialize(Iter os, bool prettify = false) const; 170 | std::string serialize(bool prettify = false) const; 171 | private: 172 | template value(const T*); // intentionally defined to block implicit conversion of pointer to bool 173 | template static void _indent(Iter os, int indent); 174 | template void _serialize(Iter os, int indent) const; 175 | std::string _serialize(int indent) const; 176 | }; 177 | 178 | typedef value::array array; 179 | typedef value::object object; 180 | 181 | inline value::value() : type_(null_type) {} 182 | 183 | inline value::value(int type, bool) : type_(type) { 184 | switch (type) { 185 | #define INIT(p, v) case p##type: u_.p = v; break 186 | INIT(boolean_, false); 187 | INIT(number_, 0.0); 188 | #ifdef PICOJSON_USE_INT64 189 | INIT(int64_, 0); 190 | #endif 191 | INIT(string_, new std::string()); 192 | INIT(array_, new array()); 193 | INIT(object_, new object()); 194 | #undef INIT 195 | default: break; 196 | } 197 | } 198 | 199 | inline value::value(bool b) : type_(boolean_type) { 200 | u_.boolean_ = b; 201 | } 202 | 203 | #ifdef PICOJSON_USE_INT64 204 | inline value::value(int64_t i) : type_(int64_type) { 205 | u_.int64_ = i; 206 | } 207 | #endif 208 | 209 | inline value::value(double n) : type_(number_type) { 210 | if ( 211 | #ifdef _MSC_VER 212 | ! _finite(n) 213 | #elif __cplusplus>=201103L || !(defined(isnan) && defined(isinf)) 214 | std::isnan(n) || std::isinf(n) 215 | #else 216 | isnan(n) || isinf(n) 217 | #endif 218 | ) { 219 | throw std::overflow_error(""); 220 | } 221 | u_.number_ = n; 222 | } 223 | 224 | inline value::value(const std::string& s) : type_(string_type) { 225 | u_.string_ = new std::string(s); 226 | } 227 | 228 | inline value::value(const array& a) : type_(array_type) { 229 | u_.array_ = new array(a); 230 | } 231 | 232 | inline value::value(const object& o) : type_(object_type) { 233 | u_.object_ = new object(o); 234 | } 235 | 236 | inline value::value(const char* s) : type_(string_type) { 237 | u_.string_ = new std::string(s); 238 | } 239 | 240 | inline value::value(const char* s, size_t len) : type_(string_type) { 241 | u_.string_ = new std::string(s, len); 242 | } 243 | 244 | inline value::~value() { 245 | switch (type_) { 246 | #define DEINIT(p) case p##type: delete u_.p; break 247 | DEINIT(string_); 248 | DEINIT(array_); 249 | DEINIT(object_); 250 | #undef DEINIT 251 | default: break; 252 | } 253 | } 254 | 255 | inline value::value(const value& x) : type_(x.type_) { 256 | switch (type_) { 257 | #define INIT(p, v) case p##type: u_.p = v; break 258 | INIT(string_, new std::string(*x.u_.string_)); 259 | INIT(array_, new array(*x.u_.array_)); 260 | INIT(object_, new object(*x.u_.object_)); 261 | #undef INIT 262 | default: 263 | u_ = x.u_; 264 | break; 265 | } 266 | } 267 | 268 | inline value& value::operator=(const value& x) { 269 | if (this != &x) { 270 | value t(x); 271 | swap(t); 272 | } 273 | return *this; 274 | } 275 | 276 | #if PICOJSON_USE_RVALUE_REFERENCE 277 | inline value::value(value&& x)throw() : type_(null_type) { 278 | swap(x); 279 | } 280 | inline value& value::operator=(value&& x)throw() { 281 | swap(x); 282 | return *this; 283 | } 284 | #endif 285 | inline void value::swap(value& x)throw() { 286 | std::swap(type_, x.type_); 287 | std::swap(u_, x.u_); 288 | } 289 | 290 | #define IS(ctype, jtype) \ 291 | template <> inline bool value::is() const { \ 292 | return type_ == jtype##_type; \ 293 | } 294 | IS(null, null) 295 | IS(bool, boolean) 296 | #ifdef PICOJSON_USE_INT64 297 | IS(int64_t, int64) 298 | #endif 299 | IS(std::string, string) 300 | IS(array, array) 301 | IS(object, object) 302 | #undef IS 303 | template <> inline bool value::is() const { 304 | return type_ == number_type 305 | #ifdef PICOJSON_USE_INT64 306 | || type_ == int64_type 307 | #endif 308 | ; 309 | } 310 | 311 | #define GET(ctype, var) \ 312 | template <> inline const ctype& value::get() const { \ 313 | PICOJSON_ASSERT("type mismatch! call is() before get()" \ 314 | && is()); \ 315 | return var; \ 316 | } \ 317 | template <> inline ctype& value::get() { \ 318 | PICOJSON_ASSERT("type mismatch! call is() before get()" \ 319 | && is()); \ 320 | return var; \ 321 | } 322 | GET(bool, u_.boolean_) 323 | GET(std::string, *u_.string_) 324 | GET(array, *u_.array_) 325 | GET(object, *u_.object_) 326 | #ifdef PICOJSON_USE_INT64 327 | GET(double, (type_ == int64_type && (const_cast(this)->type_ = number_type, const_cast(this)->u_.number_ = u_.int64_), u_.number_)) 328 | GET(int64_t, u_.int64_) 329 | #else 330 | GET(double, u_.number_) 331 | #endif 332 | #undef GET 333 | 334 | inline bool value::evaluate_as_boolean() const { 335 | switch (type_) { 336 | case null_type: 337 | return false; 338 | case boolean_type: 339 | return u_.boolean_; 340 | case number_type: 341 | return u_.number_ != 0; 342 | #ifdef PICOJSON_USE_INT64 343 | case int64_type: 344 | return u_.int64_ != 0; 345 | #endif 346 | case string_type: 347 | return ! u_.string_->empty(); 348 | default: 349 | return true; 350 | } 351 | } 352 | 353 | inline const value& value::get(size_t idx) const { 354 | static value s_null; 355 | PICOJSON_ASSERT(is()); 356 | return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; 357 | } 358 | 359 | inline value& value::get(size_t idx) { 360 | static value s_null; 361 | PICOJSON_ASSERT(is()); 362 | return idx < u_.array_->size() ? (*u_.array_)[idx] : s_null; 363 | } 364 | 365 | inline const value& value::get(const std::string& key) const { 366 | static value s_null; 367 | PICOJSON_ASSERT(is()); 368 | object::const_iterator i = u_.object_->find(key); 369 | return i != u_.object_->end() ? i->second : s_null; 370 | } 371 | 372 | inline value& value::get(const std::string& key) { 373 | static value s_null; 374 | PICOJSON_ASSERT(is()); 375 | object::iterator i = u_.object_->find(key); 376 | return i != u_.object_->end() ? i->second : s_null; 377 | } 378 | 379 | inline bool value::contains(size_t idx) const { 380 | PICOJSON_ASSERT(is()); 381 | return idx < u_.array_->size(); 382 | } 383 | 384 | inline bool value::contains(const std::string& key) const { 385 | PICOJSON_ASSERT(is()); 386 | object::const_iterator i = u_.object_->find(key); 387 | return i != u_.object_->end(); 388 | } 389 | 390 | inline std::string value::to_str() const { 391 | switch (type_) { 392 | case null_type: return "null"; 393 | case boolean_type: return u_.boolean_ ? "true" : "false"; 394 | #ifdef PICOJSON_USE_INT64 395 | case int64_type: { 396 | char buf[sizeof("-9223372036854775808")]; 397 | SNPRINTF(buf, sizeof(buf), "%" PRId64, u_.int64_); 398 | return buf; 399 | } 400 | #endif 401 | case number_type: { 402 | char buf[256]; 403 | double tmp; 404 | SNPRINTF(buf, sizeof(buf), fabs(u_.number_) < (1ULL << 53) && modf(u_.number_, &tmp) == 0 ? "%.f" : "%.17g", u_.number_); 405 | #if PICOJSON_USE_LOCALE 406 | char *decimal_point = localeconv()->decimal_point; 407 | if (strcmp(decimal_point, ".") != 0) { 408 | size_t decimal_point_len = strlen(decimal_point); 409 | for (char *p = buf; *p != '\0'; ++p) { 410 | if (strncmp(p, decimal_point, decimal_point_len) == 0) { 411 | return std::string(buf, p) + "." + (p + decimal_point_len); 412 | } 413 | } 414 | } 415 | #endif 416 | return buf; 417 | } 418 | case string_type: return *u_.string_; 419 | case array_type: return "array"; 420 | case object_type: return "object"; 421 | default: PICOJSON_ASSERT(0); 422 | #ifdef _MSC_VER 423 | __assume(0); 424 | #endif 425 | } 426 | return std::string(); 427 | } 428 | 429 | template void copy(const std::string& s, Iter oi) { 430 | std::copy(s.begin(), s.end(), oi); 431 | } 432 | 433 | template void serialize_str(const std::string& s, Iter oi) { 434 | *oi++ = '"'; 435 | for (std::string::const_iterator i = s.begin(); i != s.end(); ++i) { 436 | switch (*i) { 437 | #define MAP(val, sym) case val: copy(sym, oi); break 438 | MAP('"', "\\\""); 439 | MAP('\\', "\\\\"); 440 | MAP('/', "\\/"); 441 | MAP('\b', "\\b"); 442 | MAP('\f', "\\f"); 443 | MAP('\n', "\\n"); 444 | MAP('\r', "\\r"); 445 | MAP('\t', "\\t"); 446 | #undef MAP 447 | default: 448 | if (static_cast(*i) < 0x20 || *i == 0x7f) { 449 | char buf[7]; 450 | SNPRINTF(buf, sizeof(buf), "\\u%04x", *i & 0xff); 451 | copy(buf, buf + 6, oi); 452 | } else { 453 | *oi++ = *i; 454 | } 455 | break; 456 | } 457 | } 458 | *oi++ = '"'; 459 | } 460 | 461 | template void value::serialize(Iter oi, bool prettify) const { 462 | return _serialize(oi, prettify ? 0 : -1); 463 | } 464 | 465 | inline std::string value::serialize(bool prettify) const { 466 | return _serialize(prettify ? 0 : -1); 467 | } 468 | 469 | template void value::_indent(Iter oi, int indent) { 470 | *oi++ = '\n'; 471 | for (int i = 0; i < indent * INDENT_WIDTH; ++i) { 472 | *oi++ = ' '; 473 | } 474 | } 475 | 476 | template void value::_serialize(Iter oi, int indent) const { 477 | switch (type_) { 478 | case string_type: 479 | serialize_str(*u_.string_, oi); 480 | break; 481 | case array_type: { 482 | *oi++ = '['; 483 | if (indent != -1) { 484 | ++indent; 485 | } 486 | for (array::const_iterator i = u_.array_->begin(); 487 | i != u_.array_->end(); 488 | ++i) { 489 | if (i != u_.array_->begin()) { 490 | *oi++ = ','; 491 | } 492 | if (indent != -1) { 493 | _indent(oi, indent); 494 | } 495 | i->_serialize(oi, indent); 496 | } 497 | if (indent != -1) { 498 | --indent; 499 | if (! u_.array_->empty()) { 500 | _indent(oi, indent); 501 | } 502 | } 503 | *oi++ = ']'; 504 | break; 505 | } 506 | case object_type: { 507 | *oi++ = '{'; 508 | if (indent != -1) { 509 | ++indent; 510 | } 511 | for (object::const_iterator i = u_.object_->begin(); 512 | i != u_.object_->end(); 513 | ++i) { 514 | if (i != u_.object_->begin()) { 515 | *oi++ = ','; 516 | } 517 | if (indent != -1) { 518 | _indent(oi, indent); 519 | } 520 | serialize_str(i->first, oi); 521 | *oi++ = ':'; 522 | if (indent != -1) { 523 | *oi++ = ' '; 524 | } 525 | i->second._serialize(oi, indent); 526 | } 527 | if (indent != -1) { 528 | --indent; 529 | if (! u_.object_->empty()) { 530 | _indent(oi, indent); 531 | } 532 | } 533 | *oi++ = '}'; 534 | break; 535 | } 536 | default: 537 | copy(to_str(), oi); 538 | break; 539 | } 540 | if (indent == 0) { 541 | *oi++ = '\n'; 542 | } 543 | } 544 | 545 | inline std::string value::_serialize(int indent) const { 546 | std::string s; 547 | _serialize(std::back_inserter(s), indent); 548 | return s; 549 | } 550 | 551 | template class input { 552 | protected: 553 | Iter cur_, end_; 554 | int last_ch_; 555 | bool ungot_; 556 | int line_; 557 | public: 558 | input(const Iter& first, const Iter& last) : cur_(first), end_(last), last_ch_(-1), ungot_(false), line_(1) {} 559 | int getc() { 560 | if (ungot_) { 561 | ungot_ = false; 562 | return last_ch_; 563 | } 564 | if (cur_ == end_) { 565 | last_ch_ = -1; 566 | return -1; 567 | } 568 | if (last_ch_ == '\n') { 569 | line_++; 570 | } 571 | last_ch_ = *cur_ & 0xff; 572 | ++cur_; 573 | return last_ch_; 574 | } 575 | void ungetc() { 576 | if (last_ch_ != -1) { 577 | PICOJSON_ASSERT(! ungot_); 578 | ungot_ = true; 579 | } 580 | } 581 | Iter cur() const { return cur_; } 582 | int line() const { return line_; } 583 | void skip_ws() { 584 | while (1) { 585 | int ch = getc(); 586 | if (! (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r')) { 587 | ungetc(); 588 | break; 589 | } 590 | } 591 | } 592 | bool expect(int expect) { 593 | skip_ws(); 594 | if (getc() != expect) { 595 | ungetc(); 596 | return false; 597 | } 598 | return true; 599 | } 600 | bool match(const std::string& pattern) { 601 | for (std::string::const_iterator pi(pattern.begin()); 602 | pi != pattern.end(); 603 | ++pi) { 604 | if (getc() != *pi) { 605 | ungetc(); 606 | return false; 607 | } 608 | } 609 | return true; 610 | } 611 | }; 612 | 613 | template inline int _parse_quadhex(input &in) { 614 | int uni_ch = 0, hex; 615 | for (int i = 0; i < 4; i++) { 616 | if ((hex = in.getc()) == -1) { 617 | return -1; 618 | } 619 | if ('0' <= hex && hex <= '9') { 620 | hex -= '0'; 621 | } else if ('A' <= hex && hex <= 'F') { 622 | hex -= 'A' - 0xa; 623 | } else if ('a' <= hex && hex <= 'f') { 624 | hex -= 'a' - 0xa; 625 | } else { 626 | in.ungetc(); 627 | return -1; 628 | } 629 | uni_ch = uni_ch * 16 + hex; 630 | } 631 | return uni_ch; 632 | } 633 | 634 | template inline bool _parse_codepoint(String& out, input& in) { 635 | int uni_ch; 636 | if ((uni_ch = _parse_quadhex(in)) == -1) { 637 | return false; 638 | } 639 | if (0xd800 <= uni_ch && uni_ch <= 0xdfff) { 640 | if (0xdc00 <= uni_ch) { 641 | // a second 16-bit of a surrogate pair appeared 642 | return false; 643 | } 644 | // first 16-bit of surrogate pair, get the next one 645 | if (in.getc() != '\\' || in.getc() != 'u') { 646 | in.ungetc(); 647 | return false; 648 | } 649 | int second = _parse_quadhex(in); 650 | if (! (0xdc00 <= second && second <= 0xdfff)) { 651 | return false; 652 | } 653 | uni_ch = ((uni_ch - 0xd800) << 10) | ((second - 0xdc00) & 0x3ff); 654 | uni_ch += 0x10000; 655 | } 656 | if (uni_ch < 0x80) { 657 | out.push_back(uni_ch); 658 | } else { 659 | if (uni_ch < 0x800) { 660 | out.push_back(0xc0 | (uni_ch >> 6)); 661 | } else { 662 | if (uni_ch < 0x10000) { 663 | out.push_back(0xe0 | (uni_ch >> 12)); 664 | } else { 665 | out.push_back(0xf0 | (uni_ch >> 18)); 666 | out.push_back(0x80 | ((uni_ch >> 12) & 0x3f)); 667 | } 668 | out.push_back(0x80 | ((uni_ch >> 6) & 0x3f)); 669 | } 670 | out.push_back(0x80 | (uni_ch & 0x3f)); 671 | } 672 | return true; 673 | } 674 | 675 | template inline bool _parse_string(String& out, input& in) { 676 | while (1) { 677 | int ch = in.getc(); 678 | if (ch < ' ') { 679 | in.ungetc(); 680 | return false; 681 | } else if (ch == '"') { 682 | return true; 683 | } else if (ch == '\\') { 684 | if ((ch = in.getc()) == -1) { 685 | return false; 686 | } 687 | switch (ch) { 688 | #define MAP(sym, val) case sym: out.push_back(val); break 689 | MAP('"', '\"'); 690 | MAP('\\', '\\'); 691 | MAP('/', '/'); 692 | MAP('b', '\b'); 693 | MAP('f', '\f'); 694 | MAP('n', '\n'); 695 | MAP('r', '\r'); 696 | MAP('t', '\t'); 697 | #undef MAP 698 | case 'u': 699 | if (! _parse_codepoint(out, in)) { 700 | return false; 701 | } 702 | break; 703 | default: 704 | return false; 705 | } 706 | } else { 707 | out.push_back(ch); 708 | } 709 | } 710 | return false; 711 | } 712 | 713 | template inline bool _parse_array(Context& ctx, input& in) { 714 | if (! ctx.parse_array_start()) { 715 | return false; 716 | } 717 | size_t idx = 0; 718 | if (in.expect(']')) { 719 | return ctx.parse_array_stop(idx); 720 | } 721 | do { 722 | if (! ctx.parse_array_item(in, idx)) { 723 | return false; 724 | } 725 | idx++; 726 | } while (in.expect(',')); 727 | return in.expect(']') && ctx.parse_array_stop(idx); 728 | } 729 | 730 | template inline bool _parse_object(Context& ctx, input& in) { 731 | if (! ctx.parse_object_start()) { 732 | return false; 733 | } 734 | if (in.expect('}')) { 735 | return true; 736 | } 737 | do { 738 | std::string key; 739 | if (! in.expect('"') 740 | || ! _parse_string(key, in) 741 | || ! in.expect(':')) { 742 | return false; 743 | } 744 | if (! ctx.parse_object_item(in, key)) { 745 | return false; 746 | } 747 | } while (in.expect(',')); 748 | return in.expect('}'); 749 | } 750 | 751 | template inline std::string _parse_number(input& in) { 752 | std::string num_str; 753 | while (1) { 754 | int ch = in.getc(); 755 | if (('0' <= ch && ch <= '9') || ch == '+' || ch == '-' 756 | || ch == 'e' || ch == 'E') { 757 | num_str.push_back(ch); 758 | } else if (ch == '.') { 759 | #if PICOJSON_USE_LOCALE 760 | num_str += localeconv()->decimal_point; 761 | #else 762 | num_str.push_back('.'); 763 | #endif 764 | } else { 765 | in.ungetc(); 766 | break; 767 | } 768 | } 769 | return num_str; 770 | } 771 | 772 | template inline bool _parse(Context& ctx, input& in) { 773 | in.skip_ws(); 774 | int ch = in.getc(); 775 | switch (ch) { 776 | #define IS(ch, text, op) case ch: \ 777 | if (in.match(text) && op) { \ 778 | return true; \ 779 | } else { \ 780 | return false; \ 781 | } 782 | IS('n', "ull", ctx.set_null()); 783 | IS('f', "alse", ctx.set_bool(false)); 784 | IS('t', "rue", ctx.set_bool(true)); 785 | #undef IS 786 | case '"': 787 | return ctx.parse_string(in); 788 | case '[': 789 | return _parse_array(ctx, in); 790 | case '{': 791 | return _parse_object(ctx, in); 792 | default: 793 | if (('0' <= ch && ch <= '9') || ch == '-') { 794 | double f; 795 | char *endp; 796 | in.ungetc(); 797 | std::string num_str = _parse_number(in); 798 | if (num_str.empty()) { 799 | return false; 800 | } 801 | #ifdef PICOJSON_USE_INT64 802 | { 803 | errno = 0; 804 | intmax_t ival = strtoimax(num_str.c_str(), &endp, 10); 805 | if (errno == 0 806 | && std::numeric_limits::min() <= ival 807 | && ival <= std::numeric_limits::max() 808 | && endp == num_str.c_str() + num_str.size()) { 809 | ctx.set_int64(ival); 810 | return true; 811 | } 812 | } 813 | #endif 814 | f = strtod(num_str.c_str(), &endp); 815 | if (endp == num_str.c_str() + num_str.size()) { 816 | ctx.set_number(f); 817 | return true; 818 | } 819 | return false; 820 | } 821 | break; 822 | } 823 | in.ungetc(); 824 | return false; 825 | } 826 | 827 | class deny_parse_context { 828 | public: 829 | bool set_null() { return false; } 830 | bool set_bool(bool) { return false; } 831 | #ifdef PICOJSON_USE_INT64 832 | bool set_int64(int64_t) { return false; } 833 | #endif 834 | bool set_number(double) { return false; } 835 | template bool parse_string(input&) { return false; } 836 | bool parse_array_start() { return false; } 837 | template bool parse_array_item(input&, size_t) { 838 | return false; 839 | } 840 | bool parse_array_stop(size_t) { return false; } 841 | bool parse_object_start() { return false; } 842 | template bool parse_object_item(input&, const std::string&) { 843 | return false; 844 | } 845 | }; 846 | 847 | class default_parse_context { 848 | protected: 849 | value* out_; 850 | public: 851 | default_parse_context(value* out) : out_(out) {} 852 | bool set_null() { 853 | *out_ = value(); 854 | return true; 855 | } 856 | bool set_bool(bool b) { 857 | *out_ = value(b); 858 | return true; 859 | } 860 | #ifdef PICOJSON_USE_INT64 861 | bool set_int64(int64_t i) { 862 | *out_ = value(i); 863 | return true; 864 | } 865 | #endif 866 | bool set_number(double f) { 867 | *out_ = value(f); 868 | return true; 869 | } 870 | template bool parse_string(input& in) { 871 | *out_ = value(string_type, false); 872 | return _parse_string(out_->get(), in); 873 | } 874 | bool parse_array_start() { 875 | *out_ = value(array_type, false); 876 | return true; 877 | } 878 | template bool parse_array_item(input& in, size_t) { 879 | array& a = out_->get(); 880 | a.push_back(value()); 881 | default_parse_context ctx(&a.back()); 882 | return _parse(ctx, in); 883 | } 884 | bool parse_array_stop(size_t) { return true; } 885 | bool parse_object_start() { 886 | *out_ = value(object_type, false); 887 | return true; 888 | } 889 | template bool parse_object_item(input& in, const std::string& key) { 890 | object& o = out_->get(); 891 | default_parse_context ctx(&o[key]); 892 | return _parse(ctx, in); 893 | } 894 | private: 895 | default_parse_context(const default_parse_context&); 896 | default_parse_context& operator=(const default_parse_context&); 897 | }; 898 | 899 | class null_parse_context { 900 | public: 901 | struct dummy_str { 902 | void push_back(int) {} 903 | }; 904 | public: 905 | null_parse_context() {} 906 | bool set_null() { return true; } 907 | bool set_bool(bool) { return true; } 908 | #ifdef PICOJSON_USE_INT64 909 | bool set_int64(int64_t) { return true; } 910 | #endif 911 | bool set_number(double) { return true; } 912 | template bool parse_string(input& in) { 913 | dummy_str s; 914 | return _parse_string(s, in); 915 | } 916 | bool parse_array_start() { return true; } 917 | template bool parse_array_item(input& in, size_t) { 918 | return _parse(*this, in); 919 | } 920 | bool parse_array_stop(size_t) { return true; } 921 | bool parse_object_start() { return true; } 922 | template bool parse_object_item(input& in, const std::string&) { 923 | return _parse(*this, in); 924 | } 925 | private: 926 | null_parse_context(const null_parse_context&); 927 | null_parse_context& operator=(const null_parse_context&); 928 | }; 929 | 930 | // obsolete, use the version below 931 | template inline std::string parse(value& out, Iter& pos, const Iter& last) { 932 | std::string err; 933 | pos = parse(out, pos, last, &err); 934 | return err; 935 | } 936 | 937 | template inline Iter _parse(Context& ctx, const Iter& first, const Iter& last, std::string* err) { 938 | input in(first, last); 939 | if (! _parse(ctx, in) && err != NULL) { 940 | char buf[64]; 941 | SNPRINTF(buf, sizeof(buf), "syntax error at line %d near: ", in.line()); 942 | *err = buf; 943 | while (1) { 944 | int ch = in.getc(); 945 | if (ch == -1 || ch == '\n') { 946 | break; 947 | } else if (ch >= ' ') { 948 | err->push_back(ch); 949 | } 950 | } 951 | } 952 | return in.cur(); 953 | } 954 | 955 | template inline Iter parse(value& out, const Iter& first, const Iter& last, std::string* err) { 956 | default_parse_context ctx(&out); 957 | return _parse(ctx, first, last, err); 958 | } 959 | 960 | inline std::string parse(value& out, const std::string& s) { 961 | std::string err; 962 | parse(out, s.begin(), s.end(), &err); 963 | return err; 964 | } 965 | 966 | inline std::string parse(value& out, std::istream& is) { 967 | std::string err; 968 | parse(out, std::istreambuf_iterator(is.rdbuf()), 969 | std::istreambuf_iterator(), &err); 970 | return err; 971 | } 972 | 973 | template struct last_error_t { 974 | static std::string s; 975 | }; 976 | template std::string last_error_t::s; 977 | 978 | inline void set_last_error(const std::string& s) { 979 | last_error_t::s = s; 980 | } 981 | 982 | inline const std::string& get_last_error() { 983 | return last_error_t::s; 984 | } 985 | 986 | inline bool operator==(const value& x, const value& y) { 987 | if (x.is()) 988 | return y.is(); 989 | #define PICOJSON_CMP(type) \ 990 | if (x.is()) \ 991 | return y.is() && x.get() == y.get() 992 | PICOJSON_CMP(bool); 993 | PICOJSON_CMP(double); 994 | PICOJSON_CMP(std::string); 995 | PICOJSON_CMP(array); 996 | PICOJSON_CMP(object); 997 | #undef PICOJSON_CMP 998 | PICOJSON_ASSERT(0); 999 | #ifdef _MSC_VER 1000 | __assume(0); 1001 | #endif 1002 | return false; 1003 | } 1004 | 1005 | inline bool operator!=(const value& x, const value& y) { 1006 | return ! (x == y); 1007 | } 1008 | } 1009 | 1010 | #if !PICOJSON_USE_RVALUE_REFERENCE 1011 | namespace std { 1012 | template<> inline void swap(picojson::value& x, picojson::value& y) 1013 | { 1014 | x.swap(y); 1015 | } 1016 | } 1017 | #endif 1018 | 1019 | inline std::istream& operator>>(std::istream& is, picojson::value& x) 1020 | { 1021 | picojson::set_last_error(std::string()); 1022 | std::string err = picojson::parse(x, is); 1023 | if (! err.empty()) { 1024 | picojson::set_last_error(err); 1025 | is.setstate(std::ios::failbit); 1026 | } 1027 | return is; 1028 | } 1029 | 1030 | inline std::ostream& operator<<(std::ostream& os, const picojson::value& x) 1031 | { 1032 | x.serialize(std::ostream_iterator(os)); 1033 | return os; 1034 | } 1035 | #ifdef _MSC_VER 1036 | #pragma warning(pop) 1037 | #endif 1038 | 1039 | #endif 1040 | -------------------------------------------------------------------------------- /Source/GLTFLoader/Private/tiny_gltf_loader.h: -------------------------------------------------------------------------------- 1 | // 2 | // Tiny glTF loader. 3 | // 4 | // Copyright (c) 2015, Syoyo Fujita. 5 | // All rights reserved. 6 | // (Licensed under 2-clause BSD liecense) 7 | // 8 | // Redistribution and use in source and binary forms, with or without 9 | // modification, are permitted provided that the following conditions are met: 10 | // 11 | // 1. Redistributions of source code must retain the above copyright notice, 12 | // this 13 | // list of conditions and the following disclaimer. 14 | // 2. Redistributions in binary form must reproduce the above copyright notice, 15 | // this list of conditions and the following disclaimer in the documentation 16 | // and/or other materials provided with the distribution. 17 | // 18 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | // AND 20 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 23 | // FOR 24 | // ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | // DAMAGES 26 | // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 28 | // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | // 32 | // 33 | // Version: 34 | // - v0.9.2 Support parsing `texture` 35 | // - v0.9.1 Support loading glTF asset from memory 36 | // - v0.9.0 Initial 37 | // 38 | // Tiny glTF loader is using following libraries: 39 | // 40 | // - picojson: C++ JSON library. 41 | // - base64: base64 decode/encode library. 42 | // - stb_image: Image loading library. 43 | // 44 | #ifndef TINY_GLTF_LOADER_H 45 | #define TINY_GLTF_LOADER_H 46 | 47 | #include 48 | #include 49 | #include 50 | 51 | namespace tinygltf { 52 | 53 | #define TINYGLTF_MODE_POINTS (0) 54 | #define TINYGLTF_MODE_LINE (1) 55 | #define TINYGLTF_MODE_LINE_LOOP (2) 56 | #define TINYGLTF_MODE_TRIANGLES (4) 57 | #define TINYGLTF_MODE_TRIANGLE_STRIP (5) 58 | #define TINYGLTF_MODE_TRIANGLE_FAN (6) 59 | 60 | #define TINYGLTF_COMPONENT_TYPE_BYTE (5120) 61 | #define TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE (5121) 62 | #define TINYGLTF_COMPONENT_TYPE_SHORT (5122) 63 | #define TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT (5123) 64 | #define TINYGLTF_COMPONENT_TYPE_INT (5124) 65 | #define TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT (5125) 66 | #define TINYGLTF_COMPONENT_TYPE_FLOAT (5126) 67 | #define TINYGLTF_COMPONENT_TYPE_DOUBLE (5127) 68 | 69 | #define TINYGLTF_TYPE_VEC2 (2) 70 | #define TINYGLTF_TYPE_VEC3 (3) 71 | #define TINYGLTF_TYPE_VEC4 (4) 72 | #define TINYGLTF_TYPE_MAT2 (32 + 2) 73 | #define TINYGLTF_TYPE_MAT3 (32 + 3) 74 | #define TINYGLTF_TYPE_MAT4 (32 + 4) 75 | #define TINYGLTF_TYPE_SCALAR (64 + 1) 76 | #define TINYGLTF_TYPE_VECTOR (64 + 4) 77 | #define TINYGLTF_TYPE_MATRIX (64 + 16) 78 | 79 | #define TINYGLTF_IMAGE_FORMAT_JPEG (0) 80 | #define TINYGLTF_IMAGE_FORMAT_PNG (1) 81 | #define TINYGLTF_IMAGE_FORMAT_BMP (2) 82 | #define TINYGLTF_IMAGE_FORMAT_GIF (3) 83 | 84 | #define TINYGLTF_TEXTURE_FORMAT_RGBA (6408) 85 | #define TINYGLTF_TEXTURE_TARGET_TEXTURE2D (3553) 86 | #define TINYGLTF_TEXTURE_TYPE_UNSIGNED_BYTE (5121) 87 | 88 | #define TINYGLTF_TARGET_ARRAY_BUFFER (34962) 89 | #define TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER (34963) 90 | 91 | typedef struct PARAMETER { 92 | std::string stringValue; 93 | std::vector numberArray; 94 | } Parameter; 95 | 96 | typedef std::map ParameterMap; 97 | 98 | typedef struct IMAGE { 99 | std::string name; 100 | int width; 101 | int height; 102 | int component; 103 | std::vector image; 104 | } Image; 105 | 106 | typedef struct TEXTURE { 107 | int format; 108 | int internalFormat; 109 | std::string sampler; // Required 110 | std::string source; // Required 111 | int target; 112 | int type; 113 | std::string name; 114 | } Texture; 115 | 116 | typedef struct MATERIAL { 117 | std::string name; 118 | std::string technique; 119 | ParameterMap values; 120 | } Material; 121 | 122 | typedef struct BUFFERVIEW { 123 | std::string name; 124 | std::string buffer; // Required 125 | size_t byteOffset; // Required 126 | size_t byteLength; // default: 0 127 | int target; 128 | } BufferView; 129 | 130 | typedef struct ACCESSOR { 131 | std::string bufferView; 132 | std::string name; 133 | size_t byteOffset; 134 | size_t byteStride; 135 | int componentType; // One of TINYGLTF_COMPONENT_TYPE_*** 136 | size_t count; 137 | int type; // One of TINYGLTF_TYPE_*** 138 | std::vector minValues; // Optional 139 | std::vector maxValues; // Optional 140 | } Accessor; 141 | 142 | class Camera { 143 | public: 144 | Camera() {} 145 | ~Camera() {} 146 | 147 | std::string name; 148 | bool isOrthographic; // false = perspective. 149 | 150 | // Some common properties. 151 | float aspectRatio; 152 | float yFov; 153 | float zFar; 154 | float zNear; 155 | }; 156 | 157 | typedef struct PRIMITIVE { 158 | std::map attributes; // A dictionary object of 159 | // strings, where each string 160 | // is the ID of the accessor 161 | // containing an attribute. 162 | std::string material; // The ID of the material to apply to this primitive 163 | // when rendering. 164 | std::string indices; // The ID of the accessor that contains the indices. 165 | int mode; // one of TINYGLTF_MODE_*** 166 | } Primitive; 167 | 168 | typedef struct MESH { 169 | std::string name; 170 | std::vector primitives; 171 | } Mesh; 172 | 173 | class Node { 174 | public: 175 | Node() {} 176 | ~Node() {} 177 | 178 | std::string camera; // camera object referenced by this node. 179 | 180 | std::string name; 181 | std::vector children; 182 | std::vector rotation; // length must be 0 or 4 183 | std::vector scale; // length must be 0 or 3 184 | std::vector translation; // length must be 0 or 3 185 | std::vector matrix; // length must be 0 or 16 186 | std::vector meshes; 187 | }; 188 | 189 | typedef struct BUFFER { 190 | std::string name; 191 | std::vector data; 192 | } Buffer; 193 | 194 | typedef struct ASSET { 195 | std::string generator; 196 | std::string version; 197 | std::string profile_api; 198 | std::string profile_version; 199 | bool premultipliedAlpha; 200 | } Asset; 201 | 202 | class Scene { 203 | public: 204 | Scene() {} 205 | ~Scene() {} 206 | 207 | std::map accessors; 208 | std::map buffers; 209 | std::map bufferViews; 210 | std::map materials; 211 | std::map meshes; 212 | std::map nodes; 213 | std::map textures; 214 | std::map images; 215 | std::map > scenes; // list of nodes 216 | 217 | std::string defaultScene; 218 | 219 | Asset asset; 220 | }; 221 | 222 | class TinyGLTFLoader { 223 | public: 224 | TinyGLTFLoader(){}; 225 | ~TinyGLTFLoader(){}; 226 | 227 | /// Loads glTF asset from a file. 228 | /// Returns false and set error string to `err` if there's an error. 229 | bool LoadFromFile(Scene &scene, std::string &err, 230 | const std::string &filename); 231 | 232 | /// Loads glTF asset from string(memory). 233 | /// `length` = strlen(str); 234 | /// Returns false and set error string to `err` if there's an error. 235 | bool LoadFromString(Scene &scene, std::string &err, const char *str, 236 | const unsigned int length, const std::string &baseDir); 237 | }; 238 | 239 | } // namespace tinygltf 240 | 241 | #ifdef TINYGLTF_LOADER_IMPLEMENTATION 242 | #include 243 | #include 244 | #include 245 | 246 | #include "picojson.h" 247 | #include "stb_image.h" 248 | 249 | #ifdef _WIN32 250 | #include 251 | #else 252 | #include 253 | #endif 254 | 255 | using namespace tinygltf; 256 | 257 | namespace { 258 | 259 | bool FileExists(const std::string &abs_filename) { 260 | bool ret; 261 | FILE *fp = fopen(abs_filename.c_str(), "rb"); 262 | if (fp) { 263 | ret = true; 264 | fclose(fp); 265 | } else { 266 | ret = false; 267 | } 268 | 269 | return ret; 270 | } 271 | 272 | std::string ExpandFilePath(const std::string &filepath) { 273 | #ifdef _WIN32 274 | uint32 len = ExpandEnvironmentStringsA(filepath.c_str(), NULL, 0); 275 | char *str = new char[len]; 276 | ExpandEnvironmentStringsA(filepath.c_str(), str, len); 277 | 278 | std::string s(str); 279 | 280 | delete[] str; 281 | 282 | return s; 283 | #else 284 | 285 | #if TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR 286 | // no expansion 287 | std::string s = filepath; 288 | #else 289 | std::string s; 290 | wordexp_t p; 291 | 292 | if (filepath.empty()) { 293 | return ""; 294 | } 295 | 296 | // char** w; 297 | int ret = wordexp(filepath.c_str(), &p, 0); 298 | if (ret) { 299 | // err 300 | s = filepath; 301 | return s; 302 | } 303 | 304 | // Use first element only. 305 | if (p.we_wordv) { 306 | s = std::string(p.we_wordv[0]); 307 | wordfree(&p); 308 | } else { 309 | s = filepath; 310 | } 311 | 312 | #endif 313 | 314 | return s; 315 | #endif 316 | } 317 | 318 | std::string JoinPath(const std::string &path0, const std::string &path1) { 319 | if (path0.empty()) { 320 | return path1; 321 | } else { 322 | // check '/' 323 | char lastChar = *path0.rbegin(); 324 | if (lastChar != '/') { 325 | return path0 + std::string("/") + path1; 326 | } else { 327 | return path0 + path1; 328 | } 329 | } 330 | } 331 | 332 | std::string FindFile(const std::vector &paths, 333 | const std::string &filepath) { 334 | for (size_t i = 0; i < paths.size(); i++) { 335 | std::string absPath = ExpandFilePath(JoinPath(paths[i], filepath)); 336 | if (FileExists(absPath)) { 337 | return absPath; 338 | } 339 | } 340 | 341 | return std::string(); 342 | } 343 | 344 | // std::string GetFilePathExtension(const std::string& FileName) 345 | //{ 346 | // if(FileName.find_last_of(".") != std::string::npos) 347 | // return FileName.substr(FileName.find_last_of(".")+1); 348 | // return ""; 349 | //} 350 | 351 | std::string GetBaseDir(const std::string &filepath) { 352 | if (filepath.find_last_of("/\\") != std::string::npos) 353 | return filepath.substr(0, filepath.find_last_of("/\\")); 354 | return ""; 355 | } 356 | 357 | // std::string base64_encode(unsigned char const* , unsigned int len); 358 | std::string base64_decode(std::string const &s); 359 | 360 | /* 361 | base64.cpp and base64.h 362 | 363 | Copyright (C) 2004-2008 René Nyffenegger 364 | 365 | This source code is provided 'as-is', without any express or implied 366 | warranty. In no event will the author be held liable for any damages 367 | arising from the use of this software. 368 | 369 | Permission is granted to anyone to use this software for any purpose, 370 | including commercial applications, and to alter it and redistribute it 371 | freely, subject to the following restrictions: 372 | 373 | 1. The origin of this source code must not be misrepresented; you must not 374 | claim that you wrote the original source code. If you use this source code 375 | in a product, an acknowledgment in the product documentation would be 376 | appreciated but is not required. 377 | 378 | 2. Altered source versions must be plainly marked as such, and must not be 379 | misrepresented as being the original source code. 380 | 381 | 3. This notice may not be removed or altered from any source distribution. 382 | 383 | René Nyffenegger rene.nyffenegger@adp-gmbh.ch 384 | 385 | */ 386 | 387 | //#include "base64.h" 388 | //#include 389 | 390 | static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 391 | "abcdefghijklmnopqrstuvwxyz" 392 | "0123456789+/"; 393 | 394 | static inline bool is_base64(unsigned char c) { 395 | return (isalnum(c) || (c == '+') || (c == '/')); 396 | } 397 | 398 | #if 0 399 | std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) { 400 | std::string ret; 401 | int i = 0; 402 | int j = 0; 403 | unsigned char char_array_3[3]; 404 | unsigned char char_array_4[4]; 405 | 406 | while (in_len--) { 407 | char_array_3[i++] = *(bytes_to_encode++); 408 | if (i == 3) { 409 | char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; 410 | char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); 411 | char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); 412 | char_array_4[3] = char_array_3[2] & 0x3f; 413 | 414 | for(i = 0; (i <4) ; i++) 415 | ret += base64_chars[char_array_4[i]]; 416 | i = 0; 417 | } 418 | } 419 | 420 | if (i) 421 | { 422 | for(j = i; j < 3; j++) 423 | char_array_3[j] = '\0'; 424 | 425 | char_array_4[0] = (char_array_3[0] & 0xfc) >> 2; 426 | char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4); 427 | char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6); 428 | char_array_4[3] = char_array_3[2] & 0x3f; 429 | 430 | for (j = 0; (j < i + 1); j++) 431 | ret += base64_chars[char_array_4[j]]; 432 | 433 | while((i++ < 3)) 434 | ret += '='; 435 | 436 | } 437 | 438 | return ret; 439 | 440 | } 441 | #endif 442 | 443 | std::string base64_decode(std::string const &encoded_string) { 444 | int in_len = encoded_string.size(); 445 | int i = 0; 446 | int j = 0; 447 | int in_ = 0; 448 | unsigned char char_array_4[4], char_array_3[3]; 449 | std::string ret; 450 | 451 | while (in_len-- && (encoded_string[in_] != '=') && 452 | is_base64(encoded_string[in_])) { 453 | char_array_4[i++] = encoded_string[in_]; 454 | in_++; 455 | if (i == 4) { 456 | for (i = 0; i < 4; i++) 457 | char_array_4[i] = base64_chars.find(char_array_4[i]); 458 | 459 | char_array_3[0] = 460 | (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); 461 | char_array_3[1] = 462 | ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); 463 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 464 | 465 | for (i = 0; (i < 3); i++) 466 | ret += char_array_3[i]; 467 | i = 0; 468 | } 469 | } 470 | 471 | if (i) { 472 | for (j = i; j < 4; j++) 473 | char_array_4[j] = 0; 474 | 475 | for (j = 0; j < 4; j++) 476 | char_array_4[j] = base64_chars.find(char_array_4[j]); 477 | 478 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); 479 | char_array_3[1] = 480 | ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); 481 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 482 | 483 | for (j = 0; (j < i - 1); j++) 484 | ret += char_array_3[j]; 485 | } 486 | 487 | return ret; 488 | } 489 | 490 | bool LoadExternalFile(std::vector &out, std::string &err, 491 | const std::string &filename, const std::string &basedir, 492 | size_t reqBytes, bool checkSize) { 493 | out.clear(); 494 | 495 | std::vector paths; 496 | paths.push_back(basedir); 497 | paths.push_back("."); 498 | 499 | std::string filepath = FindFile(paths, filename); 500 | if (filepath.empty()) { 501 | err += "File not found : " + filename; 502 | return false; 503 | } 504 | 505 | std::ifstream f(filepath.c_str(), std::ifstream::binary); 506 | if (!f) { 507 | err += "File open error : " + filepath; 508 | return false; 509 | } 510 | 511 | f.seekg(0, f.end); 512 | size_t sz = f.tellg(); 513 | std::vector buf(sz); 514 | 515 | f.seekg(0, f.beg); 516 | f.read(reinterpret_cast(&buf.at(0)), sz); 517 | f.close(); 518 | 519 | if (checkSize) { 520 | if (reqBytes == sz) { 521 | out.swap(buf); 522 | return true; 523 | } else { 524 | std::stringstream ss; 525 | ss << "File size mismatch : " << filepath << ", requestedBytes " 526 | << reqBytes << ", but got " << sz << std::endl; 527 | err += ss.str(); 528 | return false; 529 | } 530 | } 531 | 532 | out.swap(buf); 533 | return true; 534 | } 535 | 536 | bool IsDataURI(const std::string &in) { 537 | std::string header = "data:application/octet-stream;base64,"; 538 | if (in.find(header) == 0) { 539 | return true; 540 | } 541 | 542 | header = "data:image/png;base64,"; 543 | if (in.find(header) == 0) { 544 | return true; 545 | } 546 | 547 | header = "data:image/jpeg;base64,"; 548 | if (in.find(header) == 0) { 549 | return true; 550 | } 551 | 552 | return false; 553 | } 554 | 555 | bool DecodeDataURI(std::vector &out, const std::string &in, 556 | size_t reqBytes, bool checkSize) { 557 | std::string header = "data:application/octet-stream;base64,"; 558 | std::string data; 559 | if (in.find(header) == 0) { 560 | data = base64_decode(in.substr(header.size())); // cut mime string. 561 | } 562 | 563 | if (data.empty()) { 564 | header = "data:image/jpeg;base64,"; 565 | if (in.find(header) == 0) { 566 | data = base64_decode(in.substr(header.size())); // cut mime string. 567 | } 568 | } 569 | 570 | if (data.empty()) { 571 | header = "data:image/png;base64,"; 572 | if (in.find(header) == 0) { 573 | data = base64_decode(in.substr(header.size())); // cut mime string. 574 | } 575 | } 576 | 577 | if (data.empty()) { 578 | return false; 579 | } 580 | 581 | if (checkSize) { 582 | if (data.size() != reqBytes) { 583 | return false; 584 | } 585 | out.resize(reqBytes); 586 | } else { 587 | out.resize(data.size()); 588 | } 589 | std::copy(data.begin(), data.end(), out.begin()); 590 | return true; 591 | 592 | return false; 593 | } 594 | 595 | bool ParseBooleanProperty(bool &ret, std::string &err, 596 | const picojson::object &o, 597 | const std::string &property, bool required) { 598 | picojson::object::const_iterator it = o.find(property); 599 | if (it == o.end()) { 600 | if (required) { 601 | err += "'" + property + "' property is missing.\n"; 602 | } 603 | return false; 604 | } 605 | 606 | if (!it->second.is()) { 607 | if (required) { 608 | err += "'" + property + "' property is not a bool type.\n"; 609 | } 610 | return false; 611 | } 612 | 613 | ret = it->second.get(); 614 | 615 | return true; 616 | } 617 | 618 | bool ParseNumberProperty(double &ret, std::string &err, 619 | const picojson::object &o, const std::string &property, 620 | bool required) { 621 | picojson::object::const_iterator it = o.find(property); 622 | if (it == o.end()) { 623 | if (required) { 624 | err += "'" + property + "' property is missing.\n"; 625 | } 626 | return false; 627 | } 628 | 629 | if (!it->second.is()) { 630 | if (required) { 631 | err += "'" + property + "' property is not a number type.\n"; 632 | } 633 | return false; 634 | } 635 | 636 | ret = it->second.get(); 637 | 638 | return true; 639 | } 640 | 641 | bool ParseNumberArrayProperty(std::vector &ret, std::string &err, 642 | const picojson::object &o, 643 | const std::string &property, bool required) { 644 | picojson::object::const_iterator it = o.find(property); 645 | if (it == o.end()) { 646 | if (required) { 647 | err += "'" + property + "' property is missing.\n"; 648 | } 649 | return false; 650 | } 651 | 652 | if (!it->second.is()) { 653 | if (required) { 654 | err += "'" + property + "' property is not an array.\n"; 655 | } 656 | return false; 657 | } 658 | 659 | ret.clear(); 660 | const picojson::array &arr = it->second.get(); 661 | for (size_t i = 0; i < arr.size(); i++) { 662 | if (!arr[i].is()) { 663 | if (required) { 664 | err += "'" + property + "' property is not a number.\n"; 665 | } 666 | return false; 667 | } 668 | ret.push_back(arr[i].get()); 669 | } 670 | 671 | return true; 672 | } 673 | 674 | bool ParseStringProperty(std::string &ret, std::string &err, 675 | const picojson::object &o, const std::string &property, 676 | bool required) { 677 | picojson::object::const_iterator it = o.find(property); 678 | if (it == o.end()) { 679 | if (required) { 680 | err += "'" + property + "' property is missing.\n"; 681 | } 682 | return false; 683 | } 684 | 685 | if (!it->second.is()) { 686 | if (required) { 687 | err += "'" + property + "' property is not a string type.\n"; 688 | } 689 | return false; 690 | } 691 | 692 | ret = it->second.get(); 693 | 694 | return true; 695 | } 696 | 697 | bool ParseStringArrayProperty(std::vector &ret, std::string &err, 698 | const picojson::object &o, 699 | const std::string &property, bool required) { 700 | picojson::object::const_iterator it = o.find(property); 701 | if (it == o.end()) { 702 | if (required) { 703 | err += "'" + property + "' property is missing.\n"; 704 | } 705 | return false; 706 | } 707 | 708 | if (!it->second.is()) { 709 | if (required) { 710 | err += "'" + property + "' property is not an array.\n"; 711 | } 712 | return false; 713 | } 714 | 715 | ret.clear(); 716 | const picojson::array &arr = it->second.get(); 717 | for (size_t i = 0; i < arr.size(); i++) { 718 | if (!arr[i].is()) { 719 | if (required) { 720 | err += "'" + property + "' property is not a string.\n"; 721 | } 722 | return false; 723 | } 724 | ret.push_back(arr[i].get()); 725 | } 726 | 727 | return true; 728 | } 729 | 730 | bool ParseAsset(Asset &asset, std::string &err, const picojson::object &o) { 731 | 732 | ParseStringProperty(asset.generator, err, o, "generator", false); 733 | ParseBooleanProperty(asset.premultipliedAlpha, err, o, "premultipliedAlpha", 734 | false); 735 | 736 | ParseStringProperty(asset.version, err, o, "version", false); 737 | 738 | picojson::object::const_iterator profile = o.find("profile"); 739 | if (profile != o.end()) { 740 | const picojson::value &v = profile->second; 741 | if (v.contains("api") & v.get("api").is()) { 742 | asset.profile_api = v.get("api").get(); 743 | } 744 | if (v.contains("version") & v.get("version").is()) { 745 | asset.profile_version = v.get("version").get(); 746 | } 747 | } 748 | 749 | return true; 750 | } 751 | 752 | bool ParseImage(Image &image, std::string &err, const picojson::object &o, 753 | const std::string &basedir) { 754 | 755 | std::string uri; 756 | if (!ParseStringProperty(uri, err, o, "uri", true)) { 757 | return false; 758 | } 759 | 760 | ParseStringProperty(image.name, err, o, "name", false); 761 | 762 | std::vector img; 763 | if (IsDataURI(uri)) { 764 | if (!DecodeDataURI(img, uri, 0, false)) { 765 | err += "Failed to decode 'uri'.\n"; 766 | return false; 767 | } 768 | } else { 769 | // Assume external file 770 | if (!LoadExternalFile(img, err, uri, basedir, 0, false)) { 771 | err += "Failed to load external 'uri'.\n"; 772 | return false; 773 | } 774 | if (img.empty()) { 775 | err += "File is empty.\n"; 776 | return false; 777 | } 778 | } 779 | 780 | int w, h, comp; 781 | unsigned char *data = 782 | stbi_load_from_memory(&img.at(0), img.size(), &w, &h, &comp, 0); 783 | if (!data) { 784 | err += "Unknown image format.\n"; 785 | return false; 786 | } 787 | 788 | if (w < 1 || h < 1) { 789 | err += "Unknown image format.\n"; 790 | return false; 791 | } 792 | 793 | image.width = w; 794 | image.height = h; 795 | image.component = comp; 796 | image.image.resize(w * h * comp); 797 | std::copy(data, data + w * h * comp, image.image.begin()); 798 | 799 | return true; 800 | } 801 | 802 | bool ParseTexture(Texture &texture, std::string &err, const picojson::object &o, 803 | const std::string &basedir) { 804 | 805 | if (!ParseStringProperty(texture.sampler, err, o, "sampler", true)) { 806 | return false; 807 | } 808 | 809 | if (!ParseStringProperty(texture.source, err, o, "source", true)) { 810 | return false; 811 | } 812 | 813 | ParseStringProperty(texture.name, err, o, "name", false); 814 | 815 | double format = TINYGLTF_TEXTURE_FORMAT_RGBA; 816 | ParseNumberProperty(format, err, o, "format", false); 817 | 818 | double internalFormat = TINYGLTF_TEXTURE_FORMAT_RGBA; 819 | ParseNumberProperty(internalFormat, err, o, "internalFormat", false); 820 | 821 | double target = TINYGLTF_TEXTURE_TARGET_TEXTURE2D; 822 | ParseNumberProperty(target, err, o, "target", false); 823 | 824 | double type = TINYGLTF_TEXTURE_TYPE_UNSIGNED_BYTE; 825 | ParseNumberProperty(type, err, o, "type", false); 826 | 827 | texture.format = static_cast(format); 828 | texture.internalFormat = static_cast(internalFormat); 829 | texture.target = static_cast(target); 830 | texture.type = static_cast(type); 831 | 832 | return true; 833 | } 834 | 835 | bool ParseBuffer(Buffer &buffer, std::string &err, const picojson::object &o, 836 | const std::string &basedir) { 837 | double byteLength; 838 | if (!ParseNumberProperty(byteLength, err, o, "byteLength", true)) { 839 | return false; 840 | } 841 | 842 | std::string uri; 843 | if (!ParseStringProperty(uri, err, o, "uri", true)) { 844 | return false; 845 | } 846 | 847 | picojson::object::const_iterator type = o.find("type"); 848 | if (type != o.end()) { 849 | if (type->second.is()) { 850 | const std::string &ty = (type->second).get(); 851 | if (ty.compare("arraybuffer") == 0) { 852 | // buffer.type = "arraybuffer"; 853 | } 854 | } 855 | } 856 | 857 | size_t bytes = static_cast(byteLength); 858 | if (IsDataURI(uri)) { 859 | if (!DecodeDataURI(buffer.data, uri, bytes, true)) { 860 | err += "Failed to decode 'uri'.\n"; 861 | return false; 862 | } 863 | } else { 864 | // Assume external .bin file. 865 | if (!LoadExternalFile(buffer.data, err, uri, basedir, bytes, true)) { 866 | return false; 867 | } 868 | } 869 | 870 | ParseStringProperty(buffer.name, err, o, "name", false); 871 | 872 | return true; 873 | } 874 | 875 | bool ParseBufferView(BufferView &bufferView, std::string &err, 876 | const picojson::object &o) { 877 | std::string buffer; 878 | if (!ParseStringProperty(buffer, err, o, "buffer", true)) { 879 | return false; 880 | } 881 | 882 | double byteOffset; 883 | if (!ParseNumberProperty(byteOffset, err, o, "byteOffset", true)) { 884 | return false; 885 | } 886 | 887 | double byteLength = 0.0; 888 | ParseNumberProperty(byteLength, err, o, "byteLength", false); 889 | 890 | double target = 0.0; 891 | ParseNumberProperty(target, err, o, "target", false); 892 | int targetValue = static_cast(target); 893 | if ((targetValue == TINYGLTF_TARGET_ARRAY_BUFFER) || 894 | (targetValue == TINYGLTF_TARGET_ELEMENT_ARRAY_BUFFER)) { 895 | // OK 896 | } else { 897 | targetValue = 0; 898 | } 899 | bufferView.target = targetValue; 900 | 901 | ParseStringProperty(bufferView.name, err, o, "name", false); 902 | 903 | bufferView.buffer = buffer; 904 | bufferView.byteOffset = static_cast(byteOffset); 905 | bufferView.byteLength = static_cast(byteLength); 906 | 907 | return true; 908 | } 909 | 910 | bool ParseAccessor(Accessor &accessor, std::string &err, 911 | const picojson::object &o) { 912 | std::string bufferView; 913 | if (!ParseStringProperty(bufferView, err, o, "bufferView", true)) { 914 | return false; 915 | } 916 | 917 | double byteOffset; 918 | if (!ParseNumberProperty(byteOffset, err, o, "byteOffset", true)) { 919 | return false; 920 | } 921 | 922 | double componentType; 923 | if (!ParseNumberProperty(componentType, err, o, "componentType", true)) { 924 | return false; 925 | } 926 | 927 | double count = 0.0; 928 | if (!ParseNumberProperty(count, err, o, "count", true)) { 929 | return false; 930 | } 931 | 932 | std::string type; 933 | if (!ParseStringProperty(type, err, o, "type", true)) { 934 | return false; 935 | } 936 | 937 | if (type.compare("SCALAR") == 0) { 938 | accessor.type = TINYGLTF_TYPE_SCALAR; 939 | } else if (type.compare("VEC2") == 0) { 940 | accessor.type = TINYGLTF_TYPE_VEC2; 941 | } else if (type.compare("VEC3") == 0) { 942 | accessor.type = TINYGLTF_TYPE_VEC3; 943 | } else if (type.compare("VEC4") == 0) { 944 | accessor.type = TINYGLTF_TYPE_VEC4; 945 | } else if (type.compare("MAT2") == 0) { 946 | accessor.type = TINYGLTF_TYPE_MAT2; 947 | } else if (type.compare("MAT3") == 0) { 948 | accessor.type = TINYGLTF_TYPE_MAT3; 949 | } else if (type.compare("MAT4") == 0) { 950 | accessor.type = TINYGLTF_TYPE_MAT4; 951 | } else { 952 | std::stringstream ss; 953 | ss << "Unsupported `type` for accessor object. Got \"" << type << "\"\n"; 954 | err += ss.str(); 955 | return false; 956 | } 957 | 958 | double byteStride = 0.0; 959 | ParseNumberProperty(byteStride, err, o, "byteStride", false); 960 | 961 | ParseStringProperty(accessor.name, err, o, "name", false); 962 | 963 | accessor.minValues.clear(); 964 | accessor.maxValues.clear(); 965 | ParseNumberArrayProperty(accessor.minValues, err, o, "min", false); 966 | ParseNumberArrayProperty(accessor.maxValues, err, o, "max", false); 967 | 968 | accessor.count = static_cast(count); 969 | accessor.bufferView = bufferView; 970 | accessor.byteOffset = static_cast(byteOffset); 971 | accessor.byteStride = static_cast(byteStride); 972 | 973 | { 974 | int comp = static_cast(componentType); 975 | if (comp >= TINYGLTF_COMPONENT_TYPE_BYTE && 976 | comp <= TINYGLTF_COMPONENT_TYPE_DOUBLE) { 977 | // OK 978 | accessor.componentType = comp; 979 | } else { 980 | std::stringstream ss; 981 | ss << "Invalid `componentType` in accessor. Got " << comp << "\n"; 982 | err += ss.str(); 983 | return false; 984 | } 985 | } 986 | 987 | return true; 988 | } 989 | 990 | bool ParsePrimitive(Primitive &primitive, std::string &err, 991 | const picojson::object &o) { 992 | if (!ParseStringProperty(primitive.material, err, o, "material", true)) { 993 | return false; 994 | } 995 | 996 | double mode = static_cast(TINYGLTF_MODE_TRIANGLES); 997 | ParseNumberProperty(mode, err, o, "mode", false); 998 | 999 | int primMode = static_cast(mode); 1000 | if (primMode != TINYGLTF_MODE_TRIANGLES) { 1001 | err += "Currently TinyGLTFLoader doesn not support primitive mode other \n" 1002 | "than TRIANGLES.\n"; 1003 | return false; 1004 | } 1005 | primitive.mode = primMode; 1006 | 1007 | primitive.indices = ""; 1008 | ParseStringProperty(primitive.indices, err, o, "indices", false); 1009 | 1010 | primitive.attributes.clear(); 1011 | picojson::object::const_iterator attribsObject = o.find("attributes"); 1012 | if ((attribsObject != o.end()) && 1013 | (attribsObject->second).is()) { 1014 | const picojson::object &attribs = 1015 | (attribsObject->second).get(); 1016 | picojson::object::const_iterator it(attribs.begin()); 1017 | picojson::object::const_iterator itEnd(attribs.end()); 1018 | for (; it != itEnd; it++) { 1019 | const std::string &name = it->first; 1020 | if (!(it->second).is()) { 1021 | err += "attribute expects string value.\n"; 1022 | return false; 1023 | } 1024 | const std::string &value = (it->second).get(); 1025 | 1026 | primitive.attributes[name] = value; 1027 | } 1028 | } 1029 | 1030 | return true; 1031 | } 1032 | 1033 | bool ParseMesh(Mesh &mesh, std::string &err, const picojson::object &o) { 1034 | ParseStringProperty(mesh.name, err, o, "name", false); 1035 | 1036 | mesh.primitives.clear(); 1037 | picojson::object::const_iterator primObject = o.find("primitives"); 1038 | if ((primObject != o.end()) && (primObject->second).is()) { 1039 | const picojson::array &primArray = 1040 | (primObject->second).get(); 1041 | for (size_t i = 0; i < primArray.size(); i++) { 1042 | Primitive primitive; 1043 | ParsePrimitive(primitive, err, primArray[i].get()); 1044 | mesh.primitives.push_back(primitive); 1045 | } 1046 | } 1047 | 1048 | return true; 1049 | } 1050 | 1051 | bool ParseNode(Node &node, std::string &err, const picojson::object &o) { 1052 | ParseStringProperty(node.name, err, o, "name", false); 1053 | 1054 | ParseNumberArrayProperty(node.rotation, err, o, "rotation", false); 1055 | ParseNumberArrayProperty(node.scale, err, o, "scale", false); 1056 | ParseNumberArrayProperty(node.translation, err, o, "translation", false); 1057 | ParseNumberArrayProperty(node.matrix, err, o, "matrix", false); 1058 | ParseStringArrayProperty(node.meshes, err, o, "meshes", false); 1059 | 1060 | node.children.clear(); 1061 | picojson::object::const_iterator childrenObject = o.find("children"); 1062 | if ((childrenObject != o.end()) && 1063 | (childrenObject->second).is()) { 1064 | const picojson::array &childrenArray = 1065 | (childrenObject->second).get(); 1066 | for (size_t i = 0; i < childrenArray.size(); i++) { 1067 | Node node; 1068 | if (!childrenArray[i].is()) { 1069 | err += "Invalid `children` array.\n"; 1070 | return false; 1071 | } 1072 | const std::string &childrenNode = childrenArray[i].get(); 1073 | node.children.push_back(childrenNode); 1074 | } 1075 | } 1076 | 1077 | return true; 1078 | } 1079 | 1080 | bool ParseMaterial(Material &material, std::string &err, 1081 | const picojson::object &o) { 1082 | ParseStringProperty(material.name, err, o, "name", false); 1083 | ParseStringProperty(material.technique, err, o, "technique", false); 1084 | 1085 | material.values.clear(); 1086 | picojson::object::const_iterator valuesIt = o.find("values"); 1087 | if ((valuesIt != o.end()) && (valuesIt->second).is()) { 1088 | 1089 | const picojson::object &valuesObject = 1090 | (valuesIt->second).get(); 1091 | picojson::object::const_iterator it(valuesObject.begin()); 1092 | picojson::object::const_iterator itEnd(valuesObject.end()); 1093 | 1094 | for (; it != itEnd; it++) { 1095 | // Assume number values. 1096 | Parameter param; 1097 | if (ParseStringProperty(param.stringValue, err, valuesObject, it->first, 1098 | false)) { 1099 | // Found string property. 1100 | } else if (!ParseNumberArrayProperty(param.numberArray, err, valuesObject, 1101 | it->first, false)) { 1102 | // Fallback to numer property. 1103 | double value; 1104 | if (ParseNumberProperty(value, err, valuesObject, it->first, false)) { 1105 | param.numberArray.push_back(value); 1106 | } 1107 | } 1108 | 1109 | material.values[it->first] = param; 1110 | } 1111 | } 1112 | 1113 | return true; 1114 | } 1115 | } 1116 | 1117 | bool TinyGLTFLoader::LoadFromString(Scene &scene, std::string &err, 1118 | const char *str, unsigned int length, 1119 | const std::string &baseDir) { 1120 | picojson::value v; 1121 | std::string perr = picojson::parse(v, str, str + length); 1122 | 1123 | if (!perr.empty()) { 1124 | err = perr; 1125 | return false; 1126 | } 1127 | 1128 | if (v.contains("scene") && v.get("scene").is()) { 1129 | // OK 1130 | } else { 1131 | err += "\"scene\" object not found in .gltf\n"; 1132 | return false; 1133 | } 1134 | 1135 | if (v.contains("scenes") && v.get("scenes").is()) { 1136 | // OK 1137 | } else { 1138 | err += "\"scenes\" object not found in .gltf\n"; 1139 | return false; 1140 | } 1141 | 1142 | if (v.contains("nodes") && v.get("nodes").is()) { 1143 | // OK 1144 | } else { 1145 | err += "\"nodes\" object not found in .gltf\n"; 1146 | return false; 1147 | } 1148 | 1149 | if (v.contains("accessors") && v.get("accessors").is()) { 1150 | // OK 1151 | } else { 1152 | err += "\"accessors\" object not found in .gltf\n"; 1153 | return false; 1154 | } 1155 | 1156 | if (v.contains("buffers") && v.get("buffers").is()) { 1157 | // OK 1158 | } else { 1159 | err += "\"buffers\" object not found in .gltf\n"; 1160 | return false; 1161 | } 1162 | 1163 | if (v.contains("bufferViews") && 1164 | v.get("bufferViews").is()) { 1165 | // OK 1166 | } else { 1167 | err += "\"bufferViews\" object not found in .gltf\n"; 1168 | return false; 1169 | } 1170 | 1171 | scene.buffers.clear(); 1172 | scene.bufferViews.clear(); 1173 | scene.accessors.clear(); 1174 | scene.meshes.clear(); 1175 | scene.nodes.clear(); 1176 | scene.defaultScene = ""; 1177 | 1178 | // 0. Parse Asset 1179 | if (v.contains("asset") && v.get("asset").is()) { 1180 | const picojson::object &root = v.get("asset").get(); 1181 | 1182 | ParseAsset(scene.asset, err, root); 1183 | } 1184 | 1185 | // 1. Parse Buffer 1186 | if (v.contains("buffers") && v.get("buffers").is()) { 1187 | const picojson::object &root = v.get("buffers").get(); 1188 | 1189 | picojson::object::const_iterator it(root.begin()); 1190 | picojson::object::const_iterator itEnd(root.end()); 1191 | for (; it != itEnd; it++) { 1192 | 1193 | Buffer buffer; 1194 | if (!ParseBuffer(buffer, err, (it->second).get(), 1195 | baseDir)) { 1196 | return false; 1197 | } 1198 | 1199 | scene.buffers[it->first] = buffer; 1200 | } 1201 | } 1202 | 1203 | // 2. Parse BufferView 1204 | if (v.contains("bufferViews") && 1205 | v.get("bufferViews").is()) { 1206 | 1207 | const picojson::object &root = v.get("bufferViews").get(); 1208 | 1209 | picojson::object::const_iterator it(root.begin()); 1210 | picojson::object::const_iterator itEnd(root.end()); 1211 | for (; it != itEnd; it++) { 1212 | 1213 | BufferView bufferView; 1214 | if (!ParseBufferView(bufferView, err, 1215 | (it->second).get())) { 1216 | return false; 1217 | } 1218 | 1219 | scene.bufferViews[it->first] = bufferView; 1220 | } 1221 | } 1222 | 1223 | // 3. Parse Accessor 1224 | if (v.contains("accessors") && v.get("accessors").is()) { 1225 | 1226 | const picojson::object &root = v.get("accessors").get(); 1227 | 1228 | picojson::object::const_iterator it(root.begin()); 1229 | picojson::object::const_iterator itEnd(root.end()); 1230 | for (; it != itEnd; it++) { 1231 | 1232 | Accessor accessor; 1233 | if (!ParseAccessor(accessor, err, (it->second).get())) { 1234 | return false; 1235 | } 1236 | 1237 | scene.accessors[it->first] = accessor; 1238 | } 1239 | } 1240 | 1241 | // 4. Parse Mesh 1242 | if (v.contains("meshes") && v.get("meshes").is()) { 1243 | 1244 | const picojson::object &root = v.get("meshes").get(); 1245 | 1246 | picojson::object::const_iterator it(root.begin()); 1247 | picojson::object::const_iterator itEnd(root.end()); 1248 | for (; it != itEnd; it++) { 1249 | 1250 | Mesh mesh; 1251 | if (!ParseMesh(mesh, err, (it->second).get())) { 1252 | return false; 1253 | } 1254 | 1255 | scene.meshes[it->first] = mesh; 1256 | } 1257 | } 1258 | 1259 | // 5. Parse Node 1260 | if (v.contains("nodes") && v.get("nodes").is()) { 1261 | 1262 | const picojson::object &root = v.get("nodes").get(); 1263 | 1264 | picojson::object::const_iterator it(root.begin()); 1265 | picojson::object::const_iterator itEnd(root.end()); 1266 | for (; it != itEnd; it++) { 1267 | 1268 | Node node; 1269 | if (!ParseNode(node, err, (it->second).get())) { 1270 | return false; 1271 | } 1272 | 1273 | scene.nodes[it->first] = node; 1274 | } 1275 | } 1276 | 1277 | // 6. Parse scenes. 1278 | if (v.contains("scenes") && v.get("scenes").is()) { 1279 | 1280 | const picojson::object &root = v.get("scenes").get(); 1281 | 1282 | picojson::object::const_iterator it(root.begin()); 1283 | picojson::object::const_iterator itEnd(root.end()); 1284 | for (; it != itEnd; it++) { 1285 | 1286 | const picojson::object &o = (it->second).get(); 1287 | std::vector nodes; 1288 | if (!ParseStringArrayProperty(nodes, err, o, "nodes", false)) { 1289 | return false; 1290 | } 1291 | 1292 | scene.scenes[it->first] = nodes; 1293 | } 1294 | } 1295 | 1296 | // 7. Parse default scenes. 1297 | if (v.contains("scene") && v.get("scene").is()) { 1298 | 1299 | const std::string defaultScene = v.get("scene").get(); 1300 | 1301 | scene.defaultScene = defaultScene; 1302 | } 1303 | 1304 | // 8. Parse Material 1305 | if (v.contains("materials") && v.get("materials").is()) { 1306 | 1307 | const picojson::object &root = v.get("materials").get(); 1308 | 1309 | picojson::object::const_iterator it(root.begin()); 1310 | picojson::object::const_iterator itEnd(root.end()); 1311 | for (; it != itEnd; it++) { 1312 | 1313 | Material material; 1314 | if (!ParseMaterial(material, err, (it->second).get())) { 1315 | return false; 1316 | } 1317 | 1318 | scene.materials[it->first] = material; 1319 | } 1320 | } 1321 | 1322 | // 9. Parse Image 1323 | if (v.contains("images") && v.get("images").is()) { 1324 | 1325 | const picojson::object &root = v.get("images").get(); 1326 | 1327 | picojson::object::const_iterator it(root.begin()); 1328 | picojson::object::const_iterator itEnd(root.end()); 1329 | for (; it != itEnd; it++) { 1330 | 1331 | Image image; 1332 | if (!ParseImage(image, err, (it->second).get(), 1333 | baseDir)) { 1334 | return false; 1335 | } 1336 | 1337 | scene.images[it->first] = image; 1338 | } 1339 | } 1340 | 1341 | // 9. Parse Texture 1342 | if (v.contains("textures") && v.get("textures").is()) { 1343 | 1344 | const picojson::object &root = v.get("textures").get(); 1345 | 1346 | picojson::object::const_iterator it(root.begin()); 1347 | picojson::object::const_iterator itEnd(root.end()); 1348 | for (; it != itEnd; it++) { 1349 | 1350 | Texture texture; 1351 | if (!ParseTexture(texture, err, (it->second).get(), 1352 | baseDir)) { 1353 | return false; 1354 | } 1355 | 1356 | scene.textures[it->first] = texture; 1357 | } 1358 | } 1359 | 1360 | return true; 1361 | } 1362 | 1363 | bool TinyGLTFLoader::LoadFromFile(Scene &scene, std::string &err, 1364 | const std::string &filename) { 1365 | std::stringstream ss; 1366 | 1367 | std::ifstream f(filename.c_str()); 1368 | if (!f) { 1369 | ss << "Failed to open file: " << filename << std::endl; 1370 | err = ss.str(); 1371 | return false; 1372 | } 1373 | 1374 | f.seekg(0, f.end); 1375 | size_t sz = f.tellg(); 1376 | std::vector buf(sz); 1377 | 1378 | f.seekg(0, f.beg); 1379 | f.read(&buf.at(0), sz); 1380 | f.close(); 1381 | 1382 | std::string basedir = GetBaseDir(filename); 1383 | 1384 | bool ret = LoadFromString(scene, err, &buf.at(0), buf.size(), basedir); 1385 | 1386 | return ret; 1387 | } 1388 | 1389 | #endif // TINYGLTF_LOADER_IMPLEMENTATION 1390 | 1391 | #endif // TINY_GLTF_LOADER_H 1392 | -------------------------------------------------------------------------------- /Source/GLTFLoader/Private/GLTFMeshBuilder.cpp: -------------------------------------------------------------------------------- 1 | #include "GLTFLoaderPrivatePCH.h" 2 | 3 | #include 4 | #include 5 | 6 | #define TINYGLTF_LOADER_IMPLEMENTATION 7 | #define STB_IMAGE_IMPLEMENTATION 8 | #include "tiny_gltf_loader.h" 9 | 10 | #include "GLTFMeshBuilder.h" 11 | #include "GLTFLoaderCommands.h" 12 | 13 | //#include "Editor/UnrealEd/Classes/Factories/Factory.h" 14 | 15 | // These replace TargetPlatform.h since it can't seem to find the right paths from here 16 | #include "ModuleManager.h" 17 | #include "Developer/TargetPlatform/Public/Interfaces/TargetDeviceId.h" 18 | #include "Developer/TargetPlatform/Public/Interfaces/ITargetDevice.h" 19 | #include "Developer/TargetPlatform/Public/Interfaces/ITargetPlatform.h" 20 | #include "Developer/TargetPlatform/Public/Interfaces/ITargetPlatformModule.h" 21 | #include "Developer/TargetPlatform/Public/Interfaces/ITargetPlatformManagerModule.h" 22 | 23 | #include "UnrealEd.h" 24 | #include "Developer/RawMesh/Public/RawMesh.h" 25 | #include "Developer/MeshUtilities/Public/MeshUtilities.h" 26 | 27 | #include "Engine.h" 28 | #include "StaticMeshResources.h" 29 | #include "TextureLayout.h" 30 | #include "ObjectTools.h" 31 | #include "PackageTools.h" 32 | #include "Editor/UnrealEd/Classes/Factories/FbxStaticMeshImportData.h" 33 | #include "../Private/GeomFitUtils.h" 34 | #include "FbxErrors.h" 35 | #include "Engine/StaticMeshSocket.h" 36 | #include "Engine/Polys.h" 37 | #include "PhysicsEngine/BodySetup.h" 38 | 39 | /// @cond 40 | // Syntactic sugar to neatly map the TinyGLTF enum to the corresponding data type 41 | // Adapted from http://stackoverflow.com/questions/1735796/is-it-possible-to-choose-a-c-generic-type-parameter-at-runtime 42 | template struct GLTFType; 43 | template<> struct GLTFType { typedef int8 Type; }; 44 | template<> struct GLTFType { typedef uint8 Type; }; 45 | template<> struct GLTFType { typedef int16 Type; }; 46 | template<> struct GLTFType { typedef uint16 Type; }; 47 | template<> struct GLTFType { typedef int32 Type; }; 48 | template<> struct GLTFType { typedef uint32 Type; }; 49 | template<> struct GLTFType { typedef float Type; }; 50 | template<> struct GLTFType { typedef double Type; }; 51 | 52 | template<> struct GLTFType { typedef FVector2D Type; }; 53 | template<> struct GLTFType { typedef FVector Type; }; 54 | template<> struct GLTFType { typedef FVector4 Type; }; 55 | /// @endcond 56 | 57 | template 58 | bool GLTFMeshBuilder::ConvertAttrib(TArray &OutArray, tinygltf::Mesh* Mesh, std::string AttribName, bool UseWedgeIndices, bool AutoSetArraySize) 59 | { 60 | if (AttribName != "__WedgeIndices" && !HasAttribute(Mesh, AttribName)) 61 | { 62 | return false; 63 | } 64 | 65 | UE_LOG(LogTemp, Log, TEXT("%s"), *(FText::Format(FText::FromString("Importing data for attribute \"{0}\""), FText::FromString(ToFString(AttribName)))).ToString()); 66 | 67 | if (AutoSetArraySize)// This should always be false since for now we are just extending the array each time we want to add an element 68 | { 69 | int32 Size = 0; 70 | if (UseWedgeIndices) 71 | { 72 | for (auto Prim : Mesh->primitives) 73 | { 74 | Size += GetNumWedges(&Prim); // Number of wedges 75 | } 76 | } 77 | else 78 | { 79 | for (auto Prim : Mesh->primitives) 80 | { 81 | Size += Scene->accessors[Prim.attributes.begin()->second].count; // Number of vertices 82 | } 83 | } 84 | 85 | OutArray.SetNumUninitialized(Size); 86 | } 87 | 88 | // Getting an attribute for individual triangle corners ("wedges") 89 | else if (UseWedgeIndices && AttribName != "__WedgeIndices")// Make sure we don't try to access indices for the index array itself! 90 | { 91 | for (auto Prim : Mesh->primitives) 92 | { 93 | std::string IndexAccessorName = Prim.indices; 94 | std::string AttribAccessorName = Prim.attributes[AttribName]; 95 | tinygltf::Accessor* IndexAccessor = &Scene->accessors[IndexAccessorName]; 96 | tinygltf::Accessor* AttribAccessor = &Scene->accessors[AttribAccessorName]; 97 | 98 | if (!IndexAccessor || !AttribAccessor) 99 | { 100 | AddTokenizedErrorMessage( 101 | FTokenizedMessage::Create( 102 | EMessageSeverity::Warning, 103 | FText::FromString(FString("Invalid accessor"))), 104 | FFbxErrors::Generic_Mesh_NoGeometry); 105 | return false; 106 | } 107 | 108 | TArray IndexArray; 109 | TArray VertArray; 110 | 111 | if (!GetBufferData(IndexArray, IndexAccessor) || !GetBufferData(VertArray, AttribAccessor)) 112 | { 113 | return false; 114 | } 115 | 116 | switch (Prim.mode) 117 | { 118 | case TINYGLTF_MODE_TRIANGLES: 119 | for (auto Index : IndexArray) 120 | { 121 | OutArray.Add(VertArray[Index]); 122 | } 123 | break; 124 | 125 | case TINYGLTF_MODE_TRIANGLE_STRIP: 126 | OutArray.Add(VertArray[IndexArray[0]]); 127 | OutArray.Add(VertArray[IndexArray[1]]); 128 | OutArray.Add(VertArray[IndexArray[2]]); 129 | for (int i = 2; i < IndexAccessor->count - 2; i += 2) 130 | { 131 | // First triangle 132 | OutArray.Add(VertArray[IndexArray[ i ]]); 133 | OutArray.Add(VertArray[IndexArray[i-1]]); 134 | OutArray.Add(VertArray[IndexArray[i+1]]); 135 | // Second triangle 136 | OutArray.Add(VertArray[IndexArray[ i ]]); 137 | OutArray.Add(VertArray[IndexArray[i+1]]); 138 | OutArray.Add(VertArray[IndexArray[i+2]]); 139 | } 140 | break; 141 | 142 | case TINYGLTF_MODE_TRIANGLE_FAN: 143 | for (int i = 1; i < IndexAccessor->count - 1; ++i) 144 | { 145 | // Triangle 146 | OutArray.Add(VertArray[IndexArray[ 0 ]]); 147 | OutArray.Add(VertArray[IndexArray[ i ]]); 148 | OutArray.Add(VertArray[IndexArray[i+1]]); 149 | } 150 | break; 151 | 152 | default: 153 | return false; 154 | } 155 | } 156 | } 157 | // Getting a vertex attribute 158 | else 159 | { 160 | for (auto Prim : Mesh->primitives) 161 | { 162 | std::string AccessorName; 163 | if (AttribName == "__WedgeIndices") 164 | { 165 | AccessorName = Prim.indices; 166 | } 167 | else 168 | { 169 | AccessorName = Prim.attributes[AttribName]; 170 | } 171 | if (!GetBufferData(OutArray, &Scene->accessors[AccessorName], true)) 172 | { 173 | return false; 174 | } 175 | } 176 | } 177 | return true; 178 | } 179 | 180 | // Retrieve a value from the buffer, implicitly accounting for endianness 181 | // Adapted from http://stackoverflow.com/questions/13001183/how-to-read-little-endian-integers-from-file-in-c 182 | template T GLTFMeshBuilder::BufferValue(void* Data/*, uint8 Size*/) 183 | { 184 | T Ret = T(0); 185 | 186 | auto NewData = reinterpret_cast(Data); 187 | for (int i = 0; i < sizeof(T); ++i) 188 | { 189 | Ret |= (T)(NewData[i]) << (8 * i); 190 | } 191 | return Ret; 192 | } 193 | 194 | // Use unions for floats and doubles since they don't have a bitwise OR operator 195 | template <> float GLTFMeshBuilder::BufferValue(void* Data) 196 | { 197 | assert(sizeof(float) == sizeof(int32)); 198 | 199 | union 200 | { 201 | float Ret; 202 | int32 IntRet; 203 | }; 204 | 205 | Ret = 0.0f; 206 | 207 | auto NewData = reinterpret_cast(Data); 208 | for (int i = 0; i < sizeof(int32); ++i) 209 | { 210 | IntRet |= (int32)(NewData[i]) << (8 * i); 211 | } 212 | return Ret; 213 | } 214 | 215 | template <> double GLTFMeshBuilder::BufferValue(void* Data) 216 | { 217 | assert(sizeof(float) == sizeof(int64)); 218 | 219 | union 220 | { 221 | double Ret; 222 | int64 IntRet; 223 | }; 224 | 225 | Ret = 0.0; 226 | 227 | auto NewData = reinterpret_cast(Data); 228 | for (int i = 0; i < sizeof(int64); ++i) 229 | { 230 | IntRet |= (int64)(NewData[i]) << (8 * i); 231 | } 232 | return Ret; 233 | } 234 | 235 | /// @cond 236 | struct MaterialPair 237 | { 238 | tinygltf::Material* GLTFMaterial; 239 | UMaterialInterface* Material; 240 | }; 241 | /// @endcond 242 | 243 | GLTFMeshBuilder::GLTFMeshBuilder(FString FilePath) 244 | { 245 | Loader = new tinygltf::TinyGLTFLoader; 246 | Scene = new tinygltf::Scene; 247 | 248 | std::string TempError; 249 | LoadSuccess = Loader->LoadFromFile((*Scene), TempError, ToStdString(FilePath)); 250 | Error = ToFString(TempError); 251 | } 252 | 253 | GLTFMeshBuilder::~GLTFMeshBuilder() 254 | { 255 | delete Loader; 256 | delete Scene; 257 | } 258 | 259 | int32 GLTFMeshBuilder::GetMeshCount(FString NodeName) 260 | { 261 | return (int32)Scene->nodes[ToStdString(NodeName)].meshes.size(); 262 | } 263 | 264 | FString GLTFMeshBuilder::GetRootNode() 265 | { 266 | for (auto ThisNode : Scene->nodes) 267 | { 268 | bool ShouldReturn = false; 269 | for (auto ThatNode : Scene->nodes) 270 | { 271 | if (FindInStdVector(ThatNode.second.children, ThisNode.first) == -1) // If this node's name doesn't appear in any node's list of children 272 | { 273 | ShouldReturn = true; 274 | } 275 | } 276 | 277 | if (ShouldReturn) 278 | { 279 | return ToFString(ThisNode.first); 280 | } 281 | } 282 | return FString(""); 283 | } 284 | 285 | tinygltf::Node* GLTFMeshBuilder::GetMeshParentNode(tinygltf::Mesh* InMesh) 286 | { 287 | for (auto &Node : Scene->nodes) 288 | { 289 | for (auto &MeshName : Node.second.meshes) 290 | { 291 | if (&Scene->meshes[MeshName] == InMesh) 292 | { 293 | return &Node.second; 294 | } 295 | } 296 | } 297 | return NULL; 298 | } 299 | 300 | TArray GLTFMeshBuilder::GetMeshNames(FString NodeName, bool GetChildren) 301 | { 302 | TArray MeshNameArray; 303 | 304 | for (auto Mesh : Scene->nodes[ToStdString(NodeName)].meshes) 305 | { 306 | MeshNameArray.Add(ToFString(Mesh)); 307 | } 308 | 309 | if (GetChildren) 310 | { 311 | for (auto ChildName : Scene->nodes[ToStdString(NodeName)].children) 312 | { 313 | MeshNameArray.Append(GetMeshNames(ToFString(ChildName))); 314 | } 315 | } 316 | 317 | return MeshNameArray; 318 | } 319 | 320 | UStaticMesh* GLTFMeshBuilder::ImportStaticMeshAsSingle(UObject* InParent, TArray& MeshNameArray, const FName InName, EObjectFlags Flags, UStaticMesh* InStaticMesh) 321 | { 322 | auto ImportOptions = FGLTFLoaderModule::ImportOptions; 323 | 324 | int LODIndex = 0; 325 | 326 | bool bBuildStatus = true; 327 | 328 | // Make sure rendering is done - so we are not changing data being used by collision drawing. 329 | FlushRenderingCommands(); 330 | 331 | if (MeshNameArray.Num() == 0) 332 | { 333 | return NULL; 334 | } 335 | 336 | Parent = InParent; 337 | 338 | FString MeshName = ObjectTools::SanitizeObjectName(InName.ToString()); 339 | 340 | // Parent package to place new meshes 341 | UPackage* Package = NULL; 342 | 343 | // create empty mesh 344 | UStaticMesh* StaticMesh = NULL; 345 | 346 | UStaticMesh* ExistingMesh = NULL; 347 | UObject* ExistingObject = NULL; 348 | 349 | // A mapping of vertex positions to their color in the existing static mesh 350 | TMap ExistingVertexColorData; 351 | 352 | FString NewPackageName; 353 | 354 | if( InStaticMesh == NULL || LODIndex == 0 ) 355 | { 356 | // Create a package for each mesh 357 | NewPackageName = FPackageName::GetLongPackagePath(Parent->GetOutermost()->GetName()) + TEXT("/") + MeshName; 358 | NewPackageName = PackageTools::SanitizePackageName(NewPackageName); 359 | Package = CreatePackage(NULL, *NewPackageName); 360 | 361 | ExistingMesh = FindObject( Package, *MeshName ); 362 | ExistingObject = FindObject( Package, *MeshName ); 363 | } 364 | 365 | if (ExistingMesh) 366 | { 367 | // Free any RHI resources for existing mesh before we re-create in place. 368 | ExistingMesh->PreEditChange(NULL); 369 | } 370 | else if (ExistingObject) 371 | { 372 | // Replacing an object. Here we go! 373 | // Delete the existing object 374 | bool bDeleteSucceeded = ObjectTools::DeleteSingleObject( ExistingObject ); 375 | 376 | if (bDeleteSucceeded) 377 | { 378 | // Force GC so we can cleanly create a new asset (and not do an 'in place' replacement) 379 | CollectGarbage( GARBAGE_COLLECTION_KEEPFLAGS ); 380 | 381 | // Create a package for each mesh 382 | Package = CreatePackage(NULL, *NewPackageName); 383 | 384 | // Require the parent because it will have been invalidated from the garbage collection 385 | Parent = Package; 386 | } 387 | else 388 | { 389 | // failed to delete 390 | AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(FText::FromString(FString("{0} wasn't created.\n\nThe asset is referenced by other content.")), FText::FromString(MeshName))), FFbxErrors::Generic_CannotDeleteReferenced); 391 | return NULL; 392 | } 393 | 394 | } 395 | 396 | if( InStaticMesh != NULL && LODIndex > 0 ) 397 | { 398 | StaticMesh = InStaticMesh; 399 | } 400 | else 401 | { 402 | StaticMesh = NewObject(Package, FName(*MeshName), Flags | RF_Public); 403 | } 404 | 405 | if (StaticMesh->SourceModels.Num() < LODIndex+1) 406 | { 407 | // Add one LOD 408 | new(StaticMesh->SourceModels) FStaticMeshSourceModel(); 409 | 410 | if (StaticMesh->SourceModels.Num() < LODIndex+1) 411 | { 412 | LODIndex = StaticMesh->SourceModels.Num() - 1; 413 | } 414 | } 415 | FStaticMeshSourceModel& SrcModel = StaticMesh->SourceModels[LODIndex]; 416 | if( InStaticMesh != NULL && LODIndex > 0 && !SrcModel.RawMeshBulkData->IsEmpty() ) 417 | { 418 | // clear out the old mesh data 419 | FRawMesh EmptyRawMesh; 420 | SrcModel.RawMeshBulkData->SaveRawMesh( EmptyRawMesh ); 421 | } 422 | 423 | // make sure it has a new lighting guid 424 | StaticMesh->LightingGuid = FGuid::NewGuid(); 425 | 426 | // Set it to use textured lightmaps. Note that Build Lighting will do the error-checking (texcoordindex exists for all LODs, etc). 427 | StaticMesh->LightMapResolution = 64; 428 | StaticMesh->LightMapCoordinateIndex = 1; 429 | 430 | FRawMesh NewRawMesh; 431 | SrcModel.RawMeshBulkData->LoadRawMesh(NewRawMesh); 432 | 433 | for (auto Name : MeshNameArray) 434 | { 435 | tinygltf::Mesh* Mesh = &Scene->meshes[ToStdString(Name)]; 436 | 437 | if (Mesh) 438 | { 439 | for (auto Prim : Mesh->primitives) 440 | { 441 | MeshMaterials.AddUnique(ToFString(Prim.material)); 442 | } 443 | } 444 | } 445 | 446 | for (auto Name : MeshNameArray) 447 | { 448 | tinygltf::Mesh* Mesh = &Scene->meshes[ToStdString(Name)]; 449 | 450 | if (Mesh) 451 | { 452 | if (!BuildStaticMeshFromGeometry(Mesh, StaticMesh, LODIndex, NewRawMesh)) 453 | { 454 | bBuildStatus = false; 455 | break; 456 | } 457 | } 458 | } 459 | 460 | // Store the new raw mesh. 461 | SrcModel.RawMeshBulkData->SaveRawMesh(NewRawMesh); 462 | 463 | 464 | if (bBuildStatus) 465 | { 466 | // Compress the materials array by removing any duplicates. 467 | bool bDoRemap = false; 468 | TArray MaterialMap; 469 | TArray UniqueMaterials; 470 | for (int32 MaterialIndex = 0; MaterialIndex < MeshMaterials.Num(); ++MaterialIndex) 471 | { 472 | bool bUnique = true; 473 | for (int32 OtherMaterialIndex = MaterialIndex - 1; OtherMaterialIndex >= 0; --OtherMaterialIndex) 474 | { 475 | if (MeshMaterials[MaterialIndex] == MeshMaterials[OtherMaterialIndex]) 476 | { 477 | int32 UniqueIndex = MaterialMap[OtherMaterialIndex]; 478 | 479 | MaterialMap.Add(UniqueIndex); 480 | bDoRemap = true; 481 | bUnique = false; 482 | break; 483 | } 484 | } 485 | if (bUnique) 486 | { 487 | int32 UniqueIndex = UniqueMaterials.Add(&Scene->materials[ToStdString(MeshMaterials[MaterialIndex])]); 488 | 489 | MaterialMap.Add( UniqueIndex ); 490 | } 491 | } 492 | 493 | if (UniqueMaterials.Num() > 8) 494 | { 495 | AddTokenizedErrorMessage( 496 | FTokenizedMessage::Create( 497 | EMessageSeverity::Warning, 498 | FText::Format(FText::FromString(FString("StaticMesh has a large number({1}) of materials and may render inefficently. Consider breaking up the mesh into multiple Static Mesh Assets.")), 499 | FText::AsNumber(UniqueMaterials.Num()) 500 | )), 501 | FFbxErrors::StaticMesh_TooManyMaterials); 502 | } 503 | 504 | // Sort materials based on _SkinXX in the name. 505 | TArray SortedMaterialIndex; 506 | for (int32 MaterialIndex = 0; MaterialIndex < MeshMaterials.Num(); ++MaterialIndex) 507 | { 508 | int32 SkinIndex = 0xffff; 509 | int32 RemappedIndex = MaterialMap[MaterialIndex]; 510 | if (!SortedMaterialIndex.IsValidIndex(RemappedIndex)) 511 | { 512 | FString GLTFMatName = MeshMaterials[RemappedIndex]; 513 | 514 | int32 Offset = GLTFMatName.Find(TEXT("_SKIN"), ESearchCase::IgnoreCase, ESearchDir::FromEnd); 515 | if (Offset != INDEX_NONE) 516 | { 517 | // Chop off the material name so we are left with the number in _SKINXX 518 | FString SkinXXNumber = GLTFMatName.Right(GLTFMatName.Len() - (Offset + 1)).RightChop(4); 519 | 520 | if (SkinXXNumber.IsNumeric()) 521 | { 522 | SkinIndex = FPlatformString::Atoi( *SkinXXNumber ); 523 | bDoRemap = true; 524 | } 525 | } 526 | 527 | SortedMaterialIndex.Add(((uint32)SkinIndex << 16) | ((uint32)RemappedIndex & 0xffff)); 528 | } 529 | } 530 | SortedMaterialIndex.Sort(); 531 | 532 | TArray SortedMaterials; 533 | for (int32 SortedIndex = 0; SortedIndex < SortedMaterialIndex.Num(); ++SortedIndex) 534 | { 535 | int32 RemappedIndex = SortedMaterialIndex[SortedIndex] & 0xffff; 536 | SortedMaterials.Add(UniqueMaterials[RemappedIndex]); 537 | } 538 | for (int32 MaterialIndex = 0; MaterialIndex < MaterialMap.Num(); ++MaterialIndex) 539 | { 540 | for (int32 SortedIndex = 0; SortedIndex < SortedMaterialIndex.Num(); ++SortedIndex) 541 | { 542 | int32 RemappedIndex = SortedMaterialIndex[SortedIndex] & 0xffff; 543 | if (MaterialMap[MaterialIndex] == RemappedIndex) 544 | { 545 | MaterialMap[MaterialIndex] = SortedIndex; 546 | break; 547 | } 548 | } 549 | } 550 | 551 | // Remap material indices. 552 | int32 MaxMaterialIndex = 0; 553 | int32 FirstOpenUVChannel = 1; 554 | { 555 | FRawMesh LocalRawMesh; 556 | SrcModel.RawMeshBulkData->LoadRawMesh(LocalRawMesh); 557 | 558 | if (bDoRemap) 559 | { 560 | for (int32 TriIndex = 0; TriIndex < LocalRawMesh.FaceMaterialIndices.Num(); ++TriIndex) 561 | { 562 | LocalRawMesh.FaceMaterialIndices[TriIndex] = MaterialMap[LocalRawMesh.FaceMaterialIndices[TriIndex]]; 563 | } 564 | } 565 | 566 | // Compact material indices so that we won't have any sections with zero triangles. 567 | LocalRawMesh.CompactMaterialIndices(); 568 | 569 | // Also compact the sorted materials array. 570 | if (LocalRawMesh.MaterialIndexToImportIndex.Num() > 0) 571 | { 572 | TArray OldSortedMaterials; 573 | 574 | Exchange(OldSortedMaterials,SortedMaterials); 575 | SortedMaterials.Empty(LocalRawMesh.MaterialIndexToImportIndex.Num()); 576 | for (int32 MaterialIndex = 0; MaterialIndex < LocalRawMesh.MaterialIndexToImportIndex.Num(); ++MaterialIndex) 577 | { 578 | tinygltf::Material* Material; 579 | int32 ImportIndex = LocalRawMesh.MaterialIndexToImportIndex[MaterialIndex]; 580 | if (OldSortedMaterials.IsValidIndex(ImportIndex)) 581 | { 582 | Material = OldSortedMaterials[ImportIndex]; 583 | } 584 | SortedMaterials.Add(Material); 585 | } 586 | } 587 | 588 | for (int32 TriIndex = 0; TriIndex < LocalRawMesh.FaceMaterialIndices.Num(); ++TriIndex) 589 | { 590 | MaxMaterialIndex = FMath::Max(MaxMaterialIndex,LocalRawMesh.FaceMaterialIndices[TriIndex]); 591 | } 592 | 593 | for( int32 i = 0; i < MAX_MESH_TEXTURE_COORDS; i++ ) 594 | { 595 | if( LocalRawMesh.WedgeTexCoords[i].Num() == 0 ) 596 | { 597 | FirstOpenUVChannel = i; 598 | break; 599 | } 600 | } 601 | 602 | SrcModel.RawMeshBulkData->SaveRawMesh(LocalRawMesh); 603 | } 604 | 605 | // Setup per-section info and the materials array. 606 | if (LODIndex == 0) 607 | { 608 | StaticMesh->Materials.Empty(); 609 | } 610 | 611 | // Build a new map of sections with the unique material set 612 | FMeshSectionInfoMap NewMap; 613 | int32 NumMaterials = FMath::Min(SortedMaterials.Num(),MaxMaterialIndex+1); 614 | for (int32 MaterialIndex = 0; MaterialIndex < NumMaterials; ++MaterialIndex) 615 | { 616 | FMeshSectionInfo Info = StaticMesh->SectionInfoMap.Get(LODIndex, MaterialIndex); 617 | 618 | int32 Index = StaticMesh->Materials.Add(ToUMaterial(SortedMaterials[MaterialIndex])); 619 | 620 | Info.MaterialIndex = Index; 621 | NewMap.Set( LODIndex, MaterialIndex, Info); 622 | } 623 | 624 | // Copy the final section map into the static mesh 625 | StaticMesh->SectionInfoMap.Clear(); 626 | StaticMesh->SectionInfoMap.CopyFrom(NewMap); 627 | 628 | FRawMesh LocalRawMesh; 629 | SrcModel.RawMeshBulkData->LoadRawMesh(LocalRawMesh); 630 | 631 | // Setup default LOD settings based on the selected LOD group. 632 | if (ExistingMesh == NULL && LODIndex == 0) 633 | { 634 | ITargetPlatform* CurrentPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); 635 | check(CurrentPlatform); 636 | const FStaticMeshLODGroup& LODGroup = CurrentPlatform->GetStaticMeshLODSettings().GetLODGroup(ImportOptions.StaticMeshLODGroup); 637 | int32 NumLODs = LODGroup.GetDefaultNumLODs(); 638 | while (StaticMesh->SourceModels.Num() < NumLODs) 639 | { 640 | new (StaticMesh->SourceModels) FStaticMeshSourceModel(); 641 | } 642 | for (int32 ModelLODIndex = 0; ModelLODIndex < NumLODs; ++ModelLODIndex) 643 | { 644 | StaticMesh->SourceModels[ModelLODIndex].ReductionSettings = LODGroup.GetDefaultSettings(ModelLODIndex); 645 | } 646 | StaticMesh->LightMapResolution = LODGroup.GetDefaultLightMapResolution(); 647 | } 648 | 649 | // @todo This overrides restored values currently but we need to be able to import over the existing settings if the user chose to do so. 650 | SrcModel.BuildSettings.bRemoveDegenerates = ImportOptions.bRemoveDegenerates; 651 | SrcModel.BuildSettings.bBuildAdjacencyBuffer = ImportOptions.bBuildAdjacencyBuffer; 652 | SrcModel.BuildSettings.bRecomputeNormals = LocalRawMesh.WedgeTangentZ.Num() == 0; 653 | SrcModel.BuildSettings.bRecomputeTangents = LocalRawMesh.WedgeTangentX.Num() == 0 || LocalRawMesh.WedgeTangentY.Num() == 0; 654 | SrcModel.BuildSettings.bUseMikkTSpace = false; 655 | if( ImportOptions.bGenerateLightmapUVs ) 656 | { 657 | SrcModel.BuildSettings.bGenerateLightmapUVs = true; 658 | SrcModel.BuildSettings.DstLightmapIndex = FirstOpenUVChannel; 659 | StaticMesh->LightMapCoordinateIndex = FirstOpenUVChannel; 660 | } 661 | else 662 | { 663 | SrcModel.BuildSettings.bGenerateLightmapUVs = false; 664 | } 665 | 666 | TArray BuildErrors; 667 | StaticMesh->LODGroup = ImportOptions.StaticMeshLODGroup; 668 | StaticMesh->Build(false, &BuildErrors); 669 | 670 | for( FText& Error : BuildErrors ) 671 | { 672 | AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, Error), FFbxErrors::StaticMesh_BuildError ); 673 | } 674 | 675 | // this is damage control. After build, we'd like to absolutely sure that 676 | // all index is pointing correctly and they're all used. Otherwise we remove them 677 | FMeshSectionInfoMap OldSectionInfoMap = StaticMesh->SectionInfoMap; 678 | StaticMesh->SectionInfoMap.Clear(); 679 | // fix up section data 680 | for (int32 LODResourceIndex = 0; LODResourceIndex < StaticMesh->RenderData->LODResources.Num(); ++LODResourceIndex) 681 | { 682 | FStaticMeshLODResources& LOD = StaticMesh->RenderData->LODResources[LODResourceIndex]; 683 | int32 NumSections = LOD.Sections.Num(); 684 | for(int32 SectionIndex = 0; SectionIndex < NumSections; ++SectionIndex) 685 | { 686 | FMeshSectionInfo Info = OldSectionInfoMap.Get(LODResourceIndex, SectionIndex); 687 | if (StaticMesh->Materials.IsValidIndex(Info.MaterialIndex)) 688 | { 689 | StaticMesh->SectionInfoMap.Set(LODResourceIndex, SectionIndex, Info); 690 | } 691 | } 692 | } 693 | } 694 | else 695 | { 696 | StaticMesh = NULL; 697 | } 698 | 699 | return StaticMesh; 700 | } 701 | 702 | // Reverse the winding order for triangle indices 703 | template 704 | void GLTFMeshBuilder::ReverseTriDirection(TArray& OutArray) 705 | { 706 | for (int i = 0; i < OutArray.Num() - 2; i += 3) 707 | { 708 | T Temp = OutArray[i]; 709 | OutArray[i] = OutArray[i + 2]; 710 | OutArray[i + 2] = Temp; 711 | } 712 | } 713 | 714 | bool GLTFMeshBuilder::BuildStaticMeshFromGeometry(tinygltf::Mesh* Mesh, UStaticMesh* StaticMesh, int LODIndex, FRawMesh& RawMesh) 715 | { 716 | check(StaticMesh->SourceModels.IsValidIndex(LODIndex)); 717 | 718 | auto ImportOptions = FGLTFLoaderModule::ImportOptions; 719 | 720 | tinygltf::Node* Node = GetMeshParentNode(Mesh); 721 | FStaticMeshSourceModel& SrcModel = StaticMesh->SourceModels[LODIndex]; 722 | 723 | tinygltf::Primitive* BaseLayer = &Mesh->primitives[0]; 724 | if (BaseLayer == NULL) 725 | { 726 | AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(FText::FromString(FString("Error_NoGeometryInMesh", "There is no geometry information in mesh '{0}'")), FText::FromString(ToFString(Mesh->name)))), FFbxErrors::Generic_Mesh_NoGeometry); 727 | return false; 728 | } 729 | 730 | // 731 | // create materials 732 | // 733 | 734 | TArray FoundMaterials; 735 | for (auto Prim : Mesh->primitives) 736 | { 737 | tinygltf::Material* CurrentMaterial = &Scene->materials[Prim.material]; 738 | FoundMaterials.AddUnique(CurrentMaterial); 739 | } 740 | 741 | TArray Materials; 742 | if (ImportOptions.bImportMaterials) 743 | { 744 | Materials.Add(UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface)); 745 | } 746 | else if (ImportOptions.bImportTextures) 747 | { 748 | ImportTexturesFromNode(Node); 749 | } 750 | 751 | // Used later to offset the material indices on the raw triangle data 752 | int32 MaterialIndexOffset = MeshMaterials.Num(); 753 | 754 | for (int32 MaterialIndex = 0; MaterialIndex < FoundMaterials.Num(); MaterialIndex++) 755 | { 756 | MaterialPair NewMaterialPair;// = new(MeshMaterials)FFbxMaterial; 757 | tinygltf::Material* GLTFMaterial = FoundMaterials[MaterialIndex]; 758 | NewMaterialPair.GLTFMaterial = GLTFMaterial; 759 | if (ImportOptions.bImportMaterials) 760 | { 761 | NewMaterialPair.Material = Materials[MaterialIndex]; 762 | } 763 | else 764 | { 765 | FString MaterialFullName = ObjectTools::SanitizeObjectName(ToFString(GLTFMaterial->name)); 766 | FString BasePackageName = PackageTools::SanitizePackageName(FPackageName::GetLongPackagePath(StaticMesh->GetOutermost()->GetName()) / MaterialFullName); 767 | UMaterialInterface* UnrealMaterialInterface = FindObject(NULL, *(BasePackageName + TEXT(".") + MaterialFullName)); 768 | if (UnrealMaterialInterface == NULL) 769 | { 770 | UnrealMaterialInterface = UMaterial::GetDefaultMaterial(MD_Surface); 771 | } 772 | NewMaterialPair.Material = UnrealMaterialInterface; 773 | } 774 | } 775 | 776 | if (FoundMaterials.Num() == 0) 777 | { 778 | UMaterial* DefaultMaterial = UMaterial::GetDefaultMaterial(MD_Surface); 779 | check(DefaultMaterial); 780 | MaterialPair NewMaterial; 781 | NewMaterial.Material = DefaultMaterial; 782 | NewMaterial.GLTFMaterial = NULL; 783 | FoundMaterials.AddDefaulted(1); 784 | } 785 | 786 | // Smoothing is already included in the format 787 | bool bSmoothingAvailable = true; 788 | 789 | // TODO: Collisions 790 | // 791 | // build collision 792 | // 793 | bool bImportedCollision = false; 794 | 795 | bool bEnableCollision = bImportedCollision || (/*GBuildStaticMeshCollision*/true && LODIndex == 0 && ImportOptions.bRemoveDegenerates); 796 | for (int32 SectionIndex = MaterialIndexOffset; SectionIndexSectionInfoMap.Get(LODIndex, SectionIndex); 799 | Info.bEnableCollision = bEnableCollision; 800 | StaticMesh->SectionInfoMap.Set(LODIndex, SectionIndex, Info); 801 | } 802 | 803 | // 804 | // build un-mesh triangles 805 | // 806 | 807 | // Construct the matrices for the conversion from right handed to left handed system 808 | FMatrix TotalMatrix; 809 | FMatrix TotalMatrixForNormal; 810 | TotalMatrix = GetNodeTransform(GetMeshParentNode(Mesh)); 811 | FTransform ImportTransform(ImportOptions.ImportRotation.Quaternion(), ImportOptions.ImportTranslation, FVector(ImportOptions.ImportUniformScale)); 812 | FMatrix ImportMatrix = ImportTransform.ToMatrixWithScale(); 813 | TotalMatrix = TotalMatrix * ImportMatrix; 814 | 815 | if (ImportOptions.bCorrectUpDirection) 816 | { 817 | FTransform Temp(FRotator(0.0f, 0.0f, -90.0f)); 818 | TotalMatrix = TotalMatrix * Temp.ToMatrixWithScale(); 819 | } 820 | 821 | TotalMatrixForNormal = TotalMatrix.Inverse(); 822 | TotalMatrixForNormal = TotalMatrixForNormal.GetTransposed(); 823 | 824 | // Whether an odd number of axes have negative scale 825 | bool OddNegativeScale = (TotalMatrix.M[0][0] * TotalMatrix.M[1][1] * TotalMatrix.M[2][2]) < 0; 826 | 827 | // Copy the actual data! 828 | // Vertex Positions 829 | TArray NewVertexPositions; 830 | if (!ConvertAttrib(NewVertexPositions, Mesh, std::string("POSITION"), false)) 831 | { 832 | AddTokenizedErrorMessage( 833 | FTokenizedMessage::Create( 834 | EMessageSeverity::Error, 835 | FText::FromString(FString("Could not obtain position data."))), 836 | FFbxErrors::Generic_Mesh_LOD_InvalidIndex); 837 | return false; 838 | } 839 | for (auto& Vertex : NewVertexPositions) 840 | { 841 | FVector4 Vert4(Vertex); 842 | Vertex = TotalMatrix.TransformFVector4(Vert4); 843 | } 844 | int32 VertexCount = NewVertexPositions.Num(); 845 | 846 | // Triangle indices 847 | TArray NewWedgeIndices; 848 | ConvertAttrib(NewWedgeIndices, Mesh, std::string("__WedgeIndices")); 849 | int32 WedgeCount = NewWedgeIndices.Num(); 850 | int32 TriangleCount = WedgeCount / 3; 851 | if (TriangleCount == 0) 852 | { 853 | AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(FText::FromString(FString("Error_NoTrianglesFoundInMesh", "No triangles were found on mesh '{0}'")), FText::FromString(ToFString(Mesh->name)))), FFbxErrors::StaticMesh_NoTriangles); 854 | return false; 855 | } 856 | 857 | // Normals 858 | TArray NewWedgeTangentX, NewWedgeTangentY, NewWedgeTangentZ; 859 | bool HasNormals = ConvertAttrib(NewWedgeTangentZ, Mesh, "NORMAL"); 860 | ConvertAttrib(NewWedgeTangentY, Mesh, "TANGENT"); 861 | ConvertAttrib(NewWedgeTangentX, Mesh, "BINORMAL"); 862 | if (!HasNormals) 863 | { 864 | AddTokenizedErrorMessage( 865 | FTokenizedMessage::Create( 866 | EMessageSeverity::Warning, 867 | FText::FromString(FString("Could not obtain data for normals; they will be recalculated but the model will lack smoothing data."))), 868 | FFbxErrors::Generic_Mesh_LOD_InvalidIndex); 869 | } 870 | for (auto& Normal : NewWedgeTangentZ) 871 | { 872 | Normal = TotalMatrixForNormal.TransformVector(Normal); 873 | } 874 | 875 | // UVs 876 | TArray NewWedgeTexCoords[MAX_MESH_TEXTURE_COORDS]; 877 | bool bHasUVs = false; 878 | for (int i = 0; i < MAX_MESH_TEXTURE_COORDS; ++i) 879 | { 880 | bHasUVs |= ConvertAttrib(NewWedgeTexCoords[i], Mesh, std::string("TEXCOORD_") + std::to_string(i)); 881 | } 882 | if (!bHasUVs) 883 | { 884 | AddTokenizedErrorMessage( 885 | FTokenizedMessage::Create( 886 | EMessageSeverity::Warning, 887 | FText::FromString(FString("Could not obtain UV data."))), 888 | FFbxErrors::Generic_Mesh_LOD_InvalidIndex); 889 | } 890 | 891 | TArray NewWedgeColors; 892 | ConvertAttrib(NewWedgeColors, Mesh, std::string("COLOR")); // No need to check for errors 893 | 894 | // Reverse the triangle winding order since glTF uses the opposite to Unreal 895 | // Except if the model has negative scale on an odd number of axes, which will effectively do it for us 896 | if (!OddNegativeScale) 897 | { 898 | ReverseTriDirection(NewWedgeIndices); 899 | ReverseTriDirection(NewWedgeColors); 900 | ReverseTriDirection(NewWedgeTangentX); 901 | ReverseTriDirection(NewWedgeTangentY); 902 | ReverseTriDirection(NewWedgeTangentZ); 903 | for (int i = 0; i < MAX_MESH_TEXTURE_COORDS; ++i) 904 | { 905 | ReverseTriDirection(NewWedgeTexCoords[i]); 906 | } 907 | } 908 | 909 | TArray NewFaceMaterialIndices; 910 | GetMaterialIndices(NewFaceMaterialIndices, (*Mesh)); 911 | 912 | TArray NewFaceSmoothingMasks; // Don't need to do anything with this since smoothing information is included implicitly in glTF 913 | 914 | // Force attribute arrays to the correct size, otherwise it complains 915 | NewFaceMaterialIndices.SetNumZeroed(TriangleCount); 916 | NewFaceSmoothingMasks.SetNumZeroed(TriangleCount); 917 | NewWedgeColors.SetNumZeroed(WedgeCount); 918 | NewWedgeTangentX.SetNumZeroed(WedgeCount); 919 | NewWedgeTangentY.SetNumZeroed(WedgeCount); 920 | NewWedgeTangentZ.SetNumZeroed(WedgeCount); 921 | for (int32 i = 0; i < MAX_MESH_TEXTURE_COORDS; ++i) 922 | { 923 | NewWedgeTexCoords[i].SetNumZeroed(WedgeCount); 924 | } 925 | 926 | // Add the new data to the raw mesh 927 | RawMesh.VertexPositions.Append(NewVertexPositions); 928 | RawMesh.WedgeIndices.Append(NewWedgeIndices); 929 | RawMesh.FaceMaterialIndices.Append(NewFaceMaterialIndices); 930 | RawMesh.FaceSmoothingMasks.Append(NewFaceSmoothingMasks); 931 | RawMesh.WedgeColors.Append(NewWedgeColors); 932 | RawMesh.WedgeTangentX.Append(NewWedgeTangentX); 933 | RawMesh.WedgeTangentY.Append(NewWedgeTangentY); 934 | RawMesh.WedgeTangentZ.Append(NewWedgeTangentZ); 935 | for (int32 i = 0; i < MAX_MESH_TEXTURE_COORDS; ++i) 936 | { 937 | RawMesh.WedgeTexCoords[i].Append(NewWedgeTexCoords[i]); 938 | } 939 | 940 | return true; 941 | } 942 | 943 | UMaterialInterface* GLTFMeshBuilder::ToUMaterial(tinygltf::Material* Material) 944 | { 945 | return UMaterial::GetDefaultMaterial(MD_Surface); 946 | } 947 | 948 | FString GLTFMeshBuilder::GetError() 949 | { 950 | return Error; 951 | } 952 | 953 | FString GLTFMeshBuilder::ToFString(std::string InString) 954 | { 955 | return FString(InString.c_str()); 956 | } 957 | 958 | std::string GLTFMeshBuilder::ToStdString(FString InString) 959 | { 960 | auto CharArray = InString.GetCharArray(); 961 | 962 | std::wstring WideString(&CharArray[0]); 963 | std::wstring_convert< std::codecvt_utf8 > Convert; 964 | return Convert.to_bytes(WideString); 965 | } 966 | 967 | template 968 | bool GLTFMeshBuilder::GetBufferData(TArray &OutArray, tinygltf::Accessor* Accessor, bool Append) 969 | { 970 | if (Accessor->type != TINYGLTF_TYPE_SCALAR) 971 | { 972 | return false; 973 | } 974 | 975 | if (!Accessor) 976 | { 977 | return false; 978 | } 979 | 980 | tinygltf::BufferView* BufferView = &Scene->bufferViews[Accessor->bufferView]; 981 | 982 | if (!BufferView) 983 | { 984 | return false; 985 | } 986 | 987 | tinygltf::Buffer* Buffer = &Scene->buffers[BufferView->buffer]; 988 | 989 | if (!Buffer) 990 | { 991 | return false; 992 | } 993 | 994 | if (!Append) 995 | { 996 | OutArray.Empty(); 997 | } 998 | 999 | size_t Stride; 1000 | if (Accessor->byteStride != 0) 1001 | { 1002 | Stride = Accessor->byteStride; 1003 | } 1004 | else 1005 | { 1006 | Stride = TypeSize(Accessor->componentType); 1007 | } 1008 | 1009 | unsigned char* Start = &Buffer->data[Accessor->byteOffset + BufferView->byteOffset]; 1010 | 1011 | switch (Accessor->componentType) 1012 | { 1013 | case TINYGLTF_COMPONENT_TYPE_BYTE: BufferCopy ::Type, T>(OutArray, Start, Accessor->count, Stride); break; 1014 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: BufferCopy ::Type, T>(OutArray, Start, Accessor->count, Stride); break; 1015 | case TINYGLTF_COMPONENT_TYPE_SHORT: BufferCopy ::Type, T>(OutArray, Start, Accessor->count, Stride); break; 1016 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: BufferCopy ::Type, T>(OutArray, Start, Accessor->count, Stride); break; 1017 | case TINYGLTF_COMPONENT_TYPE_INT: BufferCopy ::Type, T>(OutArray, Start, Accessor->count, Stride); break; 1018 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: BufferCopy ::Type, T>(OutArray, Start, Accessor->count, Stride); break; 1019 | case TINYGLTF_COMPONENT_TYPE_FLOAT: BufferCopy ::Type, T>(OutArray, Start, Accessor->count, Stride); break; 1020 | case TINYGLTF_COMPONENT_TYPE_DOUBLE: BufferCopy ::Type, T>(OutArray, Start, Accessor->count, Stride); break; 1021 | default: BufferCopy(OutArray, Start, Accessor->count, Stride); break; 1022 | } 1023 | 1024 | return true; 1025 | } 1026 | 1027 | // This specialization is almost the same, but checks for the VEC2 GLTF type and uses the overloaded BufferCopy function for FVector2D 1028 | template <> 1029 | bool GLTFMeshBuilder::GetBufferData(TArray &OutArray, tinygltf::Accessor* Accessor, bool Append) 1030 | { 1031 | if (Accessor->type != TINYGLTF_TYPE_VEC2) 1032 | { 1033 | return false; 1034 | } 1035 | 1036 | if (!Accessor) 1037 | { 1038 | return false; 1039 | } 1040 | 1041 | tinygltf::BufferView* BufferView = &Scene->bufferViews[Accessor->bufferView]; 1042 | 1043 | if (!BufferView) 1044 | { 1045 | return false; 1046 | } 1047 | 1048 | tinygltf::Buffer* Buffer = &Scene->buffers[BufferView->buffer]; 1049 | 1050 | if (!Buffer) 1051 | { 1052 | return false; 1053 | } 1054 | 1055 | if (!Append) 1056 | { 1057 | OutArray.Empty(); 1058 | } 1059 | 1060 | size_t Stride; 1061 | if (Accessor->byteStride != 0) 1062 | { 1063 | Stride = Accessor->byteStride; 1064 | } 1065 | else 1066 | { 1067 | Stride = TypeSize(Accessor->componentType); 1068 | } 1069 | 1070 | unsigned char* Start = &Buffer->data[Accessor->byteOffset + BufferView->byteOffset]; 1071 | 1072 | switch (Accessor->componentType) 1073 | { 1074 | case TINYGLTF_COMPONENT_TYPE_BYTE: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1075 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1076 | case TINYGLTF_COMPONENT_TYPE_SHORT: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1077 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1078 | case TINYGLTF_COMPONENT_TYPE_INT: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1079 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1080 | case TINYGLTF_COMPONENT_TYPE_FLOAT: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1081 | case TINYGLTF_COMPONENT_TYPE_DOUBLE: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1082 | default: return false; 1083 | } 1084 | 1085 | return true; 1086 | } 1087 | 1088 | // This specialization is almost the same, but checks for the VEC3 GLTF type and uses the overloaded BufferCopy function for FVector 1089 | template <> 1090 | bool GLTFMeshBuilder::GetBufferData(TArray &OutArray, tinygltf::Accessor* Accessor, bool Append) 1091 | { 1092 | if (Accessor->type != TINYGLTF_TYPE_VEC3) 1093 | { 1094 | return false; 1095 | } 1096 | 1097 | if (!Accessor) 1098 | { 1099 | return false; 1100 | } 1101 | 1102 | tinygltf::BufferView* BufferView = &Scene->bufferViews[Accessor->bufferView]; 1103 | 1104 | if (!BufferView) 1105 | { 1106 | return false; 1107 | } 1108 | 1109 | tinygltf::Buffer* Buffer = &Scene->buffers[BufferView->buffer]; 1110 | 1111 | if (!Buffer) 1112 | { 1113 | return false; 1114 | } 1115 | 1116 | if (!Append) 1117 | { 1118 | OutArray.Empty(); 1119 | } 1120 | 1121 | size_t Stride; 1122 | if (Accessor->byteStride != 0) 1123 | { 1124 | Stride = Accessor->byteStride; 1125 | } 1126 | else 1127 | { 1128 | Stride = TypeSize(Accessor->componentType); 1129 | } 1130 | 1131 | unsigned char* Start = &Buffer->data[Accessor->byteOffset + BufferView->byteOffset]; 1132 | 1133 | switch (Accessor->componentType) 1134 | { 1135 | case TINYGLTF_COMPONENT_TYPE_BYTE: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1136 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1137 | case TINYGLTF_COMPONENT_TYPE_SHORT: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1138 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1139 | case TINYGLTF_COMPONENT_TYPE_INT: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1140 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1141 | case TINYGLTF_COMPONENT_TYPE_FLOAT: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1142 | case TINYGLTF_COMPONENT_TYPE_DOUBLE: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1143 | default: return false; 1144 | } 1145 | 1146 | return true; 1147 | } 1148 | 1149 | // This specialization is almost the same, but checks for the VEC4 GLTF type and uses the overloaded BufferCopy function for FVector4 1150 | template <> 1151 | bool GLTFMeshBuilder::GetBufferData(TArray &OutArray, tinygltf::Accessor* Accessor, bool Append) 1152 | { 1153 | if (Accessor->type != TINYGLTF_TYPE_VEC4) 1154 | { 1155 | return false; 1156 | } 1157 | 1158 | if (!Accessor) 1159 | { 1160 | return false; 1161 | } 1162 | 1163 | tinygltf::BufferView* BufferView = &Scene->bufferViews[Accessor->bufferView]; 1164 | 1165 | if (!BufferView) 1166 | { 1167 | return false; 1168 | } 1169 | 1170 | tinygltf::Buffer* Buffer = &Scene->buffers[BufferView->buffer]; 1171 | 1172 | if (!Buffer) 1173 | { 1174 | return false; 1175 | } 1176 | 1177 | if (!Append) 1178 | { 1179 | OutArray.Empty(); 1180 | } 1181 | 1182 | size_t Stride; 1183 | if (Accessor->byteStride != 0) 1184 | { 1185 | Stride = Accessor->byteStride; 1186 | } 1187 | else 1188 | { 1189 | Stride = TypeSize(Accessor->componentType); 1190 | } 1191 | 1192 | unsigned char* Start = &Buffer->data[Accessor->byteOffset + BufferView->byteOffset]; 1193 | 1194 | switch (Accessor->componentType) 1195 | { 1196 | case TINYGLTF_COMPONENT_TYPE_BYTE: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1197 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1198 | case TINYGLTF_COMPONENT_TYPE_SHORT: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1199 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1200 | case TINYGLTF_COMPONENT_TYPE_INT: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1201 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1202 | case TINYGLTF_COMPONENT_TYPE_FLOAT: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1203 | case TINYGLTF_COMPONENT_TYPE_DOUBLE: BufferCopy ::Type>(OutArray, Start, Accessor->count, Stride); break; 1204 | default: return false; 1205 | } 1206 | 1207 | return true; 1208 | } 1209 | 1210 | // This specialization is almost the same, but checks for a VEC3 or VEC4 GLTF type and uses the overloaded BufferCopy function for FColor 1211 | template <> 1212 | bool GLTFMeshBuilder::GetBufferData(TArray &OutArray, tinygltf::Accessor* Accessor, bool Append) 1213 | { 1214 | if (Accessor->type != TINYGLTF_TYPE_VEC3 && Accessor->type != TINYGLTF_TYPE_VEC4) 1215 | { 1216 | return false; 1217 | } 1218 | 1219 | if (!Accessor) 1220 | { 1221 | return false; 1222 | } 1223 | 1224 | tinygltf::BufferView* BufferView = &Scene->bufferViews[Accessor->bufferView]; 1225 | 1226 | if (!BufferView) 1227 | { 1228 | return false; 1229 | } 1230 | 1231 | tinygltf::Buffer* Buffer = &Scene->buffers[BufferView->buffer]; 1232 | 1233 | if (!Buffer) 1234 | { 1235 | return false; 1236 | } 1237 | 1238 | if (!Append) 1239 | { 1240 | OutArray.Empty(); 1241 | } 1242 | 1243 | size_t Stride; 1244 | if (Accessor->byteStride != 0) 1245 | { 1246 | Stride = Accessor->byteStride; 1247 | } 1248 | else 1249 | { 1250 | Stride = TypeSize(Accessor->componentType); 1251 | } 1252 | 1253 | unsigned char* Start = &Buffer->data[Accessor->byteOffset + BufferView->byteOffset]; 1254 | 1255 | switch (Accessor->componentType) 1256 | { 1257 | case TINYGLTF_COMPONENT_TYPE_BYTE: BufferCopy ::Type>(OutArray, Accessor->type, Start, Accessor->count, Stride); break; 1258 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: BufferCopy ::Type>(OutArray, Accessor->type, Start, Accessor->count, Stride); break; 1259 | case TINYGLTF_COMPONENT_TYPE_SHORT: BufferCopy ::Type>(OutArray, Accessor->type, Start, Accessor->count, Stride); break; 1260 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: BufferCopy ::Type>(OutArray, Accessor->type, Start, Accessor->count, Stride); break; 1261 | case TINYGLTF_COMPONENT_TYPE_INT: BufferCopy ::Type>(OutArray, Accessor->type, Start, Accessor->count, Stride); break; 1262 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: BufferCopy ::Type>(OutArray, Accessor->type, Start, Accessor->count, Stride); break; 1263 | case TINYGLTF_COMPONENT_TYPE_FLOAT: BufferCopy ::Type>(OutArray, Accessor->type, Start, Accessor->count, Stride); break; 1264 | case TINYGLTF_COMPONENT_TYPE_DOUBLE: BufferCopy ::Type>(OutArray, Accessor->type, Start, Accessor->count, Stride); break; 1265 | default: return false; 1266 | } 1267 | 1268 | return true; 1269 | } 1270 | 1271 | bool GLTFMeshBuilder::HasAttribute(tinygltf::Mesh* Mesh, std::string AttribName) const 1272 | { 1273 | for (auto Prim : Mesh->primitives) 1274 | { 1275 | for (auto Entry : Prim.attributes) 1276 | { 1277 | if (Entry.first == AttribName) 1278 | { 1279 | return true; 1280 | } 1281 | } 1282 | } 1283 | return false; 1284 | } 1285 | 1286 | template int32 GLTFMeshBuilder::FindInStdVector(const std::vector &InVector, const T &InElement) const 1287 | { 1288 | for (int32 i = 0; i < InVector.size(); ++i) 1289 | { 1290 | if (InVector[i] == InElement) 1291 | { 1292 | return i; 1293 | } 1294 | } 1295 | 1296 | return -1; 1297 | } 1298 | 1299 | FMatrix GLTFMeshBuilder::GetNodeTransform(tinygltf::Node* Node) 1300 | { 1301 | if (Node->matrix.size() == 16) 1302 | { 1303 | FMatrix Ret; 1304 | for (int32 i = 0; i < 4; ++i) 1305 | { 1306 | for (int32 j = 0; j < 4; ++j) 1307 | { 1308 | // Reverse order since glTF is column major and FMatrix is row major 1309 | Ret.M[j][i] = Node->matrix[(4 * i) + j]; 1310 | } 1311 | } 1312 | return Ret; 1313 | } 1314 | else if (Node->rotation.size() == 4 && Node->scale.size() == 3 && Node->translation.size() == 3) 1315 | { 1316 | FQuat Rotation((float)Node->rotation[0], (float)Node->rotation[1], (float)Node->rotation[2], (float)Node->rotation[3]); 1317 | FVector Scale((float)Node->scale[0], (float)Node->scale[1], (float)Node->scale[2]); 1318 | FVector Translation((float)Node->translation[0], (float)Node->translation[1], (float)Node->translation[2]); 1319 | return FTransform(Rotation, Translation, Scale).ToMatrixWithScale(); 1320 | } 1321 | else 1322 | { 1323 | return FMatrix::Identity; 1324 | } 1325 | } 1326 | 1327 | size_t GLTFMeshBuilder::TypeSize(int Type) const 1328 | { 1329 | switch (Type) 1330 | { 1331 | case TINYGLTF_COMPONENT_TYPE_BYTE: 1332 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: 1333 | return sizeof(GLTFType::Type); 1334 | 1335 | case TINYGLTF_COMPONENT_TYPE_SHORT: 1336 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: 1337 | return sizeof(GLTFType::Type); 1338 | 1339 | case TINYGLTF_COMPONENT_TYPE_INT: 1340 | case TINYGLTF_COMPONENT_TYPE_UNSIGNED_INT: 1341 | return sizeof(GLTFType::Type); 1342 | 1343 | case TINYGLTF_COMPONENT_TYPE_FLOAT: 1344 | return sizeof(GLTFType::Type); 1345 | 1346 | case TINYGLTF_COMPONENT_TYPE_DOUBLE: 1347 | return sizeof(GLTFType::Type); 1348 | 1349 | default: 1350 | return -1; 1351 | } 1352 | } 1353 | 1354 | int32 GLTFMeshBuilder::GetNumWedges(tinygltf::Primitive* Prim) const 1355 | { 1356 | switch (Prim->mode) 1357 | { 1358 | case TINYGLTF_MODE_TRIANGLES: 1359 | return Prim->indices.size(); 1360 | 1361 | case TINYGLTF_MODE_TRIANGLE_STRIP: 1362 | case TINYGLTF_MODE_TRIANGLE_FAN: 1363 | return (Prim->indices.size() - 2) * 3; 1364 | 1365 | default: 1366 | return 0; 1367 | } 1368 | } 1369 | 1370 | void GLTFMeshBuilder::GetMaterialIndices(TArray& OutArray, tinygltf::Mesh& Mesh) 1371 | { 1372 | for (auto Prim : Mesh.primitives) 1373 | { 1374 | int32 Index = MeshMaterials.Find(ToFString(Prim.material)); 1375 | for (int i = 0; i < GetNumWedges(&Prim) / 3; ++i) 1376 | { 1377 | OutArray.Add(Index); 1378 | } 1379 | } 1380 | } 1381 | 1382 | template void GLTFMeshBuilder::BufferCopy(TArray& OutArray, unsigned char* Data, int32 Count, size_t Stride) 1383 | { 1384 | for (int32 i = 0; i < Count; ++i) 1385 | { 1386 | // At this point, assume we can cast directly to the destination type 1387 | OutArray.Add((DstType)BufferValue(Data)); 1388 | Data += Stride; 1389 | } 1390 | } 1391 | 1392 | template void GLTFMeshBuilder::BufferCopy(TArray &OutArray, unsigned char* Data, int32 Count, size_t Stride) 1393 | { 1394 | for (int32 i = 0; i < Count; ++i) 1395 | { 1396 | // At this point, assume we can cast directly to the destination type 1397 | OutArray.Add(FVector2D(BufferValue(Data), BufferValue(Data + sizeof(SrcType)))); 1398 | Data += Stride; 1399 | } 1400 | } 1401 | 1402 | template void GLTFMeshBuilder::BufferCopy(TArray &OutArray, unsigned char* Data, int32 Count, size_t Stride) 1403 | { 1404 | for (int32 i = 0; i < Count; ++i) 1405 | { 1406 | // At this point, assume we can cast directly to the destination type 1407 | OutArray.Add(FVector(BufferValue(Data), BufferValue(Data + sizeof(SrcType)), BufferValue(Data + 2 * sizeof(SrcType)))); 1408 | Data += Stride; 1409 | } 1410 | } 1411 | 1412 | template void GLTFMeshBuilder::BufferCopy(TArray &OutArray, unsigned char* Data, int32 Count, size_t Stride) 1413 | { 1414 | for (int32 i = 0; i < Count; ++i) 1415 | { 1416 | // At this point, assume we can cast directly to the destination type 1417 | OutArray.Add(FVector4(BufferValue(Data), BufferValue(Data + sizeof(SrcType)), BufferValue(Data + 2 * sizeof(SrcType)), BufferValue(Data + 3 * sizeof(SrcType)))); 1418 | Data += Stride; 1419 | } 1420 | } 1421 | 1422 | template void GLTFMeshBuilder::BufferCopy(TArray &OutArray, int InType, unsigned char* Data, int32 Count, size_t Stride) 1423 | { 1424 | switch (InType) 1425 | { 1426 | case TINYGLTF_TYPE_VEC3: 1427 | for (int32 i = 0; i < Count; ++i) 1428 | { 1429 | // At this point, assume we can cast directly to the destination type 1430 | OutArray.Add(FColor(BufferValue(Data), BufferValue(Data + sizeof(SrcType)), BufferValue(Data + 2 * sizeof(SrcType)))); 1431 | Data += Stride; 1432 | } 1433 | break; 1434 | case TINYGLTF_TYPE_VEC4: 1435 | for (int32 i = 0; i < Count; ++i) 1436 | { 1437 | // At this point, assume we can cast directly to the destination type 1438 | OutArray.Add(FColor(BufferValue(Data), BufferValue(Data + sizeof(SrcType)), BufferValue(Data + 2 * sizeof(SrcType)), BufferValue(Data + 3 * sizeof(SrcType)))); 1439 | Data += Stride; 1440 | } 1441 | break; 1442 | default: return; 1443 | } 1444 | } 1445 | 1446 | void GLTFMeshBuilder::AddTokenizedErrorMessage(TSharedRef Error, FName ErrorName) 1447 | { 1448 | UE_LOG(LogTemp, Warning, TEXT("%s"), *(Error->ToText().ToString())); 1449 | } --------------------------------------------------------------------------------