├── HoudiniVersion ├── README.txt ├── doc └── nodes │ ├── obj │ └── gltf_hierarchy.txt │ ├── out │ └── gltf.txt │ └── sop │ └── gltf.txt └── src ├── CustomGLTF.global ├── GLTF ├── GLTF_API.h ├── GLTF_Cache.C ├── GLTF_Cache.h ├── GLTF_GeoLoader.C ├── GLTF_GeoLoader.h ├── GLTF_Loader.C ├── GLTF_Loader.h ├── GLTF_Types.C ├── GLTF_Types.h ├── GLTF_Util.C ├── GLTF_Util.h └── Makefile ├── HOM ├── HOM_GLTF.C └── Makefile ├── Makefile ├── ROP ├── Makefile ├── ROP_GLTF.C ├── ROP_GLTF.h ├── ROP_GLTF_ExportRoot.C ├── ROP_GLTF_ExportRoot.h ├── ROP_GLTF_Image.C ├── ROP_GLTF_Image.h ├── ROP_GLTF_Refiner.C └── ROP_GLTF_Refiner.h ├── SOP ├── Makefile ├── SOP_GLTF.C └── SOP_GLTF.h └── gltf_hierarchy.hda ├── INDEX__SECTION ├── Object_1gltf__hierarchy ├── CreateScript ├── DialogScript ├── ExtraFileOptions ├── Help ├── InternalFileOptions ├── PythonModule ├── Sections.list ├── Tools.shelf └── TypePropertiesOptions ├── Sections.list └── houdini.hdalibrary /HoudiniVersion: -------------------------------------------------------------------------------- 1 | Houdini Version: 18.0.619 2 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | This contains the source code for the glTF importer and exporter for Houdini. 2 | 3 | This project has been setup to produce custom glTF libraries with its own 4 | namespace and node operator names so that it can be used alongside the default 5 | Houdini glTF libarries. 6 | 7 | Optionally, you can modify the custom library namespace and node operator names 8 | in src/CustomGLTF.global. 9 | 10 | This project has been setup to build with make. See 11 | https://www.sidefx.com/docs/hdk/_h_d_k__intro__compiling.html#HDK_Intro_Compiling_Makefiles 12 | 13 | For coding reference, see the Houdini Development Kit: https://www.sidefx.com/docs/hdk/ 14 | 15 | Requires Houdini 18.0.552 or newer. Does not work with earlier versions of Houdini. 16 | 17 | ------------------------------------------------------------------------------- 18 | Libraries: 19 | ------------------------------------------------------------------------------- 20 | 21 | - src/GLTF 22 | Source code for the core glTF import and export library which contains most 23 | of the parsing and writing code. The generated libCustomGLTF.so/.dll should be 24 | placed in a path pointed to by LD_LIBRARY_PATH (Linux), or DYLD_LIBRARY_PATH 25 | (macOS) or PATH (Windows). 26 | 27 | - src/SOP 28 | Source code for the glTF SOP importer node. The generated SOP_CustomGLTF.so/.dll 29 | should be placed in $HOME/houdiniX.X/dso or a path pointed to by HOUDINI_DSO_PATH. 30 | 31 | - src/ROP 32 | Source code for the glTF ROP exporter node. The generated ROP_CustomGLTF.so/.dll 33 | should be placed in $HOME/houdiniX.X/dso or a path pointed to by HOUDINI_DSO_PATH. 34 | 35 | - src/HOM 36 | Source code for the glTF HOM module. The generated HOM_CustomGLTF.so/.dll 37 | should be placed in $HOME/houdiniX.X/dso or a path pointed to by HOUDINI_DSO_PATH. 38 | 39 | - src/gltf_hierarchy.hda 40 | The Object node to load in a glTF scene hierarchy, as a Houdini Digital Asset. 41 | This should be placed $HOME/houdiniX.X/otls. 42 | 43 | ------------------------------------------------------------------------------- 44 | Building the glTF libraries on Windows using Cygwin: 45 | ------------------------------------------------------------------------------- 46 | 47 | To build the glTF libraries on Windows the HFS environment variable needs to be 48 | set to the Houdini install directory, and the MSVCDir environment must be set 49 | to the VC subdirectory of your Visual Studio installation. 50 | 51 | To do so, first open a cygwin shell and go to the Houdini X.Y.ZZZ directory and 52 | source the Houdini environment: 53 | 54 | cd C:/Program\ Files/Side\ Effects\ Software/Houdini\ X.Y.ZZZ 55 | source ./houdini_setup 56 | 57 | Then set the MSVC dir (change to your version of MSVC). 58 | export MSVCDir="C:/Program Files (x86)/Microsoft Visual Studio/2017/Professional/VC/Tools/MSVC/14.16.27023" 59 | 60 | The following steps assume you cloned this repo to C:\HoudiniGLTF 61 | 62 | Go to the HoudiniGLTF\src folder. Then do one of the following: 63 | 64 | To build all libs: make WINDOWS=1 all 65 | 66 | Note that libCustomGLTF.dll must be built first. The other libs depend on it. 67 | 68 | Once the libraries are built, add the location of libCustomGLTF.dll to PATH: 69 | 70 | set PATH=%PATH%;C:\HoudiniGLTF\src\GLTF 71 | 72 | Then copy over the built SOP, ROP, and HOM .dlls to %HOME%/houdiniX.X/dso 73 | (i.e. Houdini home directory). 74 | 75 | Run Houdini and you should see the Custom GLTF SOP and ROP nodes. 76 | 77 | Notes: 78 | 79 | To build a specific lib (such as libCustomGLTF.dll): make WINDOWS=1 gltf 80 | 81 | To clean the built files: make WINDOWS=1 clean 82 | 83 | Troubleshooting: 84 | 85 | If you get compiler errors about Windows libraries such as 'corecrt.h', change 86 | the WIN32_KIT_VERSION entry in Houdini 18.0.532\toolkit\makefiles\Makefile.win 87 | to the one installed on your system in C:\Program Files (x86)\Windows Kits\10\Lib. 88 | For example: WIN32_KIT_VERSION = 10.0.17763.0 89 | 90 | If either the SOP or ROP libraries are not loading or the custom nodes are not 91 | showing up, set HOUDINI_DSO_ERROR=3 environment variable, and launch Houdini. 92 | There should be log entries when these libraries are loaded. 93 | 94 | ------------------------------------------------------------------------------- 95 | Building the glTF libraries on Linux or macOS: 96 | ------------------------------------------------------------------------------- 97 | 98 | First, make sure the HFS environment variable is properly set to your Houdini 99 | install directory. 100 | 101 | Linux: export HFS=/opt/hfsX.Y.ZZZ 102 | macOS: export HFS=/Applications/Houdini/HoudiniX.Y.ZZZ/Frameworks/Houdini.framework/Versions/X.Y/Resources 103 | 104 | The following steps assume you closed this repo to /dev/HoudiniGLTF 105 | 106 | Go to the HoudiniGLTF/src/ folder. And execute the following: 107 | 108 | Linux: make all 109 | macOS: make MBSD=1 all 110 | 111 | Note that libCustomGLTF.so/.dylib must be built first. The other libs depend on it. 112 | 113 | Once the libraries are built, add the location of libCustomGLTF.so/.dylib to LD_LIBRARY_PATH: 114 | 115 | Linux: export LD_LIBRARY_PATH=/dev/HoudiniGLTF/src/GLTF 116 | macOS: export DYLD_LIBRARY_PATH=/dev/HoudiniGLTF/src/GLTF 117 | 118 | Then copy over the built SOP, ROP, and HOM .so/.dylib libs to $HOME/houdiniX.X/dso 119 | (i.e. Houdini home directory). 120 | 121 | On macOS, Houdini home directory is located at ~/Library/Preferences/houdini/X.Y/ 122 | 123 | If either the SOP or ROP libraries are not loading or the custom nodes are not 124 | showing up, set HOUDINI_DSO_ERROR=3 environment variable, and launch Houdini. 125 | There should be log entries when these libraries are loaded. -------------------------------------------------------------------------------- /doc/nodes/obj/gltf_hierarchy.txt: -------------------------------------------------------------------------------- 1 | #type: node 2 | #context: obj 3 | #internal: gltf 4 | #icon: SOP/Geometry 5 | 6 | = glTF = 7 | 8 | The glTF hierarchy importer can import a glTF scene as a network of nodes or as flattened geometry. 9 | See the following link for further information about the [glTF format|https://www.khronos.org/gltf/]. 10 | 11 | @parameters 12 | 13 | File Name: 14 | #id: filename 15 | 16 | The glTF file to. The loading method depends on the extension of the file loaded. 17 | 18 | Files with a .glb extension be treated as binary GLB files. 19 | 20 | Files with a .gltf extension will be loaded as normal JSON data. 21 | 22 | Asset Extraction Folder: 23 | #id: assetfolder 24 | 25 | Textures packed into a glTF binary buffer or represented as base64 data must be unpacked before usage. 26 | 27 | This field specifies the location textures are to be unpacked on disk. 28 | 29 | Scene: 30 | #id: scene 31 | 32 | Select the scene to import from the glTF object. 33 | 34 | In the absence of a scene name, the label will be based off the scene index. 35 | 36 | Build Scene: 37 | #id: buildscene 38 | 39 | Build the scene from the input parameters. If the scene was loaded previously, this will reload the scene hierarchy and geometry. 40 | 41 | Lock Geometry: 42 | #id: lockgeo 43 | 44 | Enable this option to lock any geometry nodes created during scene building. 45 | 46 | Flatten Hierarchy: 47 | #id:flattenhierarchy 48 | 49 | Flatten all nodes in the scene down to a single node containing all the 50 | geometry. 51 | 52 | Promote Point Attributes To Vertex: 53 | #id: promotepointattrs 54 | 55 | Promote all point attributes (excluding P) to vertex attributes. 56 | 57 | Points Merge Distance: 58 | #id: pointconsolidatedist 59 | 60 | When __Promote Point Attributes To Vertex__ is enabled, points within this distance to each other will be merged. 61 | 62 | 63 | == Filter Options == 64 | 65 | Import Geometry: 66 | #id: importgeometry 67 | 68 | Enable this option to import geometry objects. When disabled, glTF meshes will be loaded as empty Geometry nodes. 69 | 70 | Import Custom Attributes: 71 | #id: importcustomattributes 72 | 73 | Enable this option to import any custom attributes from glTF primitives. 74 | 75 | Custom attributes in glTF are prefixed with an underscore _ which will be stripped during import. 76 | 77 | Import Materials: 78 | #id: importmaterials 79 | 80 | Enable this option to import glTF materials as Principled Shader materials. This also creates material assignments. 81 | 82 | If this option is disabled, materials and material assignments will not be created. 83 | 84 | Import Non-Geometry: 85 | #id: importnongeo 86 | 87 | Import nodes which do not create or transform any geometry. Disable this to remove unused nodes when the additional hierarchical data is unneeded. 88 | 89 | Import Unused Materials: 90 | #id: importunusedmaterials 91 | 92 | Import all materials available, even if they are not assigned or used by the imported scene. 93 | -------------------------------------------------------------------------------- /doc/nodes/out/gltf.txt: -------------------------------------------------------------------------------- 1 | #type: node 2 | #context: out 3 | #internal: gltf 4 | #icon: SOP/Geometry 5 | 6 | = glTF = 7 | 8 | The glTF ROP can export your scene as a GLTF file. 9 | See the following link for further information about the [glTF format|https://www.khronos.org/gltf/]. 10 | 11 | @parameters 12 | 13 | Render to Disk: 14 | #id: execute 15 | 16 | Begins the render with the last render control settings. 17 | 18 | Controls...: 19 | #id: renderdialog 20 | 21 | Opens the render control dialog to allow adjustments of the render parameters before rendering. 22 | 23 | File Name: 24 | #id: file 25 | 26 | The path of the file to generate. The extension determines the type of file to generate. 27 | 28 | A path with a `.glb` extension generates a single binary GLB file and stores all resources internally. 29 | 30 | A path with a `.gltf` extension may store some resources externally. Exported resources are stored in the same folder as the file and are referenced relatively. 31 | 32 | Texture Format: 33 | #id: format 34 | 35 | Save all images and textures using this file format. 36 | 37 | Max Texture Resolution: 38 | #id: maxresolution 39 | 40 | If any texture is larger than this resolution then downsize it to this resolution. Useful for quickly reducing file size. 41 | 42 | Texture Quality: 43 | #id: imagequality 44 | 45 | The level of compression to apply to outputted textures. Only applies to lossy image formats. 46 | 47 | 48 | Root Object: 49 | #id: root 50 | 51 | The root object of the scene. All objects contained under this node will be included in the glTF scene. 52 | 53 | Objects: 54 | #id: objects 55 | 56 | A pattern/bundle of objects to include in the glTF scene. 57 | 58 | Flip Normal Map Y: 59 | #id: flipnormalmapy 60 | 61 | Invert the y-coordinate on the normal texture map for each exported mesh. glTF's orientation expects an upwards 62 | facing y-axis. 63 | 64 | Save All Non-Displayed (Hidden) Objects: 65 | #id: savehidden 66 | 67 | If this checkbox is turned on, hidden objects matching the __Objects__ pattern/bundle will also written to the glTF scene. 68 | 69 | Export Custom Attributes: 70 | #id: exportcustom 71 | 72 | If this checkbox is turned off, only attributes defined in the glTF standard are exported. 73 | 74 | Otherwise, all public attributes are exported. 75 | 76 | Attributes are exported with their original types when possible. However, glTF allows a maximum of 32 bits for floats and integers. Integers are also required to be unsigned. 77 | 78 | Attempting to export a datatypes larger than this will narrow the type and print a warning. 79 | 80 | Exported custom attribute names are the original names plus an _ (underscore) prefix. 81 | 82 | As glTF only supports point attributes, name collisions will be resolved using the normal attribute resolution order. 83 | 84 | Export Node Names: 85 | #id: exportnames 86 | 87 | This assigns Houdini node names to the name field of glTF objects. 88 | 89 | In addition, the name attribute on packed primitives will be exported. 90 | 91 | Names are assigned to materials, transformation nodes and geometry. 92 | 93 | Export Materials: 94 | #id: exportmaterials 95 | 96 | Enabling this option exports Principled Shader materials. 97 | 98 | Materials are exported as a combination of emissive maps, normal maps, and PBRMetallicRoughness materials. 99 | 100 | All associated textures and images used by the material are exported alongside the material. 101 | 102 | Some Principled Shader options such as ambient occlusion and subsurface scattering are not exported. 103 | 104 | Materials that do not use the Principled Shader will be ignored. 105 | 106 | Cull Empty Nodes: 107 | #id: cullempty 108 | 109 | Enabling this will cause any Object node which has no descendents containing geometry to be ignored. 110 | 111 | Rescale Texture as Power of Two: 112 | #id: poweroftwo 113 | 114 | This will rescale exported images so that the final image has dimensions which are powers of two (eg. 512x1024, 2048x2048). 115 | 116 | This is required by some low-spec glTF implementations as well as WebGL implementations in older browsers. 117 | -------------------------------------------------------------------------------- /doc/nodes/sop/gltf.txt: -------------------------------------------------------------------------------- 1 | #type: node 2 | #context: out 3 | #internal: gltf 4 | #icon: SOP/Geometry 5 | 6 | = glTF = 7 | 8 | The glTF SOP can load geometry and flattened hierarchy data from a glTF file. 9 | See the following link for further information about the [glTF format|https://www.khronos.org/gltf/]. 10 | 11 | @parameters 12 | 13 | File Name: 14 | #id: filename 15 | 16 | Load this glTF file. The loading method depends on the extension of the file loaded. 17 | 18 | Files with a `.glb` extension are treated as binary GLB files. 19 | 20 | Files with a `.gltf` extension are treated as normal JSON data. 21 | 22 | Load By: 23 | #id: loadby 24 | 25 | Use this type of glTF object to use as the root of the imported data. 26 | 27 | Primitive: 28 | Load the geometry from the selected primitive. Transforms or names are not preserved. 29 | 30 | Node: 31 | Load the geometry from the selected node. Hierarchy, names, and transforms are represented as packed primitives. 32 | 33 | Scene: 34 | Load the geometry from the selected scene. Hierarchy, names, and transforms are represented as packed primitives. 35 | 36 | Mesh ID: 37 | #id: meshid 38 | 39 | Load geometry data from the mesh at this index of the glTF mesh array. 40 | 41 | Primitive Index: 42 | #id: primitiveIndex 43 | 44 | Load geometry data from the primitive at this index of the mesh's primitive array. 45 | 46 | Root Node: 47 | #id: nodeid 48 | 49 | Load the node at this index of the glTF node array. Additionally, load all descendent nodes. 50 | 51 | Scene: 52 | #id: scene 53 | 54 | Load all nodes in the scene at this index of the glTF scene array. 55 | 56 | Geometry Type: 57 | #id: geotype 58 | 59 | Store glTF geometry data in this type of object. 60 | 61 | Flattened Geometry: 62 | Store the geometry as flat Houdini geometry. Transforms and hierarchy are baked in and not stored. Names are represented by primitive attributes and are only stored for meshes, not internal nodes. 63 | 64 | Packed Primitive: 65 | Load the geometry as a hierarchy of packed primitives. Hierarchy, names, and transforms are represented as attributes on the packed primitives. 66 | 67 | Promote Point Attributes To Vertex: 68 | #id: promotepointattrs 69 | 70 | Promote all point attributes (excluding P) to vertex attributes. 71 | 72 | Points Merge Distance: 73 | #id: pointconsolidatedist 74 | 75 | When __Promote Point Attributes To Vertex__ is enabled, points within this distance to each other will be merged. 76 | 77 | Import Custom Attributes: 78 | #id: usecustomattribs 79 | 80 | Enable this option to load all custom attributes from imported primitives. 81 | 82 | Custom attributes in glTF are prefixed with an underscore `_`, which will be stripped during the import process. 83 | 84 | Import Names: 85 | #id: loadnames 86 | 87 | Assigns node, scene, and mesh names as attributes on the imported geometry. 88 | 89 | The scene name is assigned to the `scene_name` detail attribute. 90 | 91 | Node and mesh names are assigned to the `name` attribute on imported packed primitives. 92 | 93 | Import Material Assignments: 94 | #id: materialassignments 95 | 96 | Assigns materials as primitive attributes to the imported geometry. 97 | 98 | Assignments point to the `../../materials/` path. 99 | 100 | If the material name is unique, the assigned material name will simply be the name of the glTF material. 101 | When the name is non_unique, the name will be of the format `Material_i`, where `i` is the index of the material in the glTF global materials array. If the material is nameless, the name will simply be `_i`, where i is again the index. 102 | -------------------------------------------------------------------------------- /src/CustomGLTF.global: -------------------------------------------------------------------------------- 1 | # Global include file for custom glTF library 2 | # Modify these values here to customize your glTF library 3 | 4 | # GLTF core library 5 | GLTFLIB = CustomGLTF 6 | 7 | # GLTC custom namespace 8 | GLTFNAMESPACE = GLTF_Custom 9 | 10 | # Custom Operator prefix 11 | TOKEN_PREFIX = "custom" 12 | LABEL_PREFIX = "Custom " 13 | 14 | # SOP lib name 15 | SOPLIB = SOP_CustomGLTF 16 | 17 | # ROP lib name 18 | ROPLIB = ROP_CustomGLTF 19 | 20 | # HOM lib name 21 | HOM_LIB = HOM_CustomGLTF 22 | 23 | 24 | # lib extension 25 | ifdef WINDOWS 26 | EXT = dll 27 | else ifdef MBSD 28 | EXT = dylib 29 | else 30 | EXT = so 31 | endif -------------------------------------------------------------------------------- /src/GLTF/GLTF_API.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) COPYRIGHTYEAR 3 | * Side Effects Software Inc. All rights reserved. 4 | * 5 | * Redistribution and use of Houdini Development Kit samples in source and 6 | * binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. The name of Side Effects Software may not be used to endorse or 11 | * promote products derived from this software without specific prior 12 | * written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 17 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 20 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | *---------------------------------------------------------------------------- 26 | */ 27 | 28 | #ifndef __GLTF_API_h__ 29 | #define __GLTF_API_h__ 30 | 31 | #include 32 | 33 | #ifdef GLTF_EXPORTS 34 | #define GLTF_API SYS_VISIBILITY_EXPORT 35 | #define GLTF_API_TINST SYS_VISIBILITY_EXPORT_TINST 36 | #else 37 | #define GLTF_API SYS_VISIBILITY_IMPORT 38 | #define GLTF_API_TINST SYS_VISIBILITY_IMPORT_TINST 39 | #endif 40 | 41 | #ifndef GLTF_NAMESPACE 42 | #define GLTF_NAMESPACE GLTF_Houdini 43 | #endif 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /src/GLTF/GLTF_Cache.C: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) COPYRIGHTYEAR 3 | * Side Effects Software Inc. All rights reserved. 4 | * 5 | * Redistribution and use of Houdini Development Kit samples in source and 6 | * binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. The name of Side Effects Software may not be used to endorse or 11 | * promote products derived from this software without specific prior 12 | * written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 17 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 20 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | *---------------------------------------------------------------------------- 26 | */ 27 | #include "GLTF_Cache.h" 28 | #include "GLTF_Loader.h" 29 | 30 | using namespace GLTF_NAMESPACE; 31 | 32 | static UT_Lock theThreadLock; 33 | const static exint MAX_CACHE_FILES = 5; 34 | 35 | GLTF_Cache& 36 | GLTF_Cache::GetInstance() 37 | { 38 | static GLTF_Cache theCache; 39 | 40 | return(theCache); 41 | } 42 | 43 | const UT_SharedPtr 44 | GLTF_Cache::GetLoader(const UT_StringHolder &path) 45 | { 46 | UT_AutoLock lock(theThreadLock); 47 | 48 | auto loader = myLoaderMap.find(path); 49 | if (loader == myLoaderMap.end()) 50 | { 51 | return UT_SharedPtr(nullptr); 52 | } 53 | return loader->second; 54 | } 55 | 56 | void 57 | GLTF_Cache::AutomaticEvict() 58 | { 59 | UT_AutoLock lock(theThreadLock); 60 | 61 | if (myLoaderMap.size() == 0) 62 | return; 63 | 64 | // todo: replace this with a priority queue for LRU eviction 65 | myLoaderMap.erase(myLoaderMap.begin()); 66 | } 67 | 68 | bool GLTF_Cache::EvictLoader(const UT_StringHolder& path) 69 | { 70 | UT_AutoLock lock(theThreadLock); 71 | 72 | return (myLoaderMap.erase(path) > 0); 73 | } 74 | 75 | const UT_SharedPtr 76 | GLTF_Cache::LoadLoader(const UT_StringHolder &path) 77 | { 78 | UT_AutoLock lock(theThreadLock); 79 | 80 | auto loader = GetLoader(path); 81 | if (!loader) 82 | { 83 | if (myLoaderMap.size() > MAX_CACHE_FILES) 84 | { 85 | AutomaticEvict(); 86 | } 87 | 88 | auto new_loader = UT_SharedPtr(new GLTF_Loader(UT_String(path))); 89 | 90 | // If loading fails, then return an empty object 91 | if (!new_loader->Load()) 92 | { 93 | return UT_SharedPtr(nullptr); 94 | } 95 | else 96 | { 97 | myLoaderMap.emplace(path, loader); 98 | } 99 | 100 | return new_loader; 101 | } 102 | 103 | return loader; 104 | } 105 | -------------------------------------------------------------------------------- /src/GLTF/GLTF_Cache.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) COPYRIGHTYEAR 3 | * Side Effects Software Inc. All rights reserved. 4 | * 5 | * Redistribution and use of Houdini Development Kit samples in source and 6 | * binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. The name of Side Effects Software may not be used to endorse or 11 | * promote products derived from this software without specific prior 12 | * written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 17 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 20 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | *---------------------------------------------------------------------------- 26 | */ 27 | 28 | #ifndef __SOP_GLTFCACHE_H__ 29 | #define __SOP_GLTFCACHE_H__ 30 | 31 | #include "GLTF_API.h" 32 | 33 | #include 34 | #include 35 | #include 36 | 37 | namespace GLTF_NAMESPACE 38 | { 39 | 40 | class GLTF_Loader; 41 | 42 | /// 43 | /// A singleton responsible for storing a cached GLTF_Loader. 44 | /// 45 | class GLTF_API GLTF_Cache 46 | { 47 | public: 48 | static GLTF_Cache &GetInstance(); 49 | 50 | /// 51 | /// Creates a new loader with the given filepath, calls Load() 52 | /// and returns a pointer. If we were unable to load, then 53 | /// the loader is evicted and null is returned 54 | /// 55 | const UT_SharedPtr LoadLoader(const UT_StringHolder &path); 56 | 57 | // Removes the loader from the cache, does not destroy any existing 58 | // instances as loaders are UT_SharedPtr's 59 | bool EvictLoader(const UT_StringHolder &path); 60 | 61 | private: 62 | // Gets an existing loader from the cache, returns false 63 | // if the loader does not exist 64 | const UT_SharedPtr GetLoader(const UT_StringHolder &path); 65 | 66 | 67 | void AutomaticEvict(); 68 | 69 | UT_Map> myLoaderMap; 70 | }; 71 | 72 | } // end GLTF_NAMESPACE 73 | 74 | #endif 75 | -------------------------------------------------------------------------------- /src/GLTF/GLTF_GeoLoader.C: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 3 | * Side Effects Software Inc. All rights reserved. 4 | * 5 | * Redistribution and use of Houdini Development Kit samples in source and 6 | * binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. The name of Side Effects Software may not be used to endorse or 11 | * promote products derived from this software without specific prior 12 | * written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 17 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 20 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | *---------------------------------------------------------------------------- 26 | */ 27 | #include "GLTF_GeoLoader.h" 28 | 29 | #include "GLTF_Loader.h" 30 | #include "GLTF_Types.h" 31 | #include "GLTF_Util.h" 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | #include 39 | 40 | using namespace GLTF_NAMESPACE; 41 | 42 | //====================================================================== 43 | 44 | UT_StringHolder 45 | GLTF_MapAttribName(const UT_String &name) 46 | { 47 | // Convert UV's 48 | if (name == "TEXCOORD_0") 49 | return "uv"; 50 | if (name.startsWith("TEXCOORD_")) 51 | { 52 | UT_String name_copy = name; 53 | name_copy.eraseHead(9); 54 | if (name_copy.isInteger()) 55 | { 56 | int uv_idx = name_copy.toInt(); 57 | UT_String gltf_uvname("uv"); 58 | gltf_uvname.append(std::to_string(uv_idx + 1).c_str()); 59 | return UT_StringHolder(gltf_uvname.c_str()); 60 | } 61 | // Falls through here 62 | } 63 | if (name == "NORMAL") 64 | return "N"; 65 | if (name == "TANGENT") 66 | return "tangentu"; 67 | if (name == "COLOR_0") 68 | return "Cd"; 69 | 70 | return name; 71 | } 72 | 73 | bool 74 | GLTF_IsAttributeCustom(UT_String name) 75 | { 76 | if (name.startsWith("_")) 77 | return true; 78 | return false; 79 | } 80 | 81 | // 82 | // Fills the attrib with the given GLTF buffer, applying operation 83 | // to convert types or perform other operations. 84 | // 85 | template 86 | static bool 87 | FillAttrib(GA_Detail &detail, GA_AttributeOwner owner, const UT_StringRef &name, 88 | unsigned char *attrib_data, uint32 attrib_stride, 89 | std::function operation) 90 | { 91 | GA_RWHandleT accessor_handle(&detail, owner, name); 92 | if (!accessor_handle.isValid()) 93 | return false; 94 | 95 | uint32 i = 0; 96 | for (GA_Iterator it(detail.getPointRange()); !it.atEnd(); ++it, ++i) 97 | { 98 | T elem = 99 | GLTF_Util::readInterleavedElement(attrib_data, attrib_stride, i); 100 | 101 | GA_Offset offset = *it; 102 | accessor_handle.set(offset, operation(elem)); 103 | } 104 | 105 | return true; 106 | } 107 | 108 | template 109 | static bool 110 | FillAttrib(GA_Detail &detail, GA_AttributeOwner owner, const UT_StringRef &name, 111 | unsigned char *attrib_data, uint32 attrib_stride) 112 | { 113 | GA_RWHandleT accessor_handle(&detail, owner, name); 114 | if (!accessor_handle.isValid()) 115 | return false; 116 | 117 | uint32 i = 0; 118 | for (GA_Iterator it(detail.getPointRange()); !it.atEnd(); ++it, ++i) 119 | { 120 | T elem = 121 | GLTF_Util::readInterleavedElement(attrib_data, attrib_stride, i); 122 | 123 | GA_Offset offset = *it; 124 | accessor_handle.set(offset, elem); 125 | } 126 | 127 | return true; 128 | } 129 | 130 | //====================================================================== 131 | 132 | bool 133 | GLTF_GeoLoader::load(const GLTF_Loader &loader, GLTF_Handle mesh_idx, 134 | GLTF_Handle primitive_idx, GU_Detail &detail, 135 | const GLTF_MeshLoadingOptions options) 136 | { 137 | GLTF_GeoLoader geoload(loader, mesh_idx, primitive_idx, options); 138 | return geoload.loadIntoDetail(detail); 139 | } 140 | 141 | 142 | bool 143 | GLTF_GeoLoader::loadIntoDetail(GU_Detail &detail) 144 | { 145 | const GLTF_Mesh *mesh = myLoader.getMesh(myMeshIdx); 146 | 147 | if (!mesh) 148 | return false; 149 | 150 | if (mesh->primitives.size() <= myPrimIdx) 151 | return false; 152 | 153 | const GLTF_Primitive &primitive = mesh->primitives[myPrimIdx]; 154 | 155 | if (primitive.mode != GLTF_RENDERMODE_TRIANGLES) 156 | return false; 157 | 158 | // Load points & vertices attributes 159 | auto position_attribute = primitive.attributes.find("POSITION"); 160 | if (position_attribute == primitive.attributes.end()) 161 | return false; 162 | 163 | const GLTF_Accessor *position = myLoader.getAccessor(position_attribute->second); 164 | if (position == nullptr) 165 | return false; 166 | 167 | const GLTF_Accessor *indices = myLoader.getAccessor(primitive.indices); 168 | if (indices != nullptr) 169 | { 170 | if (!LoadVerticesAndPoints(detail, myOptions, *position, *indices)) 171 | return false; 172 | } 173 | else if (!LoadVerticesAndPointsNonIndexed(detail, *position)) 174 | return false; 175 | 176 | detail.bumpDataIdsForAddOrRemove(true, true, true); 177 | 178 | // Now for any other attribute, load it as a point attribute 179 | for (const auto &attrib : primitive.attributes) 180 | { 181 | UT_String attrib_name(attrib.first.c_str()); 182 | 183 | // Position is treated specially (see LoadVerticesAndPoints) 184 | if (attrib.first == "POSITION") 185 | continue; 186 | 187 | bool custom_attrib = GLTF_IsAttributeCustom(attrib_name); 188 | if (myOptions.loadCustomAttribs || !custom_attrib) 189 | { 190 | // Erase the _ 191 | if (custom_attrib) 192 | attrib_name.eraseHead(1); 193 | 194 | const GLTF_Accessor &attrib_acc = 195 | *myLoader.getAccessor(attrib.second); 196 | 197 | if (!AddPointAttribute(detail, attrib_name, attrib_acc)) 198 | return false; 199 | } 200 | } 201 | 202 | // Handle the case when the exporter decides to use a single bufferview 203 | // for multiple submeshes (I've only seen this on the Unity exporter). 204 | // This could be handled more efficiently by only loading the points 205 | // referenced by the accessor. 206 | detail.destroyUnusedPoints(); 207 | 208 | if (myOptions.promotePointAttribs) 209 | { 210 | // Promote all point attributes to vert attributes 211 | // 212 | // Note that we can't do this easily while iterating over 213 | // GA_AttributeDict, since GU_Promote will remove items from its 214 | // UT_ArrayStringMap while we are iterating over it through a proxy 215 | // iterator ... Using a temporary array is the simplest. 216 | UT_Array to_promote; 217 | 218 | for (auto attribute : detail.getAttributeDict(GA_ATTRIB_POINT)) 219 | { 220 | // Skip Position attribute 221 | if (attribute->getName() == "P") 222 | continue; 223 | 224 | if (attribute->getScope() != GA_AttributeScope::GA_SCOPE_PUBLIC) 225 | continue; 226 | 227 | to_promote.append(attribute); 228 | } 229 | 230 | for (auto attribute : to_promote) 231 | GU_Promote::promote(detail, attribute, GA_ATTRIB_VERTEX); 232 | 233 | // Consolidate points 234 | if (myOptions.consolidatePoints) 235 | { 236 | detail.onlyConsolidatePoints( 237 | myOptions.pointConsolidationDistance, nullptr, 0, true, 238 | GU_Detail::ONLYCONS_GRP_PROP_LEAST, true); 239 | } 240 | 241 | } 242 | 243 | return true; 244 | } 245 | 246 | bool 247 | GLTF_GeoLoader::AddPointAttribute(GU_Detail &detail, 248 | const UT_StringHolder& attrib_name, 249 | const GLTF_Accessor &accessor) 250 | { 251 | unsigned char *attrib_data; 252 | GA_RWHandleT accessor_handle; 253 | 254 | if (!myLoader.LoadAccessorData(accessor, attrib_data)) 255 | return false; 256 | 257 | const GLTF_BufferView &bufferview = *myLoader.getBufferView(accessor.bufferView); 258 | 259 | uint32 attrib_stride = GLTF_Util::getStride( 260 | bufferview.byteStride, accessor.type, accessor.componentType); 261 | 262 | const UT_StringHolder houdini_attrib_name = 263 | GLTF_MapAttribName(attrib_name.c_str()); 264 | 265 | const uint32 num_elements = GLTF_Util::typeGetElements(accessor.type); 266 | 267 | // TODO: We are typecasting uint32 to int32 268 | if (accessor.componentType == GLTF_COMPONENT_FLOAT) 269 | { 270 | if (num_elements == 1) 271 | { 272 | detail.addFloatTuple(GA_ATTRIB_POINT, GA_SCOPE_PUBLIC, 273 | houdini_attrib_name, 1); 274 | 275 | FillAttrib(detail, GA_ATTRIB_POINT, houdini_attrib_name, 276 | attrib_data, attrib_stride); 277 | } 278 | else if (num_elements == 2) 279 | { 280 | if (houdini_attrib_name == "uv" || houdini_attrib_name == "uv2") 281 | { 282 | const auto flip_uvs = [](UT_Vector2F tex_coord) { 283 | return UT_Vector3F(tex_coord.x(), 1.f - tex_coord.y(), 0); 284 | }; 285 | 286 | detail.addFloatTuple(GA_ATTRIB_POINT, GA_SCOPE_PUBLIC, 287 | houdini_attrib_name, 3); 288 | 289 | FillAttrib( 290 | detail, GA_ATTRIB_POINT, houdini_attrib_name, attrib_data, 291 | attrib_stride, flip_uvs); 292 | } 293 | else 294 | { 295 | detail.addFloatTuple(GA_ATTRIB_POINT, GA_SCOPE_PUBLIC, 296 | houdini_attrib_name, 2); 297 | 298 | FillAttrib(detail, GA_ATTRIB_POINT, 299 | houdini_attrib_name, attrib_data, 300 | attrib_stride); 301 | } 302 | } 303 | else if (num_elements == 3) 304 | { 305 | if (houdini_attrib_name == "N") 306 | { 307 | detail.addNormalAttribute(GA_ATTRIB_POINT, GA_STORE_REAL32); 308 | } 309 | else 310 | { 311 | detail.addFloatTuple(GA_ATTRIB_POINT, GA_SCOPE_PUBLIC, 312 | houdini_attrib_name, 3); 313 | } 314 | 315 | FillAttrib(detail, GA_ATTRIB_POINT, 316 | houdini_attrib_name, attrib_data, 317 | attrib_stride); 318 | } 319 | else if (num_elements == 4) 320 | { 321 | detail.addFloatTuple(GA_ATTRIB_POINT, GA_SCOPE_PUBLIC, 322 | houdini_attrib_name, 4); 323 | FillAttrib(detail, GA_ATTRIB_POINT, 324 | houdini_attrib_name, attrib_data, 325 | attrib_stride); 326 | } 327 | else 328 | { 329 | UT_ASSERT(false); 330 | } 331 | } 332 | else if (accessor.componentType == GLTF_COMPONENT_UNSIGNED_BYTE || 333 | accessor.componentType == GLTF_COMPONENT_UNSIGNED_SHORT || 334 | accessor.componentType == GLTF_COMPONENT_UNSIGNED_INT) 335 | { 336 | detail.addIntTuple(GA_ATTRIB_POINT, GA_SCOPE_PUBLIC, 337 | houdini_attrib_name, num_elements); 338 | 339 | if (num_elements == 1) 340 | { 341 | FillAttrib(detail, GA_ATTRIB_POINT, houdini_attrib_name, 342 | attrib_data, attrib_stride); 343 | } 344 | else if (num_elements == 2) 345 | { 346 | FillAttrib(detail, GA_ATTRIB_POINT, 347 | houdini_attrib_name, attrib_data, 348 | attrib_stride); 349 | } 350 | else if (num_elements == 3) 351 | { 352 | FillAttrib(detail, GA_ATTRIB_POINT, 353 | houdini_attrib_name, attrib_data, 354 | attrib_stride); 355 | } 356 | else if (num_elements == 4) 357 | { 358 | FillAttrib(detail, GA_ATTRIB_POINT, 359 | houdini_attrib_name, attrib_data, 360 | attrib_stride); 361 | } 362 | else 363 | { 364 | UT_ASSERT(false); 365 | } 366 | } 367 | 368 | return true; 369 | } 370 | 371 | bool 372 | GLTF_GeoLoader::LoadVerticesAndPoints(GU_Detail &detail, 373 | const GLTF_MeshLoadingOptions &options, 374 | const GLTF_Accessor &pos, 375 | const GLTF_Accessor &ind) 376 | { 377 | 378 | // We convert everything to indexed triangle meshes, so the number 379 | // of indices must divisible by 3 380 | if (ind.count % 3 != 0) 381 | return false; 382 | 383 | GLTF_BufferView pos_bv = *myLoader.getBufferView(pos.bufferView); 384 | 385 | // Load the vertex data from the binary into a buffer 386 | unsigned char *position_data; 387 | if (!myLoader.LoadAccessorData(pos, position_data)) 388 | return false; 389 | 390 | uint32 pos_stride = GLTF_Util::getStride(pos_bv.byteStride, pos.type, 391 | pos.componentType); 392 | 393 | // Read in the vertices from the (potentially) interleaved array 394 | GA_Offset start_pt_off = detail.appendPointBlock(pos.count); 395 | for (uint32 i = 0; i < pos.count; i++) 396 | { 397 | UT_Vector3F vec = GLTF_Util::readInterleavedElement( 398 | position_data, pos_stride, i); 399 | detail.setPos3(start_pt_off + i, vec); 400 | } 401 | 402 | // Wire up indices 403 | unsigned char *indice_data; 404 | const GLTF_BufferView &indBV = *myLoader.getBufferView(ind.bufferView); 405 | if (!myLoader.LoadAccessorData(ind, indice_data)) 406 | return false; 407 | 408 | uint32 ind_stride = GLTF_Util::getStride(indBV.byteStride, ind.type, 409 | ind.componentType); 410 | const uint32 num_tris = ind.count / 3; 411 | 412 | GA_Offset start_vtxoff; 413 | detail.appendPrimitivesAndVertices(GA_PRIMPOLY, ind.count / 3, 3, 414 | start_vtxoff, true); 415 | 416 | uint32 tri_vert_indexes[3]; 417 | for (uint32 tri_idx = 0; tri_idx < num_tris; tri_idx++) 418 | { 419 | for (uint32 tri_vert_num = 0; tri_vert_num < 3; tri_vert_num++) 420 | { 421 | const uint32 indice = tri_idx * 3 + tri_vert_num; 422 | 423 | uint32 elem = 0; 424 | if (ind.componentType == 425 | GLTF_ComponentType::GLTF_COMPONENT_UNSIGNED_BYTE) 426 | { 427 | elem = GLTF_Util::readInterleavedElement( 428 | indice_data, ind_stride, indice); 429 | } 430 | else if (ind.componentType == 431 | GLTF_ComponentType::GLTF_COMPONENT_UNSIGNED_SHORT) 432 | { 433 | elem = GLTF_Util::readInterleavedElement( 434 | indice_data, ind_stride, indice); 435 | } 436 | else if (ind.componentType == 437 | GLTF_ComponentType::GLTF_COMPONENT_UNSIGNED_INT) 438 | { 439 | elem = GLTF_Util::readInterleavedElement( 440 | indice_data, ind_stride, indice); 441 | } 442 | else 443 | { 444 | return false; 445 | } 446 | 447 | tri_vert_indexes[tri_vert_num] = start_vtxoff + elem; 448 | } 449 | 450 | const uint32 cur_tri_off = start_pt_off + tri_idx * 3; 451 | 452 | detail.getTopology().wireVertexPoint( 453 | static_cast(cur_tri_off + 0), 454 | static_cast(tri_vert_indexes[0])); 455 | // Swap second and third vertex indexes to reverse tri winding order 456 | detail.getTopology().wireVertexPoint( 457 | static_cast(cur_tri_off + 2), 458 | static_cast(tri_vert_indexes[1])); 459 | detail.getTopology().wireVertexPoint( 460 | static_cast(cur_tri_off + 1), 461 | static_cast(tri_vert_indexes[2])); 462 | } 463 | 464 | return true; 465 | } 466 | 467 | bool 468 | GLTF_GeoLoader::LoadVerticesAndPointsNonIndexed(GU_Detail &detail, const GLTF_Accessor &pos) 469 | { 470 | // We convert everything to triangle meshes, so the number 471 | // of vertices must divisible by 3 472 | if (pos.count % 3 != 0) 473 | return false; 474 | 475 | const uint32 num_tris = pos.count / 3; 476 | 477 | GLTF_BufferView pos_bv = *myLoader.getBufferView(pos.bufferView); 478 | 479 | // Load the vertex data from the binary into a buffer 480 | unsigned char *position_data; 481 | if (!myLoader.LoadAccessorData(pos, position_data)) 482 | return false; 483 | 484 | uint32 pos_stride = GLTF_Util::getStride(pos_bv.byteStride, pos.type, 485 | pos.componentType); 486 | 487 | // Read in the vertices from the (potentially) interleaved array 488 | GA_Offset start_pt_off = detail.appendPointBlock(pos.count); 489 | for (uint32 i = 0; i < pos.count; i++) 490 | { 491 | UT_Vector3F vec = GLTF_Util::readInterleavedElement( 492 | position_data, pos_stride, i); 493 | detail.setPos3(start_pt_off + i, vec); 494 | } 495 | 496 | GA_Offset start_vtxoff; 497 | detail.appendPrimitivesAndVertices(GA_PRIMPOLY, num_tris, 3, 498 | start_vtxoff, true); 499 | 500 | for (uint32 tri_idx = 0; tri_idx < num_tris; tri_idx++) 501 | { 502 | const uint32 cur_tri_off = start_pt_off + (tri_idx * 3); 503 | const uint32 vtx_tri_off = start_vtxoff + (tri_idx * 3); 504 | 505 | detail.getTopology().wireVertexPoint( 506 | static_cast(cur_tri_off + 0), 507 | static_cast(vtx_tri_off + 0)); 508 | // Swap second and third vertex indexes to reverse tri winding order 509 | detail.getTopology().wireVertexPoint( 510 | static_cast(cur_tri_off + 2), 511 | static_cast(vtx_tri_off + 1)); 512 | detail.getTopology().wireVertexPoint( 513 | static_cast(cur_tri_off + 1), 514 | static_cast(vtx_tri_off + 2)); 515 | } 516 | 517 | return true; 518 | } 519 | 520 | GLTF_GeoLoader::GLTF_GeoLoader(const GLTF_Loader &loader, GLTF_Handle mesh_idx, 521 | GLTF_Handle primitive_idx, 522 | const GLTF_MeshLoadingOptions& options) 523 | : myMeshIdx(mesh_idx), 524 | myPrimIdx(primitive_idx), 525 | myLoader(loader), 526 | myOptions(options) 527 | { 528 | } 529 | -------------------------------------------------------------------------------- /src/GLTF/GLTF_GeoLoader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 3 | * Side Effects Software Inc. All rights reserved. 4 | * 5 | * Redistribution and use of Houdini Development Kit samples in source and 6 | * binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. The name of Side Effects Software may not be used to endorse or 11 | * promote products derived from this software without specific prior 12 | * written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 17 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 20 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | *---------------------------------------------------------------------------- 26 | */ 27 | 28 | #ifndef __SOP_GLTFGEOLOADER_H__ 29 | #define __SOP_GLTFGEOLOADER_H__ 30 | 31 | #include "GLTF_API.h" 32 | 33 | #include 34 | 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | // Forward declarations 41 | class GU_Detail; 42 | class UT_String; 43 | 44 | namespace GLTF_NAMESPACE 45 | { 46 | 47 | class GLTF_Loader; 48 | 49 | struct GLTF_API GLTF_MeshLoadingOptions 50 | { 51 | bool loadCustomAttribs = true; 52 | bool promotePointAttribs = true; 53 | bool consolidatePoints = true; 54 | fpreal pointConsolidationDistance = 0.0001F; 55 | }; 56 | 57 | class GLTF_API GLTF_GeoLoader 58 | { 59 | public: 60 | GLTF_GeoLoader(const GLTF_Loader &loader, GLTF_Handle mesh_idx, 61 | GLTF_Handle primitive_idx, 62 | const GLTF_MeshLoadingOptions& options = {}); 63 | 64 | bool loadIntoDetail(GU_Detail &detail); 65 | 66 | static bool load(const GLTF_Loader &loader, GLTF_Handle mesh_idx, 67 | GLTF_Handle primitive_idx, GU_Detail &detail, 68 | const GLTF_MeshLoadingOptions options = {}); 69 | 70 | private: 71 | bool 72 | LoadVerticesAndPoints(GU_Detail &detail, 73 | const GLTF_MeshLoadingOptions &options, 74 | const GLTF_Accessor &pos, const GLTF_Accessor &ind); 75 | 76 | bool 77 | LoadVerticesAndPointsNonIndexed(GU_Detail &detail, const GLTF_Accessor &pos); 78 | 79 | bool 80 | AddPointAttribute(GU_Detail &detail, const UT_StringHolder &attrib_name, 81 | const GLTF_Accessor &accessor); 82 | 83 | // const uint32 myRootNode; 84 | const GLTF_Handle myMeshIdx; 85 | const GLTF_Handle myPrimIdx; 86 | const GLTF_Loader &myLoader; 87 | const GLTF_MeshLoadingOptions myOptions; 88 | }; 89 | 90 | } // end GLTF_NAMESPACE 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /src/GLTF/GLTF_Loader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) COPYRIGHTYEAR 3 | * Side Effects Software Inc. All rights reserved. 4 | * 5 | * Redistribution and use of Houdini Development Kit samples in source and 6 | * binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. The name of Side Effects Software may not be used to endorse or 11 | * promote products derived from this software without specific prior 12 | * written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 17 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 20 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | *---------------------------------------------------------------------------- 26 | */ 27 | 28 | #ifndef __SOP_GLTFLOADER_H__ 29 | #define __SOP_GLTFLOADER_H__ 30 | 31 | #include "GLTF_API.h" 32 | #include "GLTF_Types.h" 33 | 34 | #include 35 | #include 36 | #include 37 | 38 | class UT_JSONValue; 39 | class UT_JSONValueMap; 40 | class UT_JSONValueArray; 41 | 42 | namespace GLTF_NAMESPACE 43 | { 44 | 45 | //================================================= 46 | 47 | /// 48 | /// A class for loading a GLTF file into a more usuable structure. 49 | /// 50 | class GLTF_API GLTF_Loader 51 | { 52 | public: 53 | GLTF_Loader(); 54 | GLTF_Loader(UT_String filename); 55 | virtual ~GLTF_Loader(); 56 | 57 | // Delete copy constructor 58 | GLTF_Loader(GLTF_Loader &loader) = delete; 59 | GLTF_Loader(const GLTF_Loader &loader) = delete; 60 | 61 | /// 62 | /// Loads and parses the JSON data within this GLTF file. 63 | /// Does not load any associated buffer data. 64 | /// @return Whether or not the load suceeded 65 | /// 66 | bool Load(); 67 | 68 | /// 69 | /// Loads all data that can be accessed with the given accessor and returns 70 | /// a pointer to the beginning of the data. The caller is not responsible 71 | /// for deleting the returned data. 72 | /// @return Whether or not the accessor data load suceeded 73 | /// 74 | bool LoadAccessorData(const GLTF_Accessor &accessor, unsigned char *&data) const; 75 | 76 | GLTF_Accessor *createAccessor(GLTF_Handle& idx); 77 | GLTF_Animation *createAnimation(GLTF_Handle& idx); 78 | GLTF_Buffer *createBuffer(GLTF_Handle& idx); 79 | GLTF_BufferView *createBufferView(GLTF_Handle& idx); 80 | GLTF_Camera *createCamera(GLTF_Handle& idx); 81 | GLTF_Image *createImage(GLTF_Handle& idx); 82 | GLTF_Material *createMaterial(GLTF_Handle& idx); 83 | GLTF_Mesh *createMesh(GLTF_Handle& idx); 84 | GLTF_Node *createNode(GLTF_Handle& idx); 85 | GLTF_Sampler *createSampler(GLTF_Handle& idx); 86 | GLTF_Scene *createScene(GLTF_Handle& idx); 87 | GLTF_Skin *createSkin(GLTF_Handle& idx); 88 | GLTF_Texture *createTexture(GLTF_Handle& idx); 89 | 90 | GLTF_Accessor const *getAccessor(GLTF_Handle idx) const; 91 | GLTF_Animation const *getAnimation(GLTF_Handle idx) const; 92 | GLTF_Asset getAsset() const; 93 | GLTF_Buffer const *getBuffer(GLTF_Handle idx) const; 94 | GLTF_BufferView const *getBufferView(GLTF_Handle idx) const; 95 | GLTF_Camera const *getCamera(GLTF_Handle idx) const; 96 | GLTF_Image const *getImage(GLTF_Handle idx) const; 97 | GLTF_Material const *getMaterial(GLTF_Handle idx) const; 98 | GLTF_Mesh const *getMesh(GLTF_Handle idx) const; 99 | GLTF_Node const *getNode(GLTF_Handle idx) const; 100 | GLTF_Sampler const *getSampler(GLTF_Handle idx) const; 101 | GLTF_Handle getDefaultScene() const; 102 | GLTF_Scene const *getScene(GLTF_Handle idx) const; 103 | GLTF_Skin const *getSkin(GLTF_Handle idx) const; 104 | GLTF_Texture const *getTexture(GLTF_Handle idx) const; 105 | 106 | GLTF_Accessor *getAccessor(GLTF_Handle idx); 107 | GLTF_Animation *getAnimation(GLTF_Handle idx); 108 | GLTF_Buffer *getBuffer(GLTF_Handle idx); 109 | GLTF_BufferView *getBufferView(GLTF_Handle idx); 110 | GLTF_Camera *getCamera(GLTF_Handle idx); 111 | GLTF_Image *getImage(GLTF_Handle idx); 112 | GLTF_Material *getMaterial(GLTF_Handle idx); 113 | GLTF_Mesh *getMesh(GLTF_Handle idx); 114 | GLTF_Node *getNode(GLTF_Handle idx); 115 | GLTF_Sampler *getSampler(GLTF_Handle idx); 116 | GLTF_Scene *getScene(GLTF_Handle idx); 117 | GLTF_Skin *getSkin(GLTF_Handle idx); 118 | GLTF_Texture *getTexture(GLTF_Handle idx); 119 | 120 | const UT_Array &getAccessors() const; 121 | const UT_Array &getAnimations() const; 122 | const UT_Array &getBuffers() const; 123 | const UT_Array &getBufferViews() const; 124 | const UT_Array &getCameras() const; 125 | const UT_Array &getImages() const; 126 | const UT_Array &getMaterials() const; 127 | const UT_Array &getMeshes() const; 128 | const UT_Array &getNodes() const; 129 | const UT_Array &getSamplers() const; 130 | const UT_Array &getScenes() const; 131 | const UT_Array &getSkins() const; 132 | const UT_Array &getTextures() const; 133 | 134 | void removeBuffer(GLTF_Handle idx); 135 | void removeNode(GLTF_Handle idx); 136 | 137 | void setDefaultScene(const GLTF_Handle &idx); 138 | void setAsset(const GLTF_Asset &asset); 139 | 140 | exint getNumAccessors() const; 141 | exint getNumAnimations() const; 142 | exint getNumBuffers() const; 143 | exint getNumBufferViews() const; 144 | exint getNumCameras() const; 145 | exint getNumImages() const; 146 | exint getNumMaterials() const; 147 | exint getNumMeshes() const; 148 | exint getNumNodes() const; 149 | exint getNumSamplers() const; 150 | exint getNumScenes() const; 151 | exint getNumSkins() const; 152 | exint getNumTextures() const; 153 | 154 | private: 155 | // Handles reading the JSON map, which may be external or 156 | // embedded in a GLB file 157 | bool ReadJSON(const UT_JSONValueMap &root_json); 158 | 159 | bool ReadGLTF(); 160 | bool ReadGLB(); 161 | 162 | bool ReadAsset(const UT_JSONValueMap &asset_json); 163 | 164 | // Read items from GLTF JSON -> GLTF struct 165 | bool ReadNode(const UT_JSONValueMap &node_json, const exint idx); 166 | bool ReadBuffer(const UT_JSONValueMap &buffer_json, const exint idx); 167 | bool ReadBufferView(const UT_JSONValueMap &bufferview_json, const exint idx); 168 | bool ReadAccessor(const UT_JSONValueMap &accessor_json, const exint idx); 169 | bool ReadPrimitive(const UT_JSONValue *primitive_json, GLTF_Primitive *primitive); 170 | bool ReadMesh(const UT_JSONValueMap &mesh_json, const exint idx); 171 | bool ReadTexture(const UT_JSONValueMap &texture_json, const exint idx); 172 | bool ReadSampler(const UT_JSONValueMap &sampler_json, const exint idx); 173 | bool ReadImage(const UT_JSONValueMap &image_json, const exint idx); 174 | bool ReadScene(const UT_JSONValueMap &scene_json, const exint idx); 175 | bool ReadMaterial(const UT_JSONValueMap &material_json, const exint idx); 176 | 177 | // Utility: Takes an array of maps, and calls funcs() on every item 178 | bool ReadArrayOfMaps(const UT_JSONValue *arr, bool required, 179 | std::function func); 180 | 181 | UT_String myFilename; 182 | UT_String myBasePath; 183 | 184 | bool myIsLoaded; 185 | 186 | // Retrieves the buffer at idx, potentially from cache if cached. 187 | bool LoadBuffer(uint32 idx, unsigned char *&buffer_data) const; 188 | 189 | // Simply an indexed array of pointers to buffer data 190 | UT_Array myAccesors; 191 | UT_Array myAnimations; 192 | GLTF_Asset myAsset; 193 | UT_Array myBuffers; 194 | UT_Array myBufferViews; 195 | UT_Array myCameras; 196 | UT_Array myImages; 197 | UT_Array myMaterials; 198 | UT_Array myMeshes; 199 | UT_Array myNodes; 200 | UT_Array mySamplers; 201 | uint32 myScene = GLTF_INVALID_IDX; 202 | UT_Array myScenes; 203 | UT_Array mySkins; 204 | UT_Array myTextures; 205 | 206 | // Loading is transparent to the caller -- we want const 207 | // semantics that 208 | mutable UT_Lock myAccessorLock; 209 | mutable UT_Array myBufferCache; 210 | }; 211 | 212 | //================================================= 213 | 214 | } // end GLTF_NAMESPACE 215 | 216 | #endif 217 | -------------------------------------------------------------------------------- /src/GLTF/GLTF_Types.C: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) COPYRIGHTYEAR 3 | * Side Effects Software Inc. All rights reserved. 4 | * 5 | * Redistribution and use of Houdini Development Kit samples in source and 6 | * binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. The name of Side Effects Software may not be used to endorse or 11 | * promote products derived from this software without specific prior 12 | * written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 17 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 20 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | *---------------------------------------------------------------------------- 26 | */ 27 | 28 | #include "GLTF_Types.h" 29 | 30 | #include 31 | 32 | using namespace GLTF_NAMESPACE; 33 | 34 | GLTF_TRANSFORM_TYPE 35 | GLTF_Node::getTransformType() const 36 | { 37 | if (matrix.isIdentity()) 38 | { 39 | return GLTF_TRANSFORM_TRS; 40 | } 41 | return GLTF_TRANSFORM_MAT4; 42 | } 43 | 44 | void 45 | GLTF_Node::getTransformAsMatrix(UT_Matrix4F &mat) const 46 | { 47 | UT_Matrix4F transform = matrix; 48 | if (transform.isIdentity()) 49 | { 50 | UT_Matrix4F rotation_transform; 51 | UT_Quaternion(rotation).getTransformMatrix(rotation_transform); 52 | UT_Matrix4F trs_matrix(1); 53 | 54 | trs_matrix.scale(scale); 55 | trs_matrix = rotation_transform * trs_matrix; 56 | trs_matrix.translate(translation); 57 | 58 | transform = trs_matrix; 59 | } 60 | mat = transform; 61 | } -------------------------------------------------------------------------------- /src/GLTF/GLTF_Types.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) COPYRIGHTYEAR 3 | * Side Effects Software Inc. All rights reserved. 4 | * 5 | * Redistribution and use of Houdini Development Kit samples in source and 6 | * binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. The name of Side Effects Software may not be used to endorse or 11 | * promote products derived from this software without specific prior 12 | * written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 17 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 20 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | *---------------------------------------------------------------------------- 26 | */ 27 | #ifndef __SOP_GLTFTYPES_H__ 28 | #define __SOP_GLTFTYPES_H__ 29 | 30 | #include "GLTF_API.h" 31 | 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | namespace GLTF_NAMESPACE 42 | { 43 | 44 | #define GLTF_INVALID_IDX uint32(~0) 45 | 46 | typedef uint32 GLTF_Int; 47 | typedef uint32 GLTF_Offset; 48 | typedef uint32 GLTF_Handle; 49 | 50 | const uint32 GLB_BUFFER_IDX = 0; 51 | const uint32 GLTF_GLB_MAGIC = 0x46546C67; 52 | const uint32 GLTF_GLB_JSON = 0x4E4F534A; 53 | const uint32 GLTF_GLB_BIN = 0x004E4942; 54 | 55 | constexpr const char *GLTF_PROJECTION_NAME_ORTHOGRAPHIC = "ORTHOGRAPHIC"; 56 | constexpr const char *GLTF_PROJECTION_NAME_PERSPECTIVE = "PERSPECTIVE"; 57 | constexpr const char *GLTF_TYPE_NAME_SCALAR = "SCALAR"; 58 | constexpr const char *GLTF_TYPE_NAME_VEC2 = "VEC2"; 59 | constexpr const char *GLTF_TYPE_NAME_VEC3 = "VEC3"; 60 | constexpr const char *GLTF_TYPE_NAME_VEC4 = "VEC4"; 61 | constexpr const char *GLTF_TYPE_NAME_MAT2 = "MAT2"; 62 | constexpr const char *GLTF_TYPE_NAME_MAT3 = "MAT3"; 63 | constexpr const char *GLTF_TYPE_NAME_MAT4 = "MAT4"; 64 | 65 | enum GLTF_RenderMode 66 | { 67 | GLTF_RENDERMODE_POINTS = 0, 68 | GLTF_RENDERMODE_LINES = 1, 69 | GLTF_RENDERMODE_LINE_LOOP = 2, 70 | GLTF_RENDERMODE_LINE_STRIP = 3, 71 | GLTF_RENDERMODE_TRIANGLES = 4, 72 | GLTF_RENDERMODE_TRIANGLE_STRIP = 5, 73 | GLTF_RENDERMODE_TRIANGLE_FAN = 6, 74 | GLTF_RENDERMODE_INVALID 75 | }; 76 | 77 | enum GLTF_ComponentType 78 | { 79 | GLTF_COMPONENT_INVALID = 0, 80 | GLTF_COMPONENT_BYTE = 5120, 81 | GLTF_COMPONENT_UNSIGNED_BYTE = 5121, 82 | GLTF_COMPONENT_SHORT = 5122, 83 | GLTF_COMPONENT_UNSIGNED_SHORT = 5123, 84 | GLTF_COMPONENT_UNSIGNED_INT = 5125, 85 | GLTF_COMPONENT_FLOAT = 5126 86 | }; 87 | 88 | enum GLTF_BufferViewTarget 89 | { 90 | GLTF_BUFFER_INVALID = 0, 91 | GLTF_BUFFER_ARRAY = 34962, 92 | GLTF_BUFFER_ELEMENT = 34963 93 | }; 94 | 95 | enum GLTF_TexFilter 96 | { 97 | GLTF_TEXFILTER_INVALID = 0, 98 | GLTF_TEXFILTER_NEAREST = 9728, 99 | GLTF_TEXFILTER_LINEAR = 9729, 100 | GLTF_TEXFILTER_NEAREST_MIPMAP_NEAREST = 9984, 101 | GLTF_TEXFILTER_LINEAR_MIPMAP_NEAREST = 9985, 102 | GLTF_TEXFILTER_NEAREST_MIPMAP_LINEAR = 9986, 103 | GLTF_TEXFILTER_LINEAR_MIPMAP_LINEAR = 9987, 104 | }; 105 | 106 | enum GLTF_TexWrap 107 | { 108 | GLTF_TEXWRAP_INVALID = 0, 109 | GLTF_TEXWRAP_CLAMP_TO_EDGE = 33071, 110 | GLTF_TEXWRAP_MIRRORED_REPEAT = 33648, 111 | GLTF_TEXWRAP_REPEAT = 10497, 112 | }; 113 | 114 | //================================================= 115 | 116 | // Represented as a string in the GLTF file 117 | enum GLTF_Type 118 | { 119 | GLTF_TYPE_INVALID = 0, 120 | GLTF_TYPE_SCALAR, 121 | GLTF_TYPE_VEC2, 122 | GLTF_TYPE_VEC3, 123 | GLTF_TYPE_VEC4, 124 | GLTF_TYPE_MAT2, 125 | GLTF_TYPE_MAT3, 126 | GLTF_TYPE_MAT4 127 | }; 128 | 129 | enum GLTF_TRANSFORM_TYPE 130 | { 131 | GLTF_TRANSFORM_NONE = 0, 132 | GLTF_TRANSFORM_MAT4, 133 | GLTF_TRANSFORM_TRS 134 | }; 135 | 136 | enum GLTF_TextureTypes 137 | { 138 | TEXTURE_NONE = 0, 139 | TEXTURE_NORMAL, 140 | TEXTURE_OCCLUSION, 141 | TEXTURE_EMISSIVE 142 | }; 143 | 144 | //========================================================================= 145 | 146 | // GLTF types - unimplemented fields are currently displayed as comments in the 147 | // structure declarations 148 | 149 | struct GLTF_API GLTF_TextureInfo 150 | { 151 | GLTF_Handle index = GLTF_INVALID_IDX; 152 | GLTF_Int texCoord = 0; 153 | // extensions 154 | // extras 155 | }; 156 | 157 | struct GLTF_API GLTF_NormalTextureInfo : public GLTF_TextureInfo 158 | { 159 | fpreal32 scale = 1.0; 160 | // extensions 161 | // extras 162 | }; 163 | 164 | struct GLTF_API GLTF_Accessor 165 | { 166 | GLTF_Handle bufferView = GLTF_INVALID_IDX; 167 | GLTF_Int byteOffset = 0; 168 | GLTF_ComponentType componentType = GLTF_COMPONENT_INVALID; // Required 169 | bool normalized = false; 170 | GLTF_Int count = 0; 171 | GLTF_Type type = GLTF_TYPE_INVALID; 172 | 173 | // A double has a 53 bit mantissa, we can therefore cast to either float 174 | // or int32 without loss of precision 175 | UT_Array max; 176 | UT_Array min; 177 | 178 | // sparse 179 | UT_String name = ""; 180 | // extensions 181 | // extras 182 | }; 183 | 184 | struct GLTF_API GLTF_Animation 185 | { 186 | // channels 187 | UT_Array samplers; 188 | UT_String name = ""; 189 | // extensions 190 | // extras 191 | }; 192 | 193 | struct GLTF_API GLTF_Asset 194 | { 195 | UT_String copyright = ""; 196 | UT_String generator = ""; 197 | UT_String version; 198 | UT_String minversion = ""; 199 | // extensions 200 | // extras 201 | }; 202 | 203 | struct GLTF_API GLTF_Buffer 204 | { 205 | UT_String myURI = ""; 206 | GLTF_Int myByteLength; 207 | UT_String name = ""; 208 | // extensions 209 | // extras 210 | }; 211 | 212 | struct GLTF_API GLTF_BufferView 213 | { 214 | GLTF_Handle buffer = 0; // Required 215 | GLTF_Int byteOffset = 0; 216 | GLTF_Int byteLength = 0; // Required 217 | GLTF_Int byteStride = 0; 218 | GLTF_BufferViewTarget target = GLTF_BUFFER_INVALID; 219 | UT_String name = ""; 220 | // extensions 221 | // extras 222 | }; 223 | 224 | struct GLTF_API GLTF_Orthographic 225 | { 226 | fpreal32 xmag; 227 | fpreal32 ymag; 228 | fpreal32 zmag; 229 | fpreal32 znear; 230 | // extensions 231 | // 232 | }; 233 | 234 | struct GLTF_API GLTF_Perspsective 235 | { 236 | UT_Optional aspectRatio; 237 | fpreal32 yfov; 238 | UT_Optional zmag; 239 | fpreal32 znear; 240 | // extensions 241 | // extras 242 | }; 243 | 244 | struct GLTF_API GLTF_Camera 245 | { 246 | UT_Optional orthographic; 247 | UT_Optional perspective; 248 | UT_String type = ""; 249 | UT_String name = ""; 250 | // extensions 251 | // extras 252 | }; 253 | 254 | struct GLTF_API GLTF_Channel 255 | { 256 | GLTF_Handle sampler; 257 | GLTF_Handle target; 258 | // extensions 259 | // extras 260 | }; 261 | 262 | struct GLTF_API GLTF_Images 263 | { 264 | UT_String uri; 265 | UT_String mimeType; 266 | UT_String name; 267 | // extensions 268 | // extras 269 | }; 270 | 271 | struct GLTF_API GLTF_Indices 272 | { 273 | GLTF_Handle bufferView; 274 | GLTF_Int byteOffset; 275 | GLTF_ComponentType componentType; 276 | // extensions 277 | // extras 278 | }; 279 | 280 | struct GLTF_API GLTF_PBRMetallicRoughness 281 | { 282 | UT_Vector4 baseColorFactor = {0.0f, 0.0f, 0.0f, 1.0f}; 283 | UT_Optional baseColorTexture; 284 | fpreal32 metallicFactor = 1.0f; 285 | fpreal32 roughnessFactor = 1.0f; 286 | UT_Optional metallicRoughnessTexture; 287 | }; 288 | 289 | struct GLTF_API GLTF_Material 290 | { 291 | UT_String name = ""; 292 | // extensions 293 | // extras 294 | UT_Optional metallicRoughness; 295 | UT_Optional normalTexture; 296 | UT_Optional occlusionTexture; 297 | UT_Optional emissiveTexture; 298 | UT_Vector3F emissiveFactor = {0.0f, 0.0f, 0.0f}; 299 | UT_String alphaMode; 300 | fpreal32 alphaCutoff; 301 | bool doubleSided; 302 | }; 303 | 304 | struct GLTF_API GLTF_Primitive 305 | { 306 | UT_StringMap attributes; 307 | GLTF_Handle indices = GLTF_INVALID_IDX; 308 | GLTF_Handle material = GLTF_INVALID_IDX; 309 | GLTF_RenderMode mode = GLTF_RENDERMODE_TRIANGLES; 310 | // targets 311 | // extensions 312 | // extras 313 | }; 314 | 315 | struct GLTF_API GLTF_Mesh 316 | { 317 | UT_Array primitives; 318 | // weights 319 | UT_String name; 320 | // extensions 321 | // extras 322 | }; 323 | 324 | struct GLTF_API GLTF_Sampler 325 | { 326 | GLTF_TexFilter magfilter; 327 | GLTF_TexFilter minFilter; 328 | GLTF_TexWrap wrapS; 329 | GLTF_TexWrap wrapT; 330 | UT_String name; 331 | // extensions 332 | // extras 333 | }; 334 | 335 | struct GLTF_API GLTF_Image 336 | { 337 | UT_String uri = ""; 338 | UT_String mimeType = ""; 339 | GLTF_Handle bufferView = GLTF_INVALID_IDX; 340 | UT_String name = ""; 341 | // extensions 342 | // extras 343 | }; 344 | 345 | struct GLTF_API GLTF_Node 346 | { 347 | GLTF_Handle camera = GLTF_INVALID_IDX; 348 | UT_Array children; 349 | GLTF_Handle skin = GLTF_INVALID_IDX; 350 | UT_Matrix4 matrix = UT_Matrix4::getIdentityMatrix(); 351 | GLTF_Handle mesh = GLTF_INVALID_IDX; 352 | UT_Vector4 rotation = {0, 0, 0, 1}; 353 | UT_Vector3 scale = {1, 1, 1}; 354 | UT_Vector3 translation = {0, 0, 0}; 355 | // weights 356 | UT_String name = ""; 357 | // Extensions 358 | // Extras 359 | 360 | // GLTF standard specifies that only a Matrix OR TRS transform 361 | // is stored. 362 | GLTF_TRANSFORM_TYPE getTransformType() const; 363 | void getTransformAsMatrix(UT_Matrix4F &mat) const; 364 | }; 365 | 366 | struct GLTF_API GLTF_Scene 367 | { 368 | UT_Array nodes; 369 | UT_String name = ""; 370 | // extensions 371 | // extras 372 | }; 373 | 374 | struct GLTF_API GLTF_Skin 375 | { 376 | // inverse bind matrices 377 | // skeleton 378 | // joins 379 | // extensions 380 | // extras 381 | }; 382 | 383 | struct GLTF_API GLTF_Sparse 384 | { 385 | // count 386 | // indices 387 | // values 388 | // extensions 389 | // extras 390 | }; 391 | 392 | struct GLTF_API GLTF_Target 393 | { 394 | GLTF_Node *node; 395 | UT_String path; 396 | // extensions 397 | // extras 398 | }; 399 | 400 | struct GLTF_API GLTF_Texture 401 | { 402 | uint32 sampler = GLTF_INVALID_IDX; 403 | uint32 source = GLTF_INVALID_IDX; 404 | UT_String name = ""; 405 | // extensions 406 | // extras 407 | }; 408 | 409 | } // end GLTF_NAMESPACE 410 | 411 | #endif 412 | -------------------------------------------------------------------------------- /src/GLTF/GLTF_Util.C: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) COPYRIGHTYEAR 3 | * Side Effects Software Inc. All rights reserved. 4 | * 5 | * Redistribution and use of Houdini Development Kit samples in source and 6 | * binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. The name of Side Effects Software may not be used to endorse or 11 | * promote products derived from this software without specific prior 12 | * written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 17 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 20 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | *---------------------------------------------------------------------------- 26 | */ 27 | 28 | #include "GLTF_Cache.h" 29 | #include "GLTF_Loader.h" 30 | #include "GLTF_Util.h" 31 | 32 | #include 33 | #include 34 | 35 | using namespace GLTF_NAMESPACE; 36 | 37 | const char GLTF_API * 38 | GLTF_Util::typeGetName(GLTF_Type type) 39 | { 40 | switch (type) 41 | { 42 | case GLTF_Type::GLTF_TYPE_SCALAR: 43 | return "SCALAR"; 44 | case GLTF_Type::GLTF_TYPE_MAT2: 45 | return "MAT2"; 46 | case GLTF_Type::GLTF_TYPE_MAT3: 47 | return "MAT3"; 48 | case GLTF_Type::GLTF_TYPE_MAT4: 49 | return "MAT4"; 50 | case GLTF_Type::GLTF_TYPE_VEC2: 51 | return "VEC2"; 52 | case GLTF_Type::GLTF_TYPE_VEC3: 53 | return "VEC3"; 54 | case GLTF_Type::GLTF_TYPE_VEC4: 55 | return "VEC4"; 56 | case GLTF_Type::GLTF_TYPE_INVALID: 57 | return nullptr; 58 | } 59 | 60 | return ""; 61 | } 62 | 63 | GLTF_Int 64 | GLTF_Util::componentTypeGetBytes(GLTF_ComponentType type) 65 | { 66 | switch (type) 67 | { 68 | case GLTF_ComponentType::GLTF_COMPONENT_BYTE: 69 | case GLTF_ComponentType::GLTF_COMPONENT_UNSIGNED_BYTE: 70 | return 1; 71 | case GLTF_ComponentType::GLTF_COMPONENT_UNSIGNED_SHORT: 72 | case GLTF_ComponentType::GLTF_COMPONENT_SHORT: 73 | return 2; 74 | case GLTF_ComponentType::GLTF_COMPONENT_UNSIGNED_INT: 75 | case GLTF_ComponentType::GLTF_COMPONENT_FLOAT: 76 | return 4; 77 | case GLTF_ComponentType::GLTF_COMPONENT_INVALID: 78 | return 0; 79 | default: 80 | return 0; 81 | } 82 | } 83 | 84 | GLTF_Int 85 | GLTF_Util::typeGetElements(GLTF_Type type) 86 | { 87 | switch (type) 88 | { 89 | case GLTF_Type::GLTF_TYPE_SCALAR: 90 | return 1; 91 | case GLTF_Type::GLTF_TYPE_VEC2: 92 | return 2; 93 | case GLTF_Type::GLTF_TYPE_VEC3: 94 | return 3; 95 | case GLTF_Type::GLTF_TYPE_VEC4: 96 | case GLTF_Type::GLTF_TYPE_MAT2: 97 | return 4; 98 | case GLTF_Type::GLTF_TYPE_MAT3: 99 | return 9; 100 | case GLTF_Type::GLTF_TYPE_MAT4: 101 | return 16; 102 | default: 103 | return 0; 104 | } 105 | } 106 | 107 | GLTF_Int 108 | GLTF_Util::getDefaultStride(GLTF_Type type, 109 | GLTF_ComponentType component_type) 110 | { 111 | return componentTypeGetBytes(component_type) * 112 | typeGetElements(type); 113 | } 114 | 115 | GLTF_Int 116 | GLTF_Util::getStride(uint32 previous_stride, GLTF_Type type, 117 | GLTF_ComponentType component_type) 118 | { 119 | if (previous_stride != 0) 120 | { 121 | return previous_stride; 122 | } 123 | return componentTypeGetBytes(component_type) * 124 | typeGetElements(type); 125 | } 126 | 127 | GLTF_Type 128 | GLTF_Util::getTypeForTupleSize(uint32 tuplesize) 129 | { 130 | switch (tuplesize) 131 | { 132 | case 1: 133 | return GLTF_Type::GLTF_TYPE_SCALAR; 134 | case 2: 135 | return GLTF_Type::GLTF_TYPE_VEC2; 136 | case 3: 137 | return GLTF_Type::GLTF_TYPE_VEC3; 138 | case 4: 139 | return GLTF_Type::GLTF_TYPE_VEC4; 140 | default: 141 | return GLTF_Type::GLTF_TYPE_INVALID; 142 | } 143 | } 144 | 145 | UT_Array 146 | GLTF_Util::getSceneList(const UT_String &filename) 147 | { 148 | UT_Array object_paths; 149 | 150 | auto loader = GLTF_Cache::GetInstance().LoadLoader(filename); 151 | if (!loader) 152 | return object_paths; 153 | 154 | for (const GLTF_Scene *scene : loader->getScenes()) 155 | { 156 | object_paths.append(UT_String(UT_String::ALWAYS_DEEP, scene->name)); 157 | } 158 | 159 | return object_paths; 160 | } 161 | 162 | bool 163 | GLTF_Util::DecomposeMatrixToTRS(const UT_Matrix4F &mat, 164 | UT_Vector3F &translation, 165 | UT_Quaternion &rotation, UT_Vector3F scale) 166 | { 167 | if (mat.determinant() != 0.0) 168 | { 169 | return false; 170 | } 171 | 172 | UT_Vector3D translationD; 173 | UT_Vector3D scaleD; 174 | UT_Vector3D euler_rotation; 175 | UT_Vector3D shears; 176 | UT_XformOrder rotorder; 177 | 178 | mat.explode(UT_XformOrder::TRS, euler_rotation, scaleD, translationD, 179 | shears); 180 | 181 | const fpreal64 EPSILON = 0.000001; 182 | if (shears.length() > EPSILON) 183 | { 184 | return false; 185 | } 186 | 187 | translation = translationD; 188 | rotation.updateFromEuler(euler_rotation, rotorder); 189 | scale = scaleD; 190 | 191 | return true; 192 | } 193 | -------------------------------------------------------------------------------- /src/GLTF/GLTF_Util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) COPYRIGHTYEAR 3 | * Side Effects Software Inc. All rights reserved. 4 | * 5 | * Redistribution and use of Houdini Development Kit samples in source and 6 | * binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. The name of Side Effects Software may not be used to endorse or 11 | * promote products derived from this software without specific prior 12 | * written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 17 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 20 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | *---------------------------------------------------------------------------- 26 | */ 27 | 28 | #ifndef __SOP_GLTFUTIL_H__ 29 | #define __SOP_GLTFUTIL_H__ 30 | 31 | #include "GLTF_API.h" 32 | #include "GLTF_Types.h" 33 | 34 | namespace GLTF_NAMESPACE 35 | { 36 | 37 | class GLTF_API GLTF_Util 38 | { 39 | public: 40 | template 41 | static T 42 | readInterleavedElement(unsigned char *data, uint32 stride, uint32 index) 43 | { 44 | return *reinterpret_cast(data + stride * index); 45 | } 46 | 47 | static const char *typeGetName(GLTF_Type type); 48 | static GLTF_Int componentTypeGetBytes(GLTF_ComponentType type); 49 | static GLTF_Int typeGetElements(GLTF_Type type); 50 | static GLTF_Int 51 | getDefaultStride(GLTF_Type type, GLTF_ComponentType component_type); 52 | 53 | /// 54 | /// Returns 0 if previous_stride == 0 and GetDefaultStride otherwise. 55 | /// 56 | static GLTF_Int getStride(uint32 previous_stride, GLTF_Type type, 57 | GLTF_ComponentType component_type); 58 | 59 | static GLTF_Type getTypeForTupleSize(uint32 tuplesize); 60 | 61 | /// 62 | /// Returns a list of the scene names in the given filename, 63 | /// where the index in the returned array corrosponds to the 64 | /// scene index, and the value corrosponds to the name if one 65 | /// exists, and "" othewise. 66 | /// 67 | static UT_Array getSceneList(const UT_String &filename); 68 | 69 | static bool 70 | DecomposeMatrixToTRS(const UT_Matrix4F &mat, UT_Vector3F &translation, 71 | UT_Quaternion &rotation, UT_Vector3F scale); 72 | }; 73 | 74 | } // end GLTF_NAMESPACE 75 | 76 | #endif 77 | -------------------------------------------------------------------------------- /src/GLTF/Makefile: -------------------------------------------------------------------------------- 1 | # The HFS Environment variable needs to be set before calling make 2 | # Windows users should also define their MSVCDir environnment variable 3 | 4 | include ../CustomGLTF.global 5 | 6 | DSONAME = lib$(GLTFLIB).$(EXT) 7 | 8 | SOURCES = \ 9 | GLTF_Cache.C \ 10 | GLTF_Loader.C \ 11 | GLTF_GeoLoader.C \ 12 | GLTF_Types.C \ 13 | GLTF_Util.C 14 | 15 | INCDIRS = \ 16 | -I$(HFS)/toolkit/include 17 | 18 | include $(HFS)/toolkit/makefiles/Makefile.gnu 19 | 20 | HDEFINES += \ 21 | -DGLTF_EXPORTS \ 22 | -DGLTF_NAMESPACE=$(GLTFNAMESPACE) 23 | 24 | ifndef WINDOWS 25 | # Additional Houdini libs 26 | LIBDIRS += -L$(HFS)/dsolib 27 | LIBS += -lHoudiniGEO -lHoudiniUT 28 | endif 29 | 30 | # Change from -bundle for core lib 31 | ifdef MBSD 32 | SHAREDFLAG = -dynamiclib 33 | endif 34 | -------------------------------------------------------------------------------- /src/HOM/HOM_GLTF.C: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) COPYRIGHTYEAR 3 | * Side Effects Software Inc. All rights reserved. 4 | * 5 | * Redistribution and use of Houdini Development Kit samples in source and 6 | * binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. The name of Side Effects Software may not be used to endorse or 11 | * promote products derived from this software without specific prior 12 | * written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 17 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 20 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | *---------------------------------------------------------------------------- 26 | */ 27 | 28 | #include 29 | // This file contains functions that will run arbitrary Python code 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | 36 | #include 37 | #include 38 | 39 | static const char *Doc_GLTFClearCache = "Doc_GLTFClearCache(gltfPath)\n"; 40 | 41 | using namespace GLTF_NAMESPACE; 42 | 43 | static PY_PyObject * 44 | Py_ClearGLTFCache(PY_PyObject *self, PY_PyObject *file) 45 | { 46 | const char *file_str; 47 | if (!PY_PyArg_ParseTuple(file, "s", &file_str)) 48 | { 49 | PY_Py_RETURN_NONE; 50 | } 51 | 52 | GLTF_Cache::GetInstance().EvictLoader(file_str); 53 | PY_Py_RETURN_NONE; 54 | } 55 | 56 | static const char *Doc_GLTFGetSceneList = "Doc_GLTFGetSceneNames(filename)\n" 57 | "\n"; 58 | 59 | PY_PyObject * 60 | Py_GLTFGetSceneList(PY_PyObject *self, PY_PyObject *file) 61 | { 62 | const char *file_str; 63 | if (!PY_PyArg_ParseTuple(file, "s", &file_str)) 64 | { 65 | PY_Py_RETURN_NONE; 66 | } 67 | 68 | UT_Array scene_list = GLTF_Util::getSceneList(file_str); 69 | 70 | exint num_scenes = scene_list.size(); 71 | PY_PyObject *result = PY_PyList_New(num_scenes * 2); 72 | 73 | for (exint i = 0; i < num_scenes; ++i) 74 | { 75 | std::string scene_name = scene_list[i].c_str(); 76 | if (scene_name == "") 77 | { 78 | scene_name = "Scene " + std::to_string(i + 1); 79 | } 80 | 81 | std::string index_string = std::to_string(i); 82 | PY_PyObject *pyname = PY_PyString_FromString(scene_name.c_str()); 83 | PY_PyObject *py_scene_idx = PY_PyString_FromString(index_string.c_str()); 84 | PY_PyList_SetItem(result, 2 * i, py_scene_idx); 85 | PY_PyList_SetItem(result, 2 * i + 1, pyname); 86 | } 87 | 88 | return result; 89 | } 90 | 91 | void 92 | HOMextendLibrary() 93 | { 94 | { 95 | // A PY_InterpreterAutoLock will grab the Python global interpreter 96 | // lock (GIL). It's important that we have the GIL before making 97 | // any calls into the Python API. 98 | PY_InterpreterAutoLock interpreter_auto_lock; 99 | 100 | static PY_PyMethodDef gltf_hom_extension_methods[] = { 101 | {"gltfClearCache", Py_ClearGLTFCache, PY_METH_VARARGS(), 102 | Doc_GLTFClearCache}, 103 | 104 | {"gltfGetSceneList", Py_GLTFGetSceneList, PY_METH_VARARGS(), 105 | Doc_GLTFGetSceneList}, 106 | 107 | {NULL, NULL, 0, NULL}}; 108 | 109 | PY_Py_InitModule("_gltf_hom_extensions", gltf_hom_extension_methods); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/HOM/Makefile: -------------------------------------------------------------------------------- 1 | # The HFS Environment variable needs to be set before calling make 2 | # Windows users should also define their MSVCDir environnment variable 3 | 4 | include ../CustomGLTF.global 5 | 6 | DSONAME = $(HOM_LIB).$(EXT) 7 | 8 | # Custom GLTF library 9 | CUSTOM_GLTF = ".." 10 | 11 | SOURCES = HOM_GLTF.C 12 | 13 | INCDIRS = \ 14 | -I$(CUSTOM_GLTF) \ 15 | -I$(HFS)/toolkit/include \ 16 | -I$(HFS)/python27/include 17 | 18 | ifdef WINDOWS 19 | LIBDIRS += -LIBPATH:$(CUSTOM_GLTF)/GLTF -LIBPATH:$(HFS)/python27/libs 20 | LIBS += lib$(GLTFLIB).lib 21 | else 22 | LIBDIRS += -L$(CUSTOM_GLTF)/GLTF -L$(HFS)/python27/libs 23 | LIBS += -l$(GLTFLIB) 24 | endif 25 | 26 | include $(HFS)/toolkit/makefiles/Makefile.gnu 27 | 28 | HDEFINES += \ 29 | -DGLTF_EXPORTS \ 30 | -DGLTF_NAMESPACE=$(GLTFNAMESPACE) 31 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # The HFS Environment variable needs to be set before calling make 2 | # Windows users should also define their MSVCDir environnment varaibale 3 | 4 | .PHONY: gltf sop rop hom all clean 5 | 6 | 7 | gltf: 8 | @$(MAKE) -C GLTF 9 | 10 | sop: gltf 11 | @$(MAKE) -C SOP 12 | 13 | rop: gltf 14 | @$(MAKE) -C ROP 15 | 16 | hom: gltf 17 | @$(MAKE) -C HOM 18 | 19 | all: gltf sop rop hom 20 | 21 | clean: 22 | @$(MAKE) -C GLTF clean 23 | @$(MAKE) -C SOP clean 24 | @$(MAKE) -C ROP clean 25 | @$(MAKE) -C HOM clean -------------------------------------------------------------------------------- /src/ROP/Makefile: -------------------------------------------------------------------------------- 1 | # The HFS Environment variable needs to be set before calling make 2 | # Windows users should also define their MSVCDir environnment variable 3 | 4 | include ../CustomGLTF.global 5 | 6 | DSONAME = $(ROPLIB).$(EXT) 7 | 8 | # Custom GLTF library 9 | CUSTOM_GLTF = ".." 10 | 11 | SOURCES = \ 12 | ROP_GLTF.C \ 13 | ROP_GLTF_ExportRoot.C \ 14 | ROP_GLTF_Image.C \ 15 | ROP_GLTF_Refiner.C 16 | 17 | INCDIRS = \ 18 | -I$(CUSTOM_GLTF) \ 19 | -I$(HFS)/toolkit/include 20 | 21 | ifdef WINDOWS 22 | LIBDIRS += -LIBPATH:$(CUSTOM_GLTF)/GLTF 23 | LIBS += lib$(GLTFLIB).lib 24 | else 25 | LIBDIRS += -L$(CUSTOM_GLTF)/GLTF 26 | LIBS += -l$(GLTFLIB) 27 | endif 28 | 29 | include $(HFS)/toolkit/makefiles/Makefile.gnu 30 | 31 | HDEFINES += \ 32 | -DGLTF_EXPORTS \ 33 | -DGLTF_NAMESPACE=$(GLTFNAMESPACE) \ 34 | -DCUSTOM_GLTF_TOKEN_PREFIX='$(TOKEN_PREFIX)' \ 35 | -DCUSTOM_GLTF_LABEL_PREFIX='$(LABEL_PREFIX)' 36 | -------------------------------------------------------------------------------- /src/ROP/ROP_GLTF.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 3 | * Side Effects Software Inc. All rights reserved. 4 | * 5 | * Redistribution and use of Houdini Development Kit samples in source and 6 | * binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. The name of Side Effects Software may not be used to endorse or 11 | * promote products derived from this software without specific prior 12 | * written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 17 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 20 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #ifndef __ROP_GLTF_h__ 27 | #define __ROP_GLTF_h__ 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | using GLTF_NAMESPACE::GLTF_Node; 34 | using GLTF_NAMESPACE::GLTF_TextureInfo; 35 | using GLTF_NAMESPACE::GLTF_Scene; 36 | 37 | typedef GLTF_NAMESPACE::GLTF_Handle GLTF_Handle; 38 | 39 | struct ROP_GLTF_ChannelMapping; 40 | struct ROP_GLTF_ImgExportParms; 41 | class ROP_GLTF_ExportRoot; 42 | class ROP_GLTF_ErrorManager; 43 | class OBJ_Geometry; 44 | 45 | class ROP_GLTF_BaseErrorManager 46 | { 47 | public: 48 | virtual ~ROP_GLTF_BaseErrorManager() = default; 49 | virtual void AddError(int code, const char *msg = 0) const = 0; 50 | virtual void AddWarning(int code, const char *msg = 0) const = 0; 51 | }; 52 | 53 | struct ROP_GLTF_TextureParms 54 | { 55 | public: 56 | bool myFlipGreenChannel = false; 57 | }; 58 | 59 | class ROP_GLTF : public ROP_Node 60 | { 61 | public: 62 | static OP_Node * 63 | myConstructor(OP_Network *net, const char *name, OP_Operator *op); 64 | 65 | /// 66 | /// Simply wraps ROP_GLTF so that the error manager on Node 67 | /// can be used in external classes. 68 | /// 69 | class ROP_GLTF_ErrorManager : public ROP_GLTF_BaseErrorManager 70 | { 71 | public: 72 | ROP_GLTF_ErrorManager(ROP_GLTF &gltf); 73 | ROP_GLTF_ErrorManager(ROP_GLTF_ErrorManager &gltf) = delete; 74 | ROP_GLTF_ErrorManager(ROP_GLTF_ErrorManager &&gltf) = delete; 75 | virtual ~ROP_GLTF_ErrorManager() = default; 76 | virtual void AddError(int code, const char *msg = 0) const; 77 | virtual void AddWarning(int code, const char *msg = 0) const; 78 | 79 | private: 80 | ROP_GLTF &myNode; 81 | }; 82 | 83 | protected: 84 | ROP_GLTF(OP_Network *net, const char *name, OP_Operator *entry); 85 | virtual bool updateParmsFlags(); 86 | virtual ~ROP_GLTF(); 87 | 88 | // From ROP_Node 89 | virtual int startRender(int nframes, fpreal s, fpreal e); 90 | virtual ROP_RENDER_CODE renderFrame(fpreal time, UT_Interrupt *boss); 91 | virtual ROP_RENDER_CODE endRender(); 92 | 93 | void OUTPUT_FILE(UT_String &str, fpreal t) const 94 | { 95 | evalString(str, "file", 0, t); 96 | } 97 | void OBJPATH(UT_String &str, fpreal t) const 98 | { 99 | evalString(str, "objpath", 0, t); 100 | } 101 | void OBJECTS(UT_String &str, fpreal time) const 102 | { 103 | evalString(str, "objects", 0, time); 104 | } 105 | bool EXPORT_MATERIALS(fpreal time) const 106 | { 107 | return evalInt("exportmaterials", 0, time) != 0; 108 | } 109 | bool EXPORT_CUSTOM_ATTRIBS(fpreal time) const 110 | { 111 | return evalInt("customattribs", 0, time) != 0; 112 | } 113 | bool EXPORT_NAMES(fpreal time) const 114 | { 115 | return evalInt("exportnames", 0, time) != 0; 116 | } 117 | void IMAGEFORMAT(UT_String &str, fpreal time) const 118 | { 119 | evalString(str, "imageformat", 0, time); 120 | } 121 | void MAXRES(UT_String &str, fpreal time) const 122 | { 123 | evalString(str, "maxresolution", 0, time); 124 | } 125 | void EXPORTTYPE(UT_String &str, fpreal time) const 126 | { 127 | evalString(str, "exporttype", 0, time); 128 | } 129 | bool CULL_EMPTY_NODES(fpreal time) const 130 | { 131 | return evalInt("cullempty", 0, time) != 0; 132 | } 133 | bool POWER_OF_TWO(fpreal time) const 134 | { 135 | return evalInt("poweroftwo", 0, time) != 0; 136 | } 137 | int IMAGE_QUALITY(fpreal time) const 138 | { 139 | return evalInt("imagequality", 0, time); 140 | } 141 | bool SAVE_HIDDEN(fpreal time) const 142 | { 143 | return evalInt("savehidden", 0, time) != 0; 144 | } 145 | bool USE_SOP_PATH(fpreal time) const 146 | { 147 | return evalInt("usesoppath", 0, time) != 0; 148 | } 149 | void SOP_PATH(UT_String &str, fpreal time) const 150 | { 151 | evalString(str, "soppath", 0, time); 152 | } 153 | bool FLIP_Y_NORMALS(fpreal time) const 154 | { 155 | return evalInt("flipnormalmapy", 0, time) != 0; 156 | } 157 | 158 | private: 159 | class GLTF_HierarchyBuilder 160 | { 161 | public: 162 | GLTF_HierarchyBuilder( 163 | OP_Node *root_node, GLTF_Node *root_gltf, 164 | ROP_GLTF_ExportRoot &export_root, 165 | std::function proc_func); 166 | 167 | uint32 Traverse(OBJ_Node *node, fpreal time); 168 | 169 | private: 170 | const OP_Node *myRootNode; 171 | ROP_GLTF_ExportRoot &myRootExporter; 172 | GLTF_Node *myRootGLTF; 173 | UT_Map myNodeMap; 174 | const std::function myFunc; 175 | }; 176 | 177 | const IMG_Format *GetImageFormat(fpreal time) const; 178 | const char *GetImageMimeType(fpreal time) const; 179 | 180 | // Takes a glTF node and a Houdini node, and assigns the transformation 181 | // of the Houdini node to the glTF node. 182 | void AssignGLTFTransform(GLTF_Node &gltf_node, OBJ_Node *node, 183 | fpreal time) const; 184 | void 185 | AssignGLTFName(GLTF_Node &gltf_node, OBJ_Node *node, fpreal time) const; 186 | 187 | // Creates a GLTF_Mesh mesh from *node and assigns it to the glTF node. 188 | // If the sop is not null, then it will pull geometry from *sop. Otherwise 189 | // it will pull geometry from the current node being rendered. 190 | void SetupGLTFMesh(GLTF_Node &gltf_node, OBJ_Node *node, fpreal time, 191 | SOP_Node *sop = nullptr); 192 | 193 | // Translation of Houdini principled shader parameters -> GLTF material 194 | // Parameters 195 | uint32 TranslatePrincipledShader(const OP_Context &context, 196 | const OP_Node *ps_node); 197 | 198 | // As we are pulling from multiple directories and outputting 199 | // the images in a single directory, there is a possiblity of 200 | // name collisions. This numbers files with the same name 201 | // in an arbitrary order. 202 | void GetNonCollidingName(const UT_String &s, UT_String &o); 203 | 204 | SOP_Node *GetSOPNode(fpreal time) const; 205 | bool hasSOPInput(fpreal time) const; 206 | 207 | // For handling textures with input from only a single channel 208 | bool 209 | TranslateTexture(const UT_String &path, const OP_Context &context, 210 | GLTF_TextureInfo &tex_info, 211 | const ROP_GLTF_TextureParms &tex_parms = {}); 212 | 213 | // For handling textures with input from multiple channels 214 | bool 215 | TranslateTexture(const UT_Array &mappings, 216 | const OP_Context &context, GLTF_TextureInfo &tex_info, 217 | const ROP_GLTF_TextureParms &tex_parms = {}); 218 | 219 | uint32 220 | OutputTexture(const UT_String &output_path, const ROP_GLTF_TextureParms &parms, 221 | std::function output_function, 223 | GLTF_TextureInfo &tex_info, const OP_Context &context); 224 | 225 | GLTF_Scene &InitializeBasicGLTFScene(GLTF_Handle &root_scene_idx); 226 | 227 | bool IsExportingGLB() const; 228 | const UT_String &GetBasePath() const; 229 | 230 | bool WriteTreeToDisk(fpreal time); 231 | void InitializeGLTFTree(fpreal time); 232 | 233 | bool BuildGLTFTree(fpreal time); 234 | bool BuildFromSOP(fpreal time, SOP_Node *sop, OBJ_Node *geo); 235 | bool BuildGLTFTreeFromHierarchy(OP_Node *root_node, OP_Bundle *bundle, 236 | fpreal time); 237 | 238 | //////////////////////////////////////////// 239 | 240 | UT_UniquePtr myRoot; 241 | fpreal myEndTime; 242 | fpreal myStartTime; 243 | bool myExportingGLB; 244 | UT_String myFilename; 245 | UT_String myBasepath; 246 | ROP_GLTF_ErrorManager *myErrorHandler; 247 | }; 248 | 249 | #endif 250 | -------------------------------------------------------------------------------- /src/ROP/ROP_GLTF_ExportRoot.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 3 | * Side Effects Software Inc. All rights reserved. 4 | * 5 | * Redistribution and use of Houdini Development Kit samples in source and 6 | * binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. The name of Side Effects Software may not be used to endorse or 11 | * promote products derived from this software without specific prior 12 | * written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 17 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 20 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #ifndef __ROP_GLTF_EXPORTROOT_h__ 27 | #define __ROP_GLTF_EXPORTROOT_h__ 28 | 29 | #include "ROP_GLTF_Image.h" 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | 37 | #include 38 | 39 | constexpr const char *GENERATOR_STRING = "Houdini GLTF 2.0 Exporter"; 40 | constexpr const char *GLTF_VERSION = "2.0"; 41 | 42 | class UT_WorkBuffer; 43 | class UT_JSONWriter; 44 | class UT_OFStream; 45 | class OP_Node; 46 | struct ROP_GLTF_ChannelMapping; 47 | 48 | typedef GLTF_NAMESPACE::GLTF_Accessor GLTF_Accessor; 49 | typedef GLTF_NAMESPACE::GLTF_Animation GLTF_Animation; 50 | typedef GLTF_NAMESPACE::GLTF_Asset GLTF_Asset; 51 | typedef GLTF_NAMESPACE::GLTF_Buffer GLTF_Buffer; 52 | typedef GLTF_NAMESPACE::GLTF_BufferView GLTF_BufferView; 53 | typedef GLTF_NAMESPACE::GLTF_Camera GLTF_Camera; 54 | typedef GLTF_NAMESPACE::GLTF_Image GLTF_Image; 55 | typedef GLTF_NAMESPACE::GLTF_Material GLTF_Material; 56 | typedef GLTF_NAMESPACE::GLTF_Mesh GLTF_Mesh; 57 | typedef GLTF_NAMESPACE::GLTF_Node GLTF_Node; 58 | typedef GLTF_NAMESPACE::GLTF_Scene GLTF_Scene; 59 | typedef GLTF_NAMESPACE::GLTF_Sampler GLTF_Sampler; 60 | typedef GLTF_NAMESPACE::GLTF_Skin GLTF_Skin; 61 | typedef GLTF_NAMESPACE::GLTF_Texture GLTF_Texture; 62 | typedef GLTF_NAMESPACE::GLTF_Material GLTF_Material; 63 | 64 | typedef GLTF_NAMESPACE::GLTF_Int GLTF_Int; 65 | typedef GLTF_NAMESPACE::GLTF_Offset GLTF_Offset; 66 | typedef GLTF_NAMESPACE::GLTF_Handle GLTF_Handle; 67 | 68 | 69 | class ROP_GLTF_ExportRoot 70 | { 71 | public: 72 | struct ExportSettings 73 | { 74 | bool exportNames = false; 75 | }; 76 | 77 | ROP_GLTF_ExportRoot(ExportSettings s); 78 | ~ROP_GLTF_ExportRoot(); 79 | 80 | bool HasCachedChannelImage( 81 | const UT_Array &mapping) const; 82 | GLTF_Handle 83 | GetCachedChannelImage(const UT_Array &mapping); 84 | void 85 | InsertCachedChannelImage(const UT_Array &mapping, 86 | GLTF_Handle idx); 87 | 88 | UT_Map &GetImageCache(); 89 | UT_Map &GetMaterialCache(); 90 | 91 | /// This keeps track of the amount of times a specific filename 92 | /// outputted to to avoid name collisions 93 | UT_Map &GetNameUsagesMap(); 94 | 95 | /// 96 | /// Allocates additional space in the buffer in index bid. 97 | /// TODO: stop allocating from reallocing space. 98 | /// 99 | void *BufferAlloc(GLTF_Handle bid, GLTF_Offset bytes, GLTF_Offset alignment, 100 | GLTF_Offset &offset); 101 | 102 | /// 103 | /// Returns a reference to the internal root GLTF object 104 | /// 105 | GLTF_NAMESPACE::GLTF_Loader &loader(); 106 | 107 | /// 108 | /// Exports this structure as a GLTF file. 109 | /// 110 | bool ExportGLTF(const UT_String &path); 111 | 112 | /// 113 | /// Exports the file as GLB on os. If a uri is defined on buffer 0, 114 | /// it will be ignored. Otherwise, there are no modifications to the 115 | /// outputted JSON. 116 | /// 117 | bool ExportAsGLB(const UT_String &path); 118 | void SerializeJSON(UT_JSONWriter &writer); 119 | 120 | // 121 | // Convenience functions: Will return the index of the created 122 | // node in the parameter idx. 123 | // 124 | GLTF_Accessor &CreateAccessor(GLTF_Handle &idx); 125 | GLTF_Buffer &CreateBuffer(GLTF_Handle &idx); 126 | GLTF_BufferView &CreateBufferview(GLTF_Handle &idx); 127 | GLTF_Node &CreateNode(GLTF_Handle &idx); 128 | GLTF_Mesh &CreateMesh(GLTF_Handle &idx); 129 | GLTF_Scene &CreateScene(GLTF_Handle &idx); 130 | GLTF_Image &CreateImage(GLTF_Handle &idx); 131 | GLTF_Texture &CreateTexture(GLTF_Handle &idx); 132 | GLTF_Material &CreateMaterial(GLTF_Handle &idx); 133 | 134 | GLTF_Accessor *getAccessor(GLTF_Handle idx); 135 | GLTF_Animation *getAnimation(GLTF_Handle idx); 136 | GLTF_Asset getAsset(); 137 | GLTF_Buffer *getBuffer(GLTF_Handle idx); 138 | GLTF_BufferView *getBufferView(GLTF_Handle idx); 139 | GLTF_Camera *getCamera(GLTF_Handle idx); 140 | GLTF_Image *getImage(GLTF_Handle idx); 141 | GLTF_Material *getMaterial(GLTF_Handle idx); 142 | GLTF_Mesh *getMesh(GLTF_Handle idx); 143 | GLTF_Node *getNode(GLTF_Handle idx); 144 | GLTF_Sampler *getSampler(GLTF_Handle idx); 145 | GLTF_Handle getDefaultScene(); 146 | GLTF_Scene *getScene(GLTF_Handle idx); 147 | GLTF_Skin *getSkin(GLTF_Handle idx); 148 | GLTF_Texture *getTexture(GLTF_Handle idx); 149 | 150 | void SetDefaultScene(GLTF_Handle idx); 151 | 152 | 153 | const UT_Array &getAccessors() const; 154 | const UT_Array &getAnimations() const; 155 | const UT_Array &getBuffers() const; 156 | const UT_Array &getBufferViews() const; 157 | const UT_Array &getCameras() const; 158 | const UT_Array &getImages() const; 159 | const UT_Array &getMaterials() const; 160 | const UT_Array &getMeshes() const; 161 | const UT_Array &getNodes() const; 162 | const UT_Array &getSamplers() const; 163 | const UT_Array &getScenes() const; 164 | const UT_Array &getSkins() const; 165 | const UT_Array &getTextures() const; 166 | 167 | private: 168 | void 169 | OutputName(UT_JSONWriter &writer, const char *string, const char *value); 170 | 171 | void SerializeAsset(UT_JSONWriter &writer); 172 | void SerializeAccessors(UT_JSONWriter &writer); 173 | void SerializeBuffers(UT_JSONWriter &writer); 174 | void SerializeBufferViews(UT_JSONWriter &writer); 175 | void SerializeNodes(UT_JSONWriter &writer); 176 | void SerializeMeshes(UT_JSONWriter &writer); 177 | void SerializeMaterials(UT_JSONWriter &writer); 178 | void SerializePrimitives(UT_JSONWriter &writer, 179 | const UT_Array &primitive); 180 | void SerializeTextures(UT_JSONWriter &writer); 181 | void SerializeImages(UT_JSONWriter &writer); 182 | void SerializeScenes(UT_JSONWriter &writer); 183 | 184 | bool OutputBuffer(const char *folder, GLTF_Handle buffer) const; 185 | void OutputGLBBuffer(UT_OFStream &stream) const; 186 | 187 | // Pre-output pass: these should be used only before outputting as they 188 | // may potentially invalidate handles 189 | void ResolveBufferLengths(); 190 | void RemoveEmptyBuffers(); 191 | 192 | // Any external files must be copied to the output folder as 193 | // GLTF is only required to support "http://" and relative URI 194 | void ConvertAbsolutePaths(const UT_String &base_path); 195 | 196 | // Returns true if opened output stream to given path. 197 | // Automatically creates directories in path. 198 | bool OpenFileStreamAtPath(const UT_String &path, UT_OFStream &os); 199 | 200 | UT_Array> myBufferData; 201 | 202 | UT_Map myNameUsagesMap; 203 | UT_Map myImageMap; 204 | UT_Map myMaterialMap; 205 | 206 | std::map, GLTF_Handle> 207 | myChannelImageMap; 208 | 209 | GLTF_NAMESPACE::GLTF_Loader myLoader; 210 | ExportSettings mySettings; 211 | }; 212 | 213 | #endif 214 | -------------------------------------------------------------------------------- /src/ROP/ROP_GLTF_Image.C: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 3 | * Side Effects Software Inc. All rights reserved. 4 | * 5 | * Redistribution and use of Houdini Development Kit samples in source and 6 | * binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. The name of Side Effects Software may not be used to endorse or 11 | * promote products derived from this software without specific prior 12 | * written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 17 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 20 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #include "ROP_GLTF_Image.h" 27 | 28 | #include "ROP_GLTF.h" 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | #include 41 | 42 | using namespace GLTF_NAMESPACE; 43 | 44 | // We use a set internal format when processing the pixels as all merged 45 | // rasters must have the same format. As jpeg & png both only support 8 bits 46 | // per color per pixel, we default to using RGB8. 47 | class theWorkFormat 48 | { 49 | public: 50 | static const PXL_DataFormat px_data_format = PXL_INT8; 51 | static const IMG_DataType img_data_format = IMG_INT8; 52 | static const IMG_ComponentOrder img_component_order = IMG_COMPONENT_RGBA; 53 | }; 54 | 55 | bool 56 | ROP_GLTF_Image::OutputImageToStream(const UT_String &filename, 57 | const IMG_Format *format, std::ostream &os, 58 | fpreal time, 59 | const ROP_GLTF_ImgExportParms &parms) 60 | { 61 | UT_Array> rasters; 62 | IMG_Stat stat; 63 | 64 | if (!GetImageRasters(filename, time, rasters, stat, true)) 65 | return false; 66 | 67 | if (rasters.size() == 0) 68 | return false; 69 | 70 | ApplyTransformations(stat, rasters, parms); 71 | 72 | IMG_FileParms file_parms; 73 | file_parms.setColorModel(IMG_RGBA); 74 | file_parms.setComponentOrder(theWorkFormat::img_component_order); 75 | file_parms.setDataType(theWorkFormat::img_data_format); 76 | file_parms.setOption("quality", std::to_string(parms.quality).c_str()); 77 | 78 | IMG_File *file = IMG_File::create(os, stat, &file_parms, format); 79 | 80 | if (!file) 81 | return false; 82 | 83 | UT_Array ptr_rasters; 84 | for (auto raster : rasters) 85 | ptr_rasters.append(raster.get()); 86 | 87 | if (!file->writeImages(ptr_rasters)) 88 | { 89 | file->close(); 90 | return false; 91 | } 92 | 93 | file->close(); 94 | return true; 95 | } 96 | 97 | bool 98 | ROP_GLTF_Image::CreateMappedTexture( 99 | const UT_Array &mappings, std::ostream &os, 100 | const IMG_Format *format, uint32 time, const ROP_GLTF_ImgExportParms &parms, 101 | ROP_GLTF_BaseErrorManager &errormgr) 102 | { 103 | if (mappings.size() == 0) 104 | return false; 105 | 106 | UT_Array>> rasters; 107 | 108 | for (const ROP_GLTF_ChannelMapping &mapping : mappings) 109 | { 110 | UT_Array> curmap_rasters; 111 | 112 | IMG_Stat stat; 113 | if (GetImageRasters(mapping.path, time, curmap_rasters, stat, false)) 114 | { 115 | rasters.append(std::move(curmap_rasters)); 116 | } 117 | else if(mapping.path != "") 118 | { 119 | UT_String error("Invalid texture specified."); 120 | error.append(mapping.path); 121 | errormgr.AddWarning(UT_ERROR_MESSAGE, error); 122 | } 123 | } 124 | 125 | if (rasters.size() == 0) 126 | return false; 127 | 128 | // Find the size of the largest raster 129 | int xres = 0; 130 | int yres = 0; 131 | for (auto it = rasters.begin(); it != rasters.end(); it++) 132 | { 133 | for (UT_SharedPtr raster : *it) 134 | { 135 | xres = std::max(static_cast(raster->getXres()), xres); 136 | yres = std::max(static_cast(raster->getYres()), yres); 137 | } 138 | } 139 | 140 | auto new_raster = UT_SharedPtr( 141 | new PXL_Raster(PACK_RGB, 142 | theWorkFormat::px_data_format, xres, yres)); 143 | 144 | new_raster->clear(); 145 | 146 | PXL_FillParms fill_parms; 147 | fill_parms.setSourceType(theWorkFormat::px_data_format); 148 | fill_parms.setDestType(theWorkFormat::px_data_format); 149 | fill_parms.setDestArea(0, 0, xres - 1, yres - 1); 150 | fill_parms.setSourceArea(0, 0, xres - 1, yres - 1); 151 | fill_parms.mySInc = 3; 152 | fill_parms.myDInc = 3; 153 | 154 | exint idx = 0; 155 | exint num_channels = 0; 156 | 157 | for (auto it = rasters.begin(); it != rasters.end(); it++) 158 | { 159 | const UT_Array> &planes = *it; 160 | if (planes.size() == 0 || 161 | planes[0]->getPacking() != PACK_RGB) 162 | { 163 | continue; 164 | } 165 | 166 | auto from_channel = mappings[idx].from_channel; 167 | auto to_channel = mappings[idx].to_channel; 168 | 169 | fill_parms.mySource = planes[0]->getPixel(0, 0, from_channel); 170 | fill_parms.myDest = new_raster->getPixel(0, 0, to_channel); 171 | 172 | PXL_Fill::fill(fill_parms); 173 | 174 | num_channels++; 175 | idx++; 176 | } 177 | 178 | if (num_channels == 0) 179 | return false; 180 | 181 | IMG_Stat stat(xres, yres, theWorkFormat::img_data_format, IMG_RGB); 182 | 183 | // Apply transformations 184 | UT_Array> planes { new_raster }; 185 | 186 | ApplyTransformations(stat, planes, parms); 187 | 188 | new_raster = planes[0]; 189 | 190 | // Save the file to our stream 191 | IMG_File *file; 192 | 193 | IMG_FileParms file_parms; 194 | file_parms.setColorModel(IMG_RGB); 195 | file_parms.setComponentOrder(theWorkFormat::img_component_order); 196 | file_parms.orientImage(IMG_ORIENT_LEFT_FIRST, IMG_ORIENT_BOTTOM_FIRST); 197 | file_parms.setInterleaved(IMG_INTERLEAVED); 198 | file_parms.setDataType(theWorkFormat::img_data_format); 199 | file_parms.setOption("quality", std::to_string(parms.quality).c_str()); 200 | 201 | file = IMG_File::create(os, stat, &file_parms, format); 202 | 203 | UT_Array new_raster_arr {new_raster.get()}; 204 | if (!file->writeImages(new_raster_arr)) 205 | { 206 | file->close(); 207 | return false; 208 | } 209 | file->close(); 210 | 211 | return true; 212 | } 213 | 214 | bool 215 | ROP_GLTF_Image::OutputImage(const UT_String &filename, const IMG_Format *format, 216 | std::ostream &os, fpreal time, 217 | const ROP_GLTF_ImgExportParms &parms, 218 | ROP_GLTF_BaseErrorManager &errormgr) 219 | { 220 | // Special case for not creating errors, since some HDAs may 221 | // enable the texture parameter but not actually specify a texture 222 | if (filename == "") 223 | return false; 224 | 225 | if (filename.startsWith("op:")) 226 | { 227 | OP_Node *node = OPgetDirector()->findNode(filename); 228 | if (!node) 229 | return false; 230 | 231 | COP2_Node *copnode = node->castToCOP2Node(); 232 | 233 | if (!copnode) 234 | return false; 235 | 236 | return OutputCopToStream(copnode, format, os, time, parms); 237 | } 238 | 239 | return OutputImageToStream(filename, format, os, time, parms); 240 | } 241 | 242 | bool 243 | ROP_GLTF_Image::GetImageRasters(const UT_StringHolder &filename, 244 | const uint32 time, 245 | UT_Array> &rasters, 246 | IMG_Stat &stat, bool include_alpha) 247 | { 248 | if (filename.isEmpty()) 249 | return false; 250 | 251 | if (filename.startsWith("op:")) 252 | { 253 | OP_Node *node = OPgetDirector()->findNode(filename); 254 | if (!node) 255 | return false; 256 | 257 | COP2_Node *copnode = node->castToCOP2Node(); 258 | 259 | if (!copnode) 260 | return false; 261 | 262 | if (!GetImageRastersFromCOP(copnode, time, rasters, stat, include_alpha)) 263 | return false; 264 | 265 | return true; 266 | } 267 | 268 | if (!GetImageRastersFromFile(filename, time, rasters, stat, include_alpha)) 269 | return false; 270 | 271 | return true; 272 | } 273 | 274 | bool 275 | ROP_GLTF_Image::GetImageRastersFromFile( 276 | const UT_StringHolder &filename, const uint32 time, 277 | UT_Array> &rasters, IMG_Stat &stat, 278 | bool include_alpha) 279 | { 280 | IMG_ColorModel img_model = IMG_RGB; 281 | if (include_alpha) 282 | { 283 | img_model = IMG_RGBA; 284 | } 285 | 286 | 287 | IMG_FileParms img_parms; 288 | img_parms.setColorModel(img_model); 289 | img_parms.setComponentOrder(theWorkFormat::img_component_order); 290 | img_parms.setInterleaved(IMG_INTERLEAVED); 291 | img_parms.setDataType(theWorkFormat::img_data_format); 292 | img_parms.selectPlaneNames("C"); 293 | 294 | IMG_File *file = IMG_File::open(filename, &img_parms); 295 | 296 | if (file == nullptr) 297 | return false; 298 | 299 | UT_Array ptr_rasters; 300 | if (!file->readImages(ptr_rasters)) 301 | { 302 | file->close(); 303 | return false; 304 | } 305 | 306 | file->close(); 307 | 308 | for (auto raster : ptr_rasters) 309 | rasters.append(UT_SharedPtr(raster)); 310 | 311 | stat = file->getStat(); 312 | 313 | return true; 314 | } 315 | 316 | bool 317 | ROP_GLTF_Image::GetImageRastersFromCOP( 318 | COP2_Node *node, const uint32 time, 319 | UT_Array> &rasters, 320 | IMG_Stat &stat, 321 | bool include_alpha) 322 | { 323 | IMG_ColorModel img_model = IMG_RGB; 324 | PXL_Packing pxl_model = PACK_RGB; 325 | if (include_alpha) 326 | { 327 | img_model = IMG_RGBA; 328 | pxl_model = PACK_RGBA; 329 | } 330 | 331 | 332 | COP2_ImageSource *is = node->getImageSource(); 333 | OP_Context context(time); 334 | 335 | if (!is) 336 | return false; 337 | 338 | short key; 339 | if (!is->open(key)) 340 | return false; 341 | 342 | // Automatically close the image fstream when exiting the function 343 | std::unique_ptr> 344 | image_source(std::move(is), [&](COP2_ImageSource *s) { s->close(key); }); 345 | 346 | // Handle color plane 347 | const TIL_Sequence *image_seq = image_source->getSequence(0.f); 348 | if (!image_seq) 349 | return false; 350 | 351 | TIL_Sequence seq = *image_seq; 352 | seq.setSingleImage(1); 353 | seq.setStart(1); 354 | seq.setLength(1); 355 | 356 | int xres, yres; 357 | seq.getRes(xres, yres); 358 | context.setRes(xres, yres); 359 | 360 | // Handle colour plane 361 | const TIL_Plane *const_color_plane = 362 | seq.getPlane(COP2_Node::getColorPlaneName()); 363 | 364 | UT_SharedPtr color_plane; 365 | if (const_color_plane) 366 | { 367 | color_plane = UT_SharedPtr(new TIL_Plane(*const_color_plane)); 368 | color_plane->setScoped(1); 369 | } 370 | 371 | UT_SharedPtr color_raster; 372 | 373 | stat = IMG_Stat(xres, yres, theWorkFormat::img_data_format, img_model); 374 | 375 | if (color_plane) 376 | { 377 | color_raster = UT_SharedPtr( 378 | new TIL_Raster(pxl_model, 379 | theWorkFormat::px_data_format, xres, yres), 380 | [=](TIL_Raster *r){} 381 | ); 382 | 383 | // Pack both A and C planes into a single raster 384 | if (!is->getImage(color_raster.get(), time, xres, yres, *color_plane, 0, 385 | 0, 0, xres-1 , yres -1, 1.0, true)) 386 | { 387 | return false; 388 | } 389 | 390 | rasters.append(color_raster); 391 | } 392 | 393 | return true; 394 | } 395 | 396 | void 397 | ROP_GLTF_Image::ApplyTransformations( 398 | IMG_Stat &stat, UT_Array> &rasters, 399 | const ROP_GLTF_ImgExportParms &parms) 400 | { 401 | if (parms.flipGreen) 402 | { 403 | for (auto raster : rasters) 404 | { 405 | auto packing = raster->getPacking(); 406 | if (packing != PACK_RGB && packing != PACK_RGBA && 407 | packing != PACK_RGB_NI && packing != PACK_RGBA_NI) 408 | { 409 | // We're not handling a RGB(A) image, time to give up 410 | continue; 411 | } 412 | 413 | exint inc = 1; 414 | if (packing == PACK_RGB) 415 | inc = 3; 416 | else if (packing == PACK_RGBA) 417 | inc = 4; 418 | // Otherwise the raster is interleaved so the increment is 1 419 | 420 | PXL_FillParms fill_parms; 421 | fill_parms.setDestType(raster->getFormat()); 422 | fill_parms.setDestArea(0, 0, stat.getXres() - 1, stat.getYres() - 1); 423 | fill_parms.myDInc = inc; 424 | fill_parms.myDest = raster->getPixel(0, 0, 1); 425 | fill_parms.myFillColor = 1; 426 | PXL_Fill::invert(fill_parms); 427 | } 428 | } 429 | 430 | auto xres = rasters[0]->getXres(); 431 | auto yres = rasters[0]->getYres(); 432 | if (parms.roundUpPowerOfTwo || xres > parms.max_res || yres > parms.max_res) 433 | { 434 | xres = std::min(NextPowerOfTwo(xres), parms.max_res); 435 | yres = std::min(NextPowerOfTwo(yres), parms.max_res); 436 | for (exint idx = 0; idx < rasters.size(); idx++) 437 | { 438 | auto dest = UT_SharedPtr(new PXL_Raster()); 439 | TIL_Raster::scaleRasterToSize(dest.get(), rasters[idx].get(), xres, yres); 440 | rasters[idx] = dest; 441 | } 442 | 443 | stat.setResolution(xres, yres); 444 | } 445 | } 446 | 447 | exint 448 | ROP_GLTF_Image::NextPowerOfTwo(exint num) 449 | { 450 | return static_cast( 451 | SYSpow(2, SYSceil(log2(static_cast(num))))); 452 | } 453 | 454 | bool 455 | ROP_GLTF_Image::OutputCopToStream(COP2_Node *node, const IMG_Format *format, 456 | std::ostream &os, fpreal time, 457 | const ROP_GLTF_ImgExportParms &parms) 458 | { 459 | UT_Array> rasters; 460 | IMG_Stat stat; 461 | 462 | if (!GetImageRastersFromCOP(node, time, rasters, stat, true)) 463 | return false; 464 | 465 | if (rasters.size() == 0) 466 | return false; 467 | 468 | ApplyTransformations(stat, rasters, parms); 469 | 470 | // glTF specifies that texel alpha values should not be premultiplied 471 | IMG_FileParms img_parms; 472 | img_parms.setColorModel(IMG_RGBA); 473 | img_parms.setComponentOrder(theWorkFormat::img_component_order); 474 | img_parms.setInterleaved(IMG_INTERLEAVED); 475 | img_parms.setDataType(theWorkFormat::img_data_format); 476 | 477 | if (!format->formatStoresColorSpace()) 478 | img_parms.adjustGammaForFormat(stat, format, IMG_DT_ANY); 479 | 480 | IMG_File *file = IMG_File::create(os, stat, &img_parms, format, 0, true); 481 | 482 | // Recreate the array of dumb pointers to interface with old code 483 | UT_Array ptr_rasters; 484 | 485 | for (auto sp_raster : rasters) 486 | ptr_rasters.append(sp_raster.get()); 487 | 488 | if (!file->writeImages(ptr_rasters)) 489 | { 490 | file->close(); 491 | return false; 492 | } 493 | 494 | file->close(); 495 | return true; 496 | } 497 | 498 | bool 499 | ROP_GLTF_ChannelMapping::operator<(const ROP_GLTF_ChannelMapping &r) const 500 | { 501 | if (path < r.path) 502 | return true; 503 | if (path > r.path) 504 | return false; 505 | 506 | if (from_channel < r.from_channel) 507 | return true; 508 | if (from_channel > r.from_channel) 509 | return false; 510 | 511 | if (to_channel < r.to_channel) 512 | return true; 513 | if (to_channel > r.to_channel) 514 | return false; 515 | 516 | return false; 517 | } 518 | -------------------------------------------------------------------------------- /src/ROP/ROP_GLTF_Image.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 3 | * Side Effects Software Inc. All rights reserved. 4 | * 5 | * Redistribution and use of Houdini Development Kit samples in source and 6 | * binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. The name of Side Effects Software may not be used to endorse or 11 | * promote products derived from this software without specific prior 12 | * written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 17 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 20 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #ifndef __ROP_GLTF_IMAGE_h__ 27 | #define __ROP_GLTF_IMAGE_h__ 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | class IMG_Format; 38 | class COP2_Node; 39 | class UT_String; 40 | class PXL_Raster; 41 | class IMG_Stat; 42 | class ROP_GLTF_BaseErrorManager; 43 | 44 | struct ROP_GLTF_ChannelMapping 45 | { 46 | // This implemented so we can obtain a lexiographic ordering 47 | // of ChannelMaps in order to cache them in a map 48 | bool operator<(const ROP_GLTF_ChannelMapping &r) const; 49 | 50 | UT_StringHolder path; 51 | exint from_channel; 52 | exint to_channel; 53 | }; 54 | 55 | struct ROP_GLTF_ImgExportParms 56 | { 57 | bool roundUpPowerOfTwo = false; 58 | // 0 indicates that there is no max raster size 59 | exint maxRasterSize = 0; 60 | exint quality = 90; 61 | exint max_res = 0; 62 | bool flipGreen = false; 63 | }; 64 | 65 | /// 66 | /// Utility functions for importing, exporting and manipulating images in 67 | /// the context of GLTF textures. 68 | /// 69 | 70 | class ROP_GLTF_Image 71 | { 72 | public: 73 | /// 74 | /// Takes a list of images and associated channels, packs them into 75 | /// a single image file, preprocesses the image and outputs it to &os 76 | /// 77 | static bool 78 | CreateMappedTexture(const UT_Array &mappings, 79 | std::ostream &os, const IMG_Format *format, uint32 time, 80 | const ROP_GLTF_ImgExportParms &parms, 81 | ROP_GLTF_BaseErrorManager &errormgr); 82 | 83 | /// 84 | /// Converts the file format for the given images, processes it for GLTF 85 | /// and outputs it to the output stream &os. 86 | /// 87 | static bool OutputImage(const UT_String &filename, const IMG_Format *format, 88 | std::ostream &os, fpreal time, 89 | const ROP_GLTF_ImgExportParms &parms, 90 | ROP_GLTF_BaseErrorManager &errormgr); 91 | 92 | private: 93 | // 94 | // Gets the image rasters from the given File or COP. Any methods 95 | // with "include_alpha" will return RGBA if the flag is on, or otherwise 96 | // RGB. This is to avoid handling other packings. 97 | // 98 | static bool 99 | GetImageRasters(const UT_StringHolder &filename, const uint32 time, 100 | UT_Array> &rasters, 101 | IMG_Stat &stat, bool include_alpha); 102 | 103 | static exint NextPowerOfTwo(exint num); 104 | 105 | static bool 106 | OutputCopToStream(COP2_Node *node, const IMG_Format *format, 107 | std::ostream &os, fpreal time, 108 | const ROP_GLTF_ImgExportParms &parms); 109 | 110 | static bool 111 | OutputImageToStream(const UT_String &filename, 112 | const IMG_Format *file_format, std::ostream &os, 113 | fpreal time, const ROP_GLTF_ImgExportParms &parms); 114 | 115 | static void 116 | ApplyTransformations(IMG_Stat &stat, 117 | UT_Array> &rasters, 118 | const ROP_GLTF_ImgExportParms &parms); 119 | 120 | static bool 121 | GetImageRastersFromFile(const UT_StringHolder &filename, const uint32 time, 122 | UT_Array> &rasters, 123 | IMG_Stat &stat, bool include_alpha); 124 | 125 | static bool 126 | GetImageRastersFromCOP(COP2_Node *node, const uint32 time, 127 | UT_Array> &rasters, 128 | IMG_Stat &stat, bool include_alpha); 129 | }; 130 | 131 | #endif 132 | -------------------------------------------------------------------------------- /src/ROP/ROP_GLTF_Refiner.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018 3 | * Side Effects Software Inc. All rights reserved. 4 | * 5 | * Redistribution and use of Houdini Development Kit samples in source and 6 | * binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. The name of Side Effects Software may not be used to endorse or 11 | * promote products derived from this software without specific prior 12 | * written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 17 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 20 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | */ 25 | 26 | #ifndef __ROP_GLTF_REFINER_h__ 27 | #define __ROP_GLTF_REFINER_h__ 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include 36 | 37 | typedef GLTF_NAMESPACE::GLTF_Primitive GLTF_Primitive; 38 | typedef GLTF_NAMESPACE::GLTF_Mesh GLTF_Mesh; 39 | typedef GLTF_NAMESPACE::GLTF_ComponentType GLTF_ComponentType; 40 | typedef GLTF_NAMESPACE::GLTF_BufferViewTarget GLTF_BufferViewTarget; 41 | 42 | class GT_PrimPolygonMesh; 43 | class GT_PrimInstance; 44 | class GU_Detail; 45 | class ROP_GLTF_BaseErrorManager; 46 | class GT_GEOPrimPacked; 47 | 48 | class ROP_GLTF_Refiner : public GT_Refine 49 | { 50 | public: 51 | struct Refine_Options 52 | { 53 | bool output_custom_attribs = false; 54 | }; 55 | 56 | ROP_GLTF_Refiner(ROP_GLTF_ExportRoot &root, GLTF_Node *node, 57 | const UT_StringHolder &obj_material, 58 | std::function create_material, 59 | Refine_Options options); 60 | 61 | virtual ~ROP_GLTF_Refiner(); 62 | virtual void addPrimitive(const GT_PrimitiveHandle &prim) override; 63 | 64 | // GLTF buffer allocation is currently unprotected 65 | bool allowThreading() const override { return false; } 66 | 67 | /// 68 | /// A convenience function. Refines the detail, and adds meshes 69 | /// (or potentially submeshes if instancing is used) to the GLTF_Node 70 | /// that is passed in. 71 | /// 72 | static void 73 | refine(const GU_Detail *src, ROP_GLTF_ExportRoot &root, GLTF_Node &node, 74 | const UT_StringHolder &obj_material, 75 | std::function create_material, 76 | Refine_Options options); 77 | 78 | 79 | private: 80 | void processPrimPolygon(GT_PrimPolygonMesh *prim, UT_Matrix4D trans, 81 | GLTF_Mesh &mesh); 82 | 83 | bool processInstance(const GT_PrimInstance *instance); 84 | 85 | GLTF_Handle appendMeshIfNotEmpty(GLTF_Mesh &mesh); 86 | 87 | void 88 | addMesh(const GT_PrimPolygonMesh &prim, UT_Matrix4D trans, GLTF_Mesh &mesh); 89 | 90 | // Creates a GLTF_Primitive based on the given attributes and 91 | // indices then returns a reference to it. 92 | GLTF_Primitive & 93 | addPoints(const GT_AttributeListHandle &attributes, 94 | const GT_DataArrayHandle &indices, GLTF_Mesh &mesh); 95 | 96 | // Translates a Houdini component type to the 'closest' GLTF 97 | // component type 98 | GLTF_ComponentType GetComponentTypeFromStorage(GT_Storage storage); 99 | 100 | struct Attrib_CopyResult 101 | { 102 | uint32 size; 103 | uint32 offset; 104 | UT_Array elem_min; 105 | UT_Array elem_max; 106 | uint32 entries; 107 | }; 108 | 109 | template 110 | Attrib_CopyResult 111 | CopyAttribData(uint32 bid, const T *arr, GT_Size entries, 112 | GT_Size old_tuple_size, GT_Size new_tuple_size, 113 | std::function func, uint32 stride); 114 | 115 | // 116 | // Allocates data from the GLTF buffer 'bid' and moves attribute data 117 | // to handle, converting type if needed. 118 | // If old_tuple_size > new_tuple_size, then the size of the tuple will 119 | // be truncated (this is mainly used for UVs). 120 | // 121 | template 122 | uint32 AddAttrib(const GT_DataArrayHandle &handle, 123 | GLTF_ComponentType target_type, GT_Size new_tuple_size, 124 | uint32 bid, GLTF_BufferViewTarget buffer_type, 125 | std::function func = {}, uint32 stride = 1); 126 | 127 | bool ExportAttribute(const UT_StringRef &attrib_name, 128 | const GT_DataArrayHandle &attrib_data, 129 | GLTF_Primitive &prim); 130 | 131 | ROP_GLTF_ExportRoot &myRoot; 132 | GLTF_Node *myNode; 133 | const UT_StringHolder &myObjectMaterial; 134 | std::function myCreateMaterial; 135 | const Refine_Options myOptions; 136 | }; 137 | 138 | class ROP_GLTF_PointSplit 139 | { 140 | public: 141 | static void 142 | Split(const GT_PrimPolygonMesh &polymesh, fpreal64 tol, 143 | GT_AttributeListHandle &new_points, GT_DataArrayHandle &new_vertices); 144 | 145 | private: 146 | ROP_GLTF_PointSplit(const GT_PrimPolygonMesh &prim, fpreal64 tol); 147 | 148 | // Refines detail and prim attributes down to vertex attributes 149 | // (which will later be refined into point attributes) 150 | GT_AttributeListHandle refineDetailPrims(); 151 | 152 | // Returns true if the points can be merged 153 | template 154 | bool compareAttribs(GT_Offset pt_1, GT_Offset pt_2, T *attr_arr, 155 | GT_Size tuple_size); 156 | 157 | template 158 | GT_DataArrayHandle 159 | splitAttribute(GT_Int32Array *new_verts, 160 | UT_Array> &vertexes_using_point, 161 | GT_Int32Array *new_pts_indirect, T *attr_arr, 162 | GT_Size tuple_size); 163 | 164 | void 165 | splitAttrib(GT_AttributeListHandle &new_points, GT_DataArrayHandle &new_vertice, 166 | const GT_AttributeListHandle &vertex_attribs, exint idx); 167 | 168 | const GT_PrimPolygonMesh &myPrim; 169 | const fpreal myTol; 170 | }; 171 | 172 | #endif 173 | -------------------------------------------------------------------------------- /src/SOP/Makefile: -------------------------------------------------------------------------------- 1 | # The HFS Environment variable needs to be set before calling make 2 | # Windows users should also define their MSVCDir environnment variable 3 | 4 | include ../CustomGLTF.global 5 | 6 | DSONAME = $(SOPLIB).$(EXT) 7 | 8 | # Custom GLTF library 9 | CUSTOM_GLTF = ".." 10 | 11 | SOURCES = SOP_GLTF.C 12 | 13 | INCDIRS = \ 14 | -I$(CUSTOM_GLTF) \ 15 | -I$(HFS)/toolkit/include 16 | 17 | ifdef WINDOWS 18 | LIBDIRS += -LIBPATH:$(CUSTOM_GLTF)/GLTF 19 | LIBS += lib$(GLTFLIB).lib 20 | else 21 | LIBDIRS += -L$(CUSTOM_GLTF)/GLTF 22 | LIBS += -l$(GLTFLIB) 23 | endif 24 | 25 | include $(HFS)/toolkit/makefiles/Makefile.gnu 26 | 27 | HDEFINES += \ 28 | -DGLTF_EXPORTS \ 29 | -DGLTF_NAMESPACE=$(GLTFNAMESPACE) \ 30 | -DCUSTOM_GLTF_TOKEN_PREFIX='$(TOKEN_PREFIX)' \ 31 | -DCUSTOM_GLTF_LABEL_PREFIX='$(LABEL_PREFIX)' 32 | -------------------------------------------------------------------------------- /src/SOP/SOP_GLTF.C: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 3 | * Side Effects Software Inc. All rights reserved. 4 | * 5 | * Redistribution and use of Houdini Development Kit samples in source and 6 | * binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. The name of Side Effects Software may not be used to endorse or 11 | * promote products derived from this software without specific prior 12 | * written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 17 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 20 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | *---------------------------------------------------------------------------- 26 | */ 27 | 28 | #include "SOP_GLTF.h" 29 | 30 | #include 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | 47 | #include 48 | #include 49 | #include 50 | #include 51 | 52 | #if !defined(CUSTOM_GLTF_TOKEN_PREFIX) 53 | #define CUSTOM_GLTF_TOKEN_PREFIX "" 54 | #define CUSTOM_GLTF_LABEL_PREFIX "" 55 | #endif 56 | 57 | using namespace GLTF_NAMESPACE; 58 | 59 | //-***************************************************************************** 60 | 61 | constexpr const char *GLTF_NAME_ATTRIB = "name"; 62 | constexpr const char *GLTF_SCENE_NAME_ATTRIB = "scene_name"; 63 | 64 | static std::string 65 | sopGetRealFileName(const char *name) 66 | { 67 | UT_String realname; 68 | 69 | // complete a path search in case it is in the geometry path 70 | UT_PathSearch::getInstance(UT_HOUDINI_GEOMETRY_PATH) 71 | ->findFile(realname, name); 72 | return realname.toStdString(); 73 | } 74 | 75 | //-***************************************************************************** 76 | 77 | OP_Node * 78 | SOP_GLTF::myConstructor(OP_Network *net, const char *name, OP_Operator *op) 79 | { 80 | return new SOP_GLTF(net, name, op); 81 | } 82 | 83 | //****************************************************************************** 84 | 85 | static int 86 | selectGLTFScenes(void *data, int index, fpreal t, const PRM_Template *tplate) 87 | { 88 | SOP_GLTF *gltf = reinterpret_cast(data); 89 | UT_WorkBuffer cmd; 90 | UT_String filename; 91 | UT_String objectpath; 92 | 93 | cmd.strcpy("listchooser"); 94 | cmd.strcat(" -r"); 95 | 96 | auto &scene_names = gltf->getSceneNames(); 97 | for (exint idx = 0; idx < scene_names.size(); idx++) 98 | { 99 | auto &scene_name = scene_names[idx]; 100 | UT_String name(UT_String::ALWAYS_DEEP); 101 | 102 | if (scene_name == "") 103 | name = ("Scene" + std::to_string(idx + 1)).c_str(); 104 | else 105 | name = scene_name; 106 | 107 | cmd.strcat(" "); 108 | cmd.strcat(name); 109 | } 110 | 111 | CMD_Manager *mgr = CMDgetManager(); 112 | UT_OStringStream oss; 113 | mgr->execute(cmd.buffer(), 0, &oss); 114 | UT_String result(oss.str().buffer()); 115 | result.trimBoundingSpace(); 116 | 117 | if (result != "") 118 | { 119 | gltf->setChRefInt("scene", 0, t, result.toInt()); 120 | gltf->setChRefString("loadby", 0, t, "scene", CH_STRING_LITERAL); 121 | } 122 | 123 | return 0; 124 | } 125 | 126 | static int 127 | selectGLTFMeshes(void *data, int index, fpreal t, const PRM_Template *tplate) 128 | { 129 | SOP_GLTF *gltf = reinterpret_cast(data); 130 | UT_WorkBuffer cmd; 131 | UT_String filename; 132 | UT_String objectpath; 133 | 134 | cmd.strcpy("listchooser"); 135 | cmd.strcat(" -r"); 136 | 137 | auto &mesh_names = gltf->getMeshNames(); 138 | for (exint idx = 0; idx < mesh_names.size(); idx++) 139 | { 140 | auto &mesh_name = mesh_names[idx]; 141 | UT_String name(UT_String::ALWAYS_DEEP); 142 | 143 | if (mesh_name.myFirst == "") 144 | name = ("Mesh" + std::to_string(idx + 1)).c_str(); 145 | else 146 | name = mesh_name.myFirst; 147 | 148 | cmd.strcat(" "); 149 | cmd.strcat(name); 150 | } 151 | 152 | CMD_Manager *mgr = CMDgetManager(); 153 | UT_OStringStream oss; 154 | mgr->execute(cmd.buffer(), 0, &oss); 155 | UT_String result(oss.str().buffer()); 156 | result.trimBoundingSpace(); 157 | 158 | if (result != "") 159 | { 160 | gltf->setChRefInt("meshid", 0, t, result.toInt()); 161 | gltf->setChRefString("loadby", 0, t, "primitive", CH_STRING_LITERAL); 162 | } 163 | 164 | return 0; 165 | } 166 | 167 | static int 168 | selectGLTFNodes(void *data, int index, fpreal t, const PRM_Template *tplate) 169 | { 170 | SOP_GLTF *gltf = reinterpret_cast(data); 171 | UT_WorkBuffer cmd; 172 | UT_String filename; 173 | UT_String objectpath; 174 | 175 | cmd.strcpy("listchooser"); 176 | cmd.strcat(" -r"); 177 | 178 | auto &node_names = gltf->getNodeNames(); 179 | for (exint idx = 0; idx < node_names.size(); idx++) 180 | { 181 | auto &node_name = node_names[idx]; 182 | UT_String name(UT_String::ALWAYS_DEEP); 183 | 184 | if (node_name == "") 185 | name = ("Node " + std::to_string(idx + 1)).c_str(); 186 | else 187 | name = node_name; 188 | 189 | cmd.strcat(" "); 190 | cmd.strcat(name); 191 | } 192 | 193 | CMD_Manager *mgr = CMDgetManager(); 194 | UT_OStringStream oss; 195 | mgr->execute(cmd.buffer(), 0, &oss); 196 | UT_String result(oss.str().buffer()); 197 | result.trimBoundingSpace(); 198 | 199 | if (result != "") 200 | { 201 | gltf->setChRefInt("nodeid", 0, t, result.toInt()); 202 | gltf->setChRefString("loadby", 0, t, "node", CH_STRING_LITERAL); 203 | } 204 | 205 | return 0; 206 | } 207 | 208 | //-***************************************************************************** 209 | 210 | static PRM_SpareData theTreeButtonSpareData( 211 | PRM_SpareArgs() << PRM_SpareToken(PRM_SpareData::getButtonIconToken(), 212 | "BUTTONS_tree")); 213 | 214 | static PRM_SpareData 215 | gltfPattern(PRM_SpareToken(PRM_SpareData::getFileChooserPatternToken(), 216 | "*.gltf, *.glb")); 217 | 218 | static PRM_Name prm_filenameName("filename", "File Name"); 219 | static PRM_Name prm_loadBy("loadby", "Load By"); 220 | static PRM_Name prm_meshID("meshid", "Mesh ID"); 221 | static PRM_Name prm_primitiveIndex("primitiveindex", "Primitive Index"); 222 | static PRM_Name prm_rootnode("nodeid", "Root Node"); 223 | static PRM_Name prm_scene("scene", "Scene"); 224 | static PRM_Name prm_loadCustomAttribs("usecustomattribs", "Import Custom Attributes"); 225 | static PRM_Name prm_LoadNames("loadnames", "Import Names"); 226 | static PRM_Name prm_meshChooser("meshchooser", "Choose Mesh"); 227 | static PRM_Name prm_sceneChooser("scenechooser", "Choose Scene"); 228 | static PRM_Name prm_nodeChooser("nodechooser", "Choose Node"); 229 | 230 | static PRM_Name prm_geoType("geotype", "Geometry Type"); 231 | static PRM_Name prm_materialAssigns("materialassigns", "Import Material Assignments"); 232 | 233 | static PRM_Name prm_promotePointAttribs("promotepointattrs", "Promote Point Attributes to Vertex"); 234 | static PRM_Name prm_pointConsolidateDistance("pointconsolidatedist", "Points Merge Distance"); 235 | 236 | static PRM_Default prm_filenameDefault(0, "default.gltf"); 237 | 238 | // Dropdown menus 239 | 240 | static PRM_Name prm_loadByOptions[] = {PRM_Name("primitive", "Primitive"), 241 | PRM_Name("mesh", "Mesh"), 242 | PRM_Name("node", "Node"), 243 | PRM_Name("scene", "Scene"), PRM_Name()}; 244 | 245 | static PRM_Default prm_loadByDefault(0, "primitive"); 246 | 247 | static PRM_Name prm_geoTypeOptions[] = { 248 | PRM_Name("flattenedgeo", "Flattened Geometry"), 249 | PRM_Name("packedprim", "Packed Primitive"), PRM_Name()}; 250 | 251 | static PRM_Default prm_geoTypeDefault(0, "flattenedgeo"); 252 | 253 | static PRM_ChoiceList 254 | prm_loadByChoices(PRM_CHOICELIST_SINGLE, prm_loadByOptions); 255 | 256 | static PRM_ChoiceList 257 | prm_geoTypeChoices(PRM_CHOICELIST_SINGLE, prm_geoTypeOptions); 258 | 259 | PRM_Template SOP_GLTF::myTemplateList[] = { 260 | PRM_Template(PRM_FILE, 1, &prm_filenameName, &prm_filenameDefault, 0, 0, 0, 261 | &gltfPattern), 262 | PRM_Template(PRM_ORD, 1, &prm_loadBy, &prm_loadByDefault, 263 | &prm_loadByChoices), 264 | PRM_Template(PRM_INT_J, PRM_TYPE_JOIN_PAIR, 1, &prm_meshID), 265 | PRM_Template(PRM_CALLBACK, PRM_TYPE_NO_LABEL, 1, &prm_meshChooser, 0, 0, 0, 266 | selectGLTFMeshes, &theTreeButtonSpareData), 267 | PRM_Template(PRM_INT_J, 1, &prm_primitiveIndex), 268 | PRM_Template(PRM_INT_J, PRM_TYPE_JOIN_PAIR, 1, &prm_rootnode), 269 | PRM_Template(PRM_CALLBACK, PRM_TYPE_NO_LABEL, 1, &prm_nodeChooser, 0, 0, 0, 270 | selectGLTFNodes, &theTreeButtonSpareData), 271 | PRM_Template(PRM_INT_J, PRM_TYPE_JOIN_PAIR, 1, &prm_scene), 272 | PRM_Template(PRM_CALLBACK, PRM_TYPE_NO_LABEL, 1, &prm_sceneChooser, 0, 0, 0, 273 | selectGLTFScenes, &theTreeButtonSpareData), 274 | PRM_Template(PRM_ORD, 1, &prm_geoType, &prm_geoTypeDefault, 275 | &prm_geoTypeChoices), 276 | PRM_Template(PRM_TOGGLE, 1, &prm_promotePointAttribs, PRMoneDefaults), 277 | PRM_Template(PRM_FLT_J, 1, &prm_pointConsolidateDistance, &PRMfitToleranceDefault), 278 | PRM_Template(PRM_TOGGLE, 1, &prm_loadCustomAttribs, PRMoneDefaults), 279 | PRM_Template(PRM_TOGGLE, 1, &prm_LoadNames, PRMoneDefaults), 280 | 281 | PRM_Template(PRM_TOGGLE, 1, &prm_materialAssigns, PRMzeroDefaults), 282 | 283 | PRM_Template()}; 284 | 285 | //-***************************************************************************** 286 | 287 | SOP_GLTF::SOP_GLTF(OP_Network *net, const char *name, OP_Operator *op) 288 | : SOP_Node(net, name, op) 289 | { 290 | } 291 | 292 | SOP_GLTF::~SOP_GLTF() {} 293 | 294 | //-***************************************************************************** 295 | 296 | bool 297 | SOP_GLTF::updateParmsFlags() 298 | { 299 | Parms parms; 300 | OP_Context c(0); 301 | evaluateParms(parms, c); 302 | 303 | GLTF_LoadStyle loadStyle = parms.myLoadStyle; 304 | uint32 promotePointAttrs = parms.myPromotePointAttrsToVertex; 305 | 306 | bool changed = false; 307 | changed |= enableParm("meshid", loadStyle == GLTF_LoadStyle::Primitive || 308 | loadStyle == GLTF_LoadStyle::Mesh); 309 | changed |= enableParm("primitiveindex", loadStyle == GLTF_LoadStyle::Primitive); 310 | changed |= enableParm("nodeid", loadStyle == GLTF_LoadStyle::Node); 311 | changed |= enableParm("scene", loadStyle == GLTF_LoadStyle::Scene); 312 | 313 | changed |= enableParm("pointconsolidatedist", promotePointAttrs == 1); 314 | 315 | return changed; 316 | } 317 | 318 | OP_ERROR 319 | SOP_GLTF::cookMySop(OP_Context &context) 320 | { 321 | Parms parms; 322 | evaluateParms(parms, context); 323 | 324 | // Don't bother trying to load if we've yet to input a filename 325 | if (parms.myFileName == "") 326 | return error(); 327 | 328 | auto loader = GLTF_Cache::GetInstance().LoadLoader(parms.myFileName); 329 | if (!loader) 330 | return UT_ERROR_ABORT; 331 | 332 | saveMeshNames(*loader); 333 | 334 | gdp->clearAndDestroy(); 335 | 336 | const UT_String version = loader->getAsset().version; 337 | // Compare version with a lexiographic compare, should be 338 | // good enough for a warning 339 | if (version < "2.0" || version > "3.0") 340 | { 341 | addWarning(SOP_MESSAGE, "Attempting to load unsupported version"); 342 | return error(); 343 | } 344 | 345 | SOP_GLTF_Loader::Options options; 346 | options.loadNames = parms.myLoadNames; 347 | options.loadCustomAttribs = parms.myUseCustomAttribs; 348 | options.flatten = parms.myGeoType == GLTF_GeoType::Houdini_Geo; 349 | options.loadMats = parms.myLoadMats; 350 | options.promotePointAttribs = parms.myPromotePointAttrsToVertex; 351 | options.consolidateByMesh = !options.flatten 352 | || parms.myLoadStyle 353 | == GLTF_LoadStyle::Primitive; 354 | options.pointConsolidationDistance = parms.myPointConsolidationDistance; 355 | 356 | if (getParent() && getParent()->getParent()) 357 | { 358 | options.material_path = getParent()->getParent()->getFullPath(); 359 | options.material_path.append("/materials/"); 360 | } 361 | else 362 | { 363 | options.loadMats = false; 364 | } 365 | 366 | SOP_GLTF_Loader sop_loader(*loader, gdp, options); 367 | 368 | if (parms.myLoadStyle == GLTF_LoadStyle::Node) 369 | { 370 | GLTF_Handle node = parms.myRootNode; 371 | if (node >= loader->getNumNodes()) 372 | { 373 | addError(SOP_MESSAGE, "Invaid Node"); 374 | return error(); 375 | } 376 | sop_loader.loadNode(*loader->getNode(node)); 377 | } 378 | else if (parms.myLoadStyle == GLTF_LoadStyle::Mesh) 379 | { 380 | GLTF_Handle mesh = parms.myMeshID; 381 | if (mesh >= loader->getNumMeshes()) 382 | { 383 | addError(SOP_MESSAGE, "Invaid Mesh"); 384 | return error(); 385 | } 386 | sop_loader.loadMesh(mesh); 387 | } 388 | else if (parms.myLoadStyle == GLTF_LoadStyle::Primitive) 389 | { 390 | // Just load in a single primitive 391 | if (!sop_loader.loadPrimitive(parms.myMeshID, parms.myPrimIndex)) 392 | { 393 | addError(SOP_MESSAGE, "Invalid Primitive"); 394 | return error(); 395 | } 396 | } 397 | else if (parms.myLoadStyle == GLTF_LoadStyle::Scene) 398 | { 399 | GLTF_Handle scene = parms.myScene; 400 | 401 | if (scene >= loader->getNumScenes()) 402 | { 403 | addError(SOP_MESSAGE, "Invalid Scene"); 404 | return error(); 405 | } 406 | 407 | sop_loader.loadScene(scene); 408 | } 409 | 410 | return error(); 411 | } 412 | 413 | //***************************************************************************** 414 | 415 | const UT_Array> & 416 | SOP_GLTF::getMeshNames() const 417 | { 418 | return myMeshes; 419 | } 420 | 421 | const UT_Array & 422 | SOP_GLTF::getNodeNames() const 423 | { 424 | return myNodes; 425 | } 426 | 427 | const UT_Array & 428 | SOP_GLTF::getSceneNames() const 429 | { 430 | return myScenes; 431 | } 432 | 433 | void 434 | SOP_GLTF::installSOP(OP_OperatorTable *table) 435 | { 436 | OP_Operator *gltf_op = 437 | new OP_Operator( 438 | CUSTOM_GLTF_TOKEN_PREFIX "gltf", // Internal name 439 | CUSTOM_GLTF_LABEL_PREFIX "GLTF", // GUI name 440 | SOP_GLTF::myConstructor, // Op Constructr 441 | SOP_GLTF::myTemplateList, // Parameter Definition 442 | 0, // Min # of Inputs 443 | 0, // Max # of Inputs 444 | 0, // Local variables 445 | OP_FLAG_GENERATOR // Generator flag 446 | ); 447 | 448 | gltf_op->setIconName("OBJ_gltf_hierarchy"); 449 | table->addOperator(gltf_op); 450 | } 451 | 452 | void 453 | newSopOperator(OP_OperatorTable *table) 454 | { 455 | SOP_GLTF::installSOP(table); 456 | } 457 | 458 | ////////////////////////////////////////////////////////// 459 | 460 | SOP_GLTF::Parms::Parms() {} 461 | 462 | void 463 | SOP_GLTF::saveMeshNames(const GLTF_NAMESPACE::GLTF_Loader &loader) 464 | { 465 | myScenes.clear(); 466 | myMeshes.clear(); 467 | myNodes.clear(); 468 | 469 | for (const GLTF_Scene *scene : loader.getScenes()) 470 | { 471 | UT_String scene_name(UT_String::ALWAYS_DEEP, scene->name); 472 | myScenes.append(scene_name); 473 | } 474 | 475 | for (const GLTF_Node *node : loader.getNodes()) 476 | { 477 | UT_String scene_name(UT_String::ALWAYS_DEEP, node->name); 478 | myNodes.append(scene_name); 479 | } 480 | 481 | for (const GLTF_Mesh *mesh : loader.getMeshes()) 482 | { 483 | UT_String mesh_name(UT_String::ALWAYS_DEEP, mesh->name); 484 | myMeshes.append( 485 | {mesh_name, static_cast(mesh->primitives.size())}); 486 | } 487 | } 488 | 489 | void 490 | SOP_GLTF_Loader::getMaterialPath(GLTF_Int index, UT_String &path) 491 | { 492 | UT_String name; 493 | name.harden(myLoader.getMaterial(index)->name); 494 | 495 | // This is technically n^2 string compares, but it realistically doesn't matter 496 | bool is_duplicate = false; 497 | const auto& materials = myLoader.getMaterials(); 498 | for (exint i = 0 ; i < materials.size(); i++) 499 | { 500 | if (i == index) 501 | { 502 | continue; 503 | } 504 | 505 | if (materials[i]->name == name) 506 | { 507 | is_duplicate = true; 508 | break; 509 | } 510 | } 511 | 512 | // Now perform the same sanitization scheme as the Python script 513 | for (exint i = 0; i < name.length(); i++) 514 | { 515 | if (!isalnum(name[i])) 516 | name[i] = '_'; 517 | } 518 | 519 | if (name[0] >= '0' && name[0] <= '9') 520 | name.prepend("_"); 521 | 522 | // If the name is a duplicate, we use the format "matname_index" 523 | // This rule also applies when the material is unnamed 524 | if (is_duplicate) 525 | { 526 | name.append(UT_String("_" + std::to_string(index))); 527 | } 528 | 529 | // TODO: INCOMPLETE 530 | path.harden(myOptions.material_path); 531 | path.append(name); 532 | } 533 | 534 | void 535 | SOP_GLTF::evaluateParms(Parms &parms, OP_Context &context) 536 | { 537 | fpreal t = context.getTime(); 538 | 539 | UT_String filename; 540 | evalString(filename, "filename", 0, t); 541 | 542 | if (filename.isstring()) 543 | { 544 | parms.myFileName = sopGetRealFileName(filename); 545 | } 546 | 547 | int mesh_id; 548 | int primitive_index; 549 | int use_custom_attribs; 550 | int root_node; 551 | int scene; 552 | int load_mesh_names; 553 | int load_mats; 554 | int promote_points_attrs_to_vertex; 555 | fpreal point_consolidation_dist; 556 | 557 | mesh_id = evalInt("meshid", 0, t); 558 | primitive_index = evalInt("primitiveindex", 0, t); 559 | use_custom_attribs = evalInt("usecustomattribs", 0, t); 560 | root_node = evalInt("nodeid", 0, t); 561 | scene = evalInt("scene", 0, t); 562 | load_mesh_names = evalInt("loadnames", 0, t); 563 | load_mats = evalInt("materialassigns", 0, t); 564 | promote_points_attrs_to_vertex = evalInt("promotepointattrs", 0, t); 565 | point_consolidation_dist = evalFloat("pointconsolidatedist", 0, t); 566 | 567 | 568 | parms.myMeshID = static_cast(mesh_id); 569 | parms.myPrimIndex = static_cast(primitive_index); 570 | parms.myUseCustomAttribs = static_cast(use_custom_attribs); 571 | parms.myRootNode = static_cast(root_node); 572 | parms.myScene = static_cast(scene); 573 | parms.myLoadNames = static_cast(load_mesh_names); 574 | parms.myLoadMats = static_cast(load_mats); 575 | parms.myPromotePointAttrsToVertex = static_cast(promote_points_attrs_to_vertex); 576 | parms.myPointConsolidationDistance = point_consolidation_dist; 577 | 578 | UT_String l_type; 579 | evalString(l_type, "loadby", 0, t); 580 | 581 | UT_String geo_type; 582 | evalString(geo_type, "geotype", 0, t); 583 | 584 | if (l_type == "scene") 585 | parms.myLoadStyle = GLTF_LoadStyle::Scene; 586 | else if (l_type == "primitive") 587 | parms.myLoadStyle = GLTF_LoadStyle::Primitive; 588 | else if (l_type == "node") 589 | parms.myLoadStyle = GLTF_LoadStyle::Node; 590 | else if (l_type == "mesh") 591 | parms.myLoadStyle = GLTF_LoadStyle::Mesh; 592 | else 593 | UT_ASSERT(false); 594 | 595 | if (geo_type == "flattenedgeo") 596 | parms.myGeoType = GLTF_GeoType::Houdini_Geo; 597 | else if (geo_type == "packedprim") 598 | parms.myGeoType = GLTF_GeoType::Packed_Primitives; 599 | else 600 | UT_ASSERT(false); 601 | } 602 | 603 | SOP_GLTF_Loader::SOP_GLTF_Loader(const GLTF_NAMESPACE::GLTF_Loader &loader, GU_Detail *detail, 604 | Options options) 605 | : myLoader(loader), myDetail(detail), myOptions(options) 606 | { 607 | } 608 | 609 | void 610 | SOP_GLTF_Loader::loadMesh(const GLTF_Handle mesh_idx) 611 | { 612 | GLTF_Node dummy_node; 613 | dummy_node.mesh = mesh_idx; 614 | loadNode(dummy_node); 615 | } 616 | 617 | static void 618 | sopConsolidatePoints(GU_Detail &detail, fpreal distance) 619 | { 620 | // Consolidate points using GU_Snap 621 | GU_Snap::PointSnapParms snap_parms; 622 | 623 | GA_ElementGroup *output_grp = UTverify_cast( 624 | detail.getElementGroupTable(GA_ATTRIB_POINT).newInternalGroup()); 625 | 626 | snap_parms.myConsolidate = true; 627 | snap_parms.myDeleteConsolidated = true; 628 | snap_parms.myDistance = distance; 629 | snap_parms.myModifyBothQueryAndTarget = true; 630 | snap_parms.myQPosH.bind(&detail, GA_ATTRIB_POINT, GA_Names::P); 631 | snap_parms.myTPosH.bind(&detail, GA_ATTRIB_POINT, GA_Names::P); 632 | snap_parms.myOutputGroup = output_grp; 633 | snap_parms.myMatchTol = 0.f; 634 | snap_parms.myMismatch = false; 635 | GU_Snap::snapPoints(detail, nullptr, snap_parms); 636 | GA_PrimitiveGroup prim_grp(detail); 637 | prim_grp.combine(output_grp); 638 | detail.cleanData(&prim_grp, false, 0.001F, true, true, true); 639 | detail.bumpDataIdsForAddOrRemove(true, false, false); 640 | detail.destroyGroup(output_grp); 641 | } 642 | 643 | void 644 | SOP_GLTF_Loader::loadNode(const GLTF_Node &node) 645 | { 646 | if (myOptions.loadNames) 647 | { 648 | myDetail->addStringTuple(GA_ATTRIB_PRIMITIVE, GLTF_NAME_ATTRIB, 1); 649 | } 650 | 651 | loadNodeRecursive(node, myDetail, UT_Matrix4F(1)); 652 | 653 | if (myOptions.promotePointAttribs && !myOptions.consolidateByMesh) 654 | { 655 | // Consolidate points of the full detail 656 | sopConsolidatePoints(*myDetail, myOptions.pointConsolidationDistance); 657 | } 658 | } 659 | 660 | void 661 | SOP_GLTF_Loader::loadScene(GLTF_Handle scene_idx) 662 | { 663 | auto *scene = myLoader.getScene(scene_idx); 664 | 665 | if (myOptions.loadNames) 666 | { 667 | GA_RWHandleS scene_name_attr; 668 | scene_name_attr = myDetail->addStringTuple(GA_ATTRIB_DETAIL, 669 | GLTF_SCENE_NAME_ATTRIB, 1); 670 | 671 | scene_name_attr.set(GA_Offset(0), 0, scene->name); 672 | } 673 | 674 | // The scene can have multiple nodes, so we create a dummy node to represent 675 | // the scene root 676 | GLTF_Node dummy_node; 677 | dummy_node.children = scene->nodes; 678 | loadNode(dummy_node); 679 | } 680 | 681 | bool 682 | SOP_GLTF_Loader::loadPrimitive(GLTF_Handle node_idx, GLTF_Handle prim_idx) 683 | { 684 | GLTF_GeoLoader loader(myLoader, node_idx, prim_idx, getGeoOptions()); 685 | 686 | if (!loader.loadIntoDetail(*myDetail)) 687 | return false; 688 | 689 | // Assign names or materials as required 690 | if (myOptions.loadNames) 691 | { 692 | // Loads the name in the format mesh_(i) where i is the index 693 | // of the primitive 694 | UT_String new_name; 695 | new_name.harden(myLoader.getMesh(node_idx)->name); 696 | new_name.append("_"); 697 | new_name.append(std::to_string(prim_idx).c_str()); 698 | 699 | GA_RWHandleS sm_name_attrib; 700 | sm_name_attrib = 701 | myDetail->addStringTuple(GA_ATTRIB_PRIMITIVE, GLTF_NAME_ATTRIB, 1); 702 | 703 | for (GA_Offset off : myDetail->getPrimitiveRange()) 704 | { 705 | sm_name_attrib.set(off, 0, new_name); 706 | } 707 | } 708 | 709 | if (myOptions.loadMats) 710 | { 711 | UT_String mat_path; 712 | const GLTF_Primitive& prim = myLoader.getMesh(node_idx)->primitives[prim_idx]; 713 | if (prim.material != GLTF_INVALID_IDX) 714 | { 715 | getMaterialPath(prim.material, mat_path); 716 | } 717 | 718 | GA_RWHandleS sm_mat_attrib; 719 | sm_mat_attrib = myDetail->addStringTuple(GA_ATTRIB_PRIMITIVE, 720 | GA_Names::shop_materialpath, 1); 721 | 722 | for (GA_Offset off : myDetail->getPrimitiveRange()) 723 | { 724 | sm_mat_attrib.set(off, 0, mat_path); 725 | } 726 | } 727 | 728 | return true; 729 | } 730 | 731 | void 732 | SOP_GLTF_Loader::loadNodeRecursive(const GLTF_Node &node, GU_Detail *parent_gd, 733 | UT_Matrix4F cum_xform) 734 | { 735 | UTgetInterrupt()->opInterrupt(); 736 | 737 | UT_Matrix4F transform; 738 | node.getTransformAsMatrix(transform); 739 | 740 | cum_xform = transform * cum_xform; 741 | 742 | // The detail which is currently being operated on 743 | GU_Detail *gd; 744 | GA_RWHandleS name_attr; 745 | GA_RWHandleS mat_attr; 746 | 747 | GU_DetailHandle gdh; 748 | 749 | if (!myOptions.flatten) 750 | { 751 | gdh.allocateAndSet(new GU_Detail); 752 | gd = gdh.writeLock(); 753 | 754 | if (myOptions.loadNames) 755 | { 756 | name_attr = 757 | gd->addStringTuple(GA_ATTRIB_PRIMITIVE, GLTF_NAME_ATTRIB, 1); 758 | } 759 | if (myOptions.loadNames) 760 | { 761 | name_attr = 762 | gd->addStringTuple(GA_ATTRIB_PRIMITIVE, GA_Names::shop_materialpath, 1); 763 | } 764 | } 765 | else 766 | { 767 | gd = parent_gd; 768 | } 769 | 770 | // Now flatten all the submeshes 771 | if (node.mesh != GLTF_INVALID_IDX) 772 | { 773 | const GLTF_Mesh &mesh = *myLoader.getMesh(node.mesh); 774 | const UT_Array &primitives = mesh.primitives; 775 | 776 | for (GLTF_Handle idx = 0; idx < primitives.size(); idx++) 777 | { 778 | GU_DetailHandle prim_gdh; 779 | prim_gdh.allocateAndSet(new GU_Detail, true); 780 | GU_Detail *prim_gd = prim_gdh.writeLock(); 781 | 782 | auto primitive = primitives[idx]; 783 | UT_String mat_path; 784 | if (primitive.material != GLTF_INVALID_IDX) 785 | { 786 | getMaterialPath(primitive.material, mat_path); 787 | } 788 | 789 | if (!GLTF_GeoLoader::load(myLoader, node.mesh, idx, *prim_gd, getGeoOptions())) 790 | { 791 | prim_gdh.unlock(prim_gd); 792 | continue; 793 | } 794 | 795 | UTgetInterrupt()->opInterrupt(); 796 | 797 | // Load as packed primitive 798 | if (!myOptions.flatten) 799 | { 800 | GU_PrimPacked *packed = 801 | GU_PackedGeometry::packGeometry(*gd, prim_gdh); 802 | 803 | if (myOptions.loadNames) 804 | { 805 | name_attr.set(packed->getPointOffset(0), 0, mesh.name); 806 | } 807 | 808 | if (myOptions.loadMats) 809 | { 810 | name_attr.set(packed->getPointOffset(0), 0, mat_path); 811 | } 812 | } 813 | // Else load as a flattened hiereachy 814 | else 815 | { 816 | if (myOptions.loadNames) 817 | { 818 | GA_RWHandleS sm_name_attrib; 819 | sm_name_attrib = prim_gd->addStringTuple( 820 | GA_ATTRIB_PRIMITIVE, GLTF_NAME_ATTRIB, 1); 821 | 822 | for (GA_Offset off : prim_gd->getPrimitiveRange()) 823 | { 824 | sm_name_attrib.set(off, 0, mesh.name); 825 | } 826 | } 827 | 828 | if (myOptions.loadMats) 829 | { 830 | GA_RWHandleS sm_mat_attrib; 831 | sm_mat_attrib = prim_gd->addStringTuple( 832 | GA_ATTRIB_PRIMITIVE, GA_Names::shop_materialpath, 1); 833 | 834 | for (GA_Offset off : prim_gd->getPrimitiveRange()) 835 | { 836 | sm_mat_attrib.set(off, 0, mat_path); 837 | } 838 | } 839 | 840 | prim_gd->transform(cum_xform, 0, 0, true, true, true, true, true); 841 | gd->copy(*prim_gd, GEO_COPY_ADD, true, false, GA_DATA_ID_BUMP); 842 | } 843 | 844 | prim_gdh.unlock(prim_gd); 845 | } 846 | } 847 | 848 | // Now run this on all children with the new transform 849 | for (GLTF_Handle child : node.children) 850 | { 851 | loadNodeRecursive(*myLoader.getNode(child), gd, cum_xform); 852 | } 853 | 854 | if (!myOptions.flatten) 855 | { 856 | GU_PrimPacked *packed = 857 | GU_PackedGeometry::packGeometry(*parent_gd, gdh); 858 | packed->transform(transform); 859 | 860 | UT_Vector3F translate; 861 | transform.getTranslates(translate); 862 | parent_gd->setPos3(packed->getPointOffset(0), translate); 863 | 864 | if (myOptions.loadNames) 865 | { 866 | GA_RWHandleS pname_attrib(parent_gd->findStringTuple( 867 | GA_ATTRIB_PRIMITIVE, GLTF_NAME_ATTRIB, 1, 1)); 868 | if (pname_attrib.isValid()) 869 | pname_attrib.set(packed->getPointOffset(0), 0, node.name); 870 | } 871 | 872 | gdh.unlock(gd); 873 | } 874 | } 875 | 876 | void 877 | SOP_GLTF_Loader::createAndSetName(GU_Detail *detail, const char *name) const 878 | { 879 | // Add the GLTF named as a primitive attribute to the flattened objects 880 | GA_RWAttributeRef str_attrib = 881 | detail->addStringTuple(GA_ATTRIB_PRIMITIVE, GLTF_NAME_ATTRIB, 1); 882 | GA_RWHandleS str_attrib_handle(str_attrib.getAttribute()); 883 | if (str_attrib_handle.isValid()) 884 | { 885 | // Set the attrib for all primitives 886 | for (GA_Iterator it(detail->getPrimitiveRange()); !it.atEnd(); ++it) 887 | { 888 | str_attrib_handle.set(*it, 0, name); 889 | } 890 | } 891 | } 892 | 893 | GLTF_NAMESPACE::GLTF_MeshLoadingOptions 894 | SOP_GLTF_Loader::getGeoOptions() const 895 | { 896 | GLTF_NAMESPACE::GLTF_MeshLoadingOptions options; 897 | options.loadCustomAttribs = myOptions.loadCustomAttribs; 898 | options.promotePointAttribs = myOptions.promotePointAttribs; 899 | options.consolidatePoints = myOptions.consolidateByMesh; 900 | options.pointConsolidationDistance = myOptions.pointConsolidationDistance; 901 | return options; 902 | } 903 | // -------------------------------------------------------------------------------- /src/SOP/SOP_GLTF.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2020 3 | * Side Effects Software Inc. All rights reserved. 4 | * 5 | * Redistribution and use of Houdini Development Kit samples in source and 6 | * binary forms, with or without modification, are permitted provided that the 7 | * following conditions are met: 8 | * 1. Redistributions of source code must retain the above copyright notice, 9 | * this list of conditions and the following disclaimer. 10 | * 2. The name of Side Effects Software may not be used to endorse or 11 | * promote products derived from this software without specific prior 12 | * written permission. 13 | * 14 | * THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 15 | * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 17 | * NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 18 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 19 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 20 | * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 21 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 22 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 23 | * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | * 25 | *---------------------------------------------------------------------------- 26 | */ 27 | 28 | #ifndef __SOP_GLTF_H__ 29 | #define __SOP_GLTF_H__ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | #include 38 | #include 39 | 40 | class GU_Detail; 41 | class GU_PrimPacked; 42 | 43 | enum GLTF_LoadStyle 44 | { 45 | Scene, 46 | Node, 47 | Mesh, 48 | Primitive 49 | }; 50 | 51 | enum GLTF_GeoType 52 | { 53 | Houdini_Geo, 54 | Packed_Primitives 55 | }; 56 | 57 | typedef GLTF_NAMESPACE::GLTF_Int GLTF_Int; 58 | typedef GLTF_NAMESPACE::GLTF_Handle GLTF_Handle; 59 | typedef GLTF_NAMESPACE::GLTF_Node GLTF_Node; 60 | 61 | /// SOP to read GLTF geometry 62 | class SOP_GLTF : public SOP_Node 63 | { 64 | public: 65 | // Standard hdk declarations 66 | static OP_Node * 67 | myConstructor(OP_Network *net, const char *name, OP_Operator *entry); 68 | 69 | static PRM_Template myTemplateList[]; 70 | static void installSOP(OP_OperatorTable *table); 71 | 72 | // Returns a an array of pairs, where the index corresponds to the 73 | // index of the mesh, the first item in the pair is the name of 74 | // the mesh, and the second is the number of primitives 75 | const UT_Array> &getMeshNames() const; 76 | const UT_Array &getNodeNames() const; 77 | const UT_Array &getSceneNames() const; 78 | 79 | protected: 80 | SOP_GLTF(OP_Network *net, const char *name, OP_Operator *op); 81 | virtual ~SOP_GLTF(); 82 | 83 | virtual bool updateParmsFlags() override; 84 | virtual OP_ERROR cookMySop(OP_Context &context) override; 85 | 86 | virtual void getDescriptiveParmName(UT_String &name) const override 87 | { 88 | name = "filename"; 89 | } 90 | 91 | private: 92 | void saveMeshNames(const GLTF_NAMESPACE::GLTF_Loader &loader); 93 | 94 | class Parms 95 | { 96 | public: 97 | Parms(); 98 | 99 | UT_String myFileName; 100 | GLTF_LoadStyle myLoadStyle; 101 | GLTF_GeoType myGeoType; 102 | GLTF_Handle myMeshID; 103 | GLTF_Handle myPrimIndex; 104 | uint32 myUseCustomAttribs; 105 | GLTF_Handle myRootNode; 106 | GLTF_Handle myScene; 107 | uint32 myLoadNames; 108 | uint32 myLoadMats; 109 | uint32 myPromotePointAttrsToVertex; 110 | fpreal myPointConsolidationDistance; 111 | }; 112 | 113 | void evaluateParms(Parms &parms, OP_Context &context); 114 | 115 | UT_Array myNodes; 116 | // The pair consists of 117 | UT_Array> myMeshes; 118 | UT_Array myScenes; 119 | }; 120 | 121 | class SOP_GLTF_Loader 122 | { 123 | public: 124 | struct Options 125 | { 126 | Options() = default; 127 | ~Options() = default; 128 | bool loadNames = false; 129 | bool flatten = false; 130 | bool loadMats = false; 131 | bool loadCustomAttribs = false; 132 | UT_String material_path; 133 | bool promotePointAttribs = true; 134 | bool consolidateByMesh = true; 135 | fpreal pointConsolidationDistance = 0.0001F; 136 | }; 137 | 138 | SOP_GLTF_Loader(const GLTF_NAMESPACE::GLTF_Loader &loader, GU_Detail *detail, 139 | Options options); 140 | 141 | void loadMesh(const GLTF_Handle mesh_idx); 142 | void loadNode(const GLTF_Node &node); 143 | void loadScene(GLTF_Handle scene_idx); 144 | bool loadPrimitive(GLTF_Handle node_idx, GLTF_Handle prim_idx); 145 | 146 | private: 147 | void getMaterialPath(GLTF_Int index, UT_String &path); 148 | 149 | // Puts the current node in parent_gd as a packed primitive 150 | // with the name as well as transforms 151 | void loadNodeRecursive(const GLTF_Node &node, GU_Detail *parent_gd, 152 | UT_Matrix4F cum_xform); 153 | 154 | void createAndSetName(GU_Detail *detail, const char *name) const; 155 | GLTF_NAMESPACE::GLTF_MeshLoadingOptions getGeoOptions() const; 156 | 157 | const GLTF_NAMESPACE::GLTF_Loader &myLoader; 158 | GU_Detail *myDetail; 159 | const Options myOptions; 160 | }; 161 | 162 | #endif 163 | -------------------------------------------------------------------------------- /src/gltf_hierarchy.hda/INDEX__SECTION: -------------------------------------------------------------------------------- 1 | Operator: gltf_hierarchy 2 | Label: glTF Hierarchy 3 | Path: oplib:/Object/gltf_hierarchy?Object/gltf_hierarchy 4 | Icon: OBJ_gltf_hierarchy 5 | Table: Object 6 | License: 7 | Extra: 8 | User: 9 | Inputs: 0 to 0 10 | Subnet: true 11 | Python: false 12 | Empty: false 13 | Modified: Wed Jan 23 13:58:11 2019 14 | 15 | -------------------------------------------------------------------------------- /src/gltf_hierarchy.hda/Object_1gltf__hierarchy/CreateScript: -------------------------------------------------------------------------------- 1 | # Automatically generated script 2 | \set noalias = 1 3 | # 4 | # Creation script for gltf_hierarchy operator 5 | # 6 | 7 | if ( "$arg1" == "" ) then 8 | echo This script is intended as a creation script 9 | exit 10 | endif 11 | 12 | # Node $arg1 (Object/gltf_hierarchy) 13 | opexprlanguage -s hscript $arg1 14 | opuserdata -n '___Version___' -v '' $arg1 15 | 16 | opcf $arg1 17 | 18 | opcf .. 19 | -------------------------------------------------------------------------------- /src/gltf_hierarchy.hda/Object_1gltf__hierarchy/DialogScript: -------------------------------------------------------------------------------- 1 | # Dialog script for gltf_hierarchy automatically generated 2 | 3 | { 4 | name gltf_hierarchy 5 | script gltf_hierarchy 6 | label "glTF Hierarchy" 7 | 8 | help { 9 | "" 10 | } 11 | 12 | inputlabel 1 "Sub-Network Input #1" 13 | inputlabel 2 "Sub-Network Input #2" 14 | inputlabel 3 "Sub-Network Input #3" 15 | inputlabel 4 "Sub-Network Input #4" 16 | 17 | group { 18 | name "stdswitcher3_1" 19 | label "Subnet" 20 | invisibletab 21 | 22 | parm { 23 | name "label1" 24 | baseparm 25 | label "Input #1 Label" 26 | invisible 27 | export dialog 28 | } 29 | parm { 30 | name "label2" 31 | baseparm 32 | label "Input #2 Label" 33 | invisible 34 | export dialog 35 | } 36 | parm { 37 | name "label3" 38 | baseparm 39 | label "Input #3 Label" 40 | invisible 41 | export dialog 42 | } 43 | parm { 44 | name "label4" 45 | baseparm 46 | label "Input #4 Label" 47 | invisible 48 | export dialog 49 | } 50 | parm { 51 | name "tdisplay" 52 | baseparm 53 | label "Display" 54 | invisible 55 | joinnext 56 | export all 57 | } 58 | parm { 59 | name "display" 60 | baseparm 61 | label "Display" 62 | invisible 63 | export all 64 | } 65 | parm { 66 | name "outputobj" 67 | baseparm 68 | label "Output Transform" 69 | invisible 70 | export all 71 | } 72 | parm { 73 | name "visibleobjects" 74 | baseparm 75 | label "Visible Children" 76 | invisible 77 | export none 78 | } 79 | parm { 80 | name "picking" 81 | baseparm 82 | label "Viewport Selecting Enabled" 83 | invisible 84 | export none 85 | } 86 | parm { 87 | name "pickscript" 88 | baseparm 89 | label "Select Script" 90 | invisible 91 | export none 92 | } 93 | parm { 94 | name "caching" 95 | baseparm 96 | label "Cache Object Transform" 97 | invisible 98 | export none 99 | } 100 | parm { 101 | name "use_dcolor" 102 | baseparm 103 | label "Set Wireframe Color" 104 | invisible 105 | export none 106 | } 107 | parm { 108 | name "dcolor" 109 | baseparm 110 | label "Wireframe Color" 111 | invisible 112 | export none 113 | } 114 | } 115 | 116 | parm { 117 | name "filename" 118 | label "File Name" 119 | type file 120 | default { "" } 121 | parmtag { "filechooser_mode" "read" } 122 | parmtag { "filechooser_pattern" "*.gltf, *.glb" } 123 | parmtag { "script_callback" "hou.pwd().hdaModule().EvaluateMenus(kwargs['node'])" } 124 | parmtag { "script_callback_language" "python" } 125 | } 126 | parm { 127 | name "assetfolder" 128 | label "Asset Extraction Folder" 129 | type directory 130 | default { "$HIP/glTF_Assets" } 131 | } 132 | parm { 133 | name "scene" 134 | label "Scene" 135 | type string 136 | default { "0" } 137 | menu { 138 | [ "kwargs['node'].hdaModule().GenerateSceneMenu(kwargs['node'])" ] 139 | language python 140 | } 141 | parmtag { "script_callback_language" "python" } 142 | } 143 | parm { 144 | name "buildscene" 145 | label "Build Scene" 146 | type button 147 | default { "0" } 148 | parmtag { "export_disable" "1" } 149 | parmtag { "script_callback" "kwargs['node'].hdaModule().ReloadGeometry(kwargs['node'])" } 150 | parmtag { "script_callback_language" "python" } 151 | } 152 | parm { 153 | name "lockgeo" 154 | label "Lock Geometry" 155 | type toggle 156 | default { "on" } 157 | disablewhen "{ importgeometry == 0 }" 158 | } 159 | parm { 160 | name "flattenhierarchy" 161 | label "Flatten Hierarchy" 162 | type toggle 163 | default { "off" } 164 | parmtag { "import_enable" "1" } 165 | } 166 | parm { 167 | name "promotepointattrs" 168 | label "Promote Point Attributes To Vertex" 169 | type toggle 170 | default { "1" } 171 | } 172 | parm { 173 | name "pointconsolidatedist" 174 | label "Points Merge Distance" 175 | type float 176 | default { "0.0001" } 177 | disablewhen "{ promotepointattrs == 0 }" 178 | range { 0! 10 } 179 | } 180 | group { 181 | name "stdswitcher3_2" 182 | label "Filter Options" 183 | 184 | parm { 185 | name "importgeometry" 186 | label "Import Geometry" 187 | type toggle 188 | default { "on" } 189 | disablewhen "{ flattenhierarchy != 0 }" 190 | } 191 | parm { 192 | name "importcustomattributes" 193 | label "Import Custom Attributes" 194 | type toggle 195 | default { "on" } 196 | disablewhen "{ importgeometry == 0 }" 197 | } 198 | parm { 199 | name "importmaterials" 200 | label "Import Materials" 201 | type toggle 202 | default { "on" } 203 | } 204 | parm { 205 | name "importnongeo" 206 | label "Import Non-Geometry" 207 | type toggle 208 | default { "off" } 209 | disablewhen "{ importgeometry == 0 } { flattenhierarchy != 0 }" 210 | } 211 | parm { 212 | name "importunusedmaterials" 213 | label "Import Unused Materials" 214 | type toggle 215 | default { "off" } 216 | disablewhen "{ importmaterials == 0 } { flattenhierarchy != 0 }" 217 | } 218 | } 219 | 220 | group { 221 | name "stdswitcher3_2_1" 222 | label "Transform" 223 | 224 | parm { 225 | name "xOrd" 226 | baseparm 227 | label "Transform Order" 228 | joinnext 229 | export none 230 | } 231 | parm { 232 | name "rOrd" 233 | baseparm 234 | label "Scene" 235 | nolabel 236 | export none 237 | } 238 | parm { 239 | name "t" 240 | baseparm 241 | label "Translate" 242 | export none 243 | } 244 | parm { 245 | name "r" 246 | baseparm 247 | label "Rotate" 248 | export none 249 | } 250 | parm { 251 | name "s" 252 | baseparm 253 | label "Scale" 254 | export none 255 | } 256 | parm { 257 | name "p" 258 | baseparm 259 | label "Pivot Translate" 260 | export none 261 | } 262 | parm { 263 | name "pr" 264 | baseparm 265 | label "Pivot Rotate" 266 | export none 267 | } 268 | parm { 269 | name "scale" 270 | baseparm 271 | label "Uniform Scale" 272 | export none 273 | } 274 | parm { 275 | name "pre_xform" 276 | baseparm 277 | label "Modify Pre-Transform" 278 | export none 279 | } 280 | parm { 281 | name "keeppos" 282 | baseparm 283 | label "Keep Position When Parenting" 284 | export none 285 | } 286 | parm { 287 | name "childcomp" 288 | baseparm 289 | label "Child Compensation" 290 | export none 291 | } 292 | parm { 293 | name "constraints_on" 294 | baseparm 295 | label "Enable Constraints" 296 | export none 297 | } 298 | parm { 299 | name "constraints_path" 300 | baseparm 301 | label "Constraints" 302 | invisible 303 | export none 304 | } 305 | parm { 306 | name "lookatpath" 307 | baseparm 308 | label "Look At" 309 | invisible 310 | export none 311 | } 312 | parm { 313 | name "lookupobjpath" 314 | baseparm 315 | label "Look Up Object" 316 | invisible 317 | export none 318 | } 319 | parm { 320 | name "lookup" 321 | baseparm 322 | label "Look At Up Vector" 323 | invisible 324 | export none 325 | } 326 | parm { 327 | name "pathobjpath" 328 | baseparm 329 | label "Path Object" 330 | invisible 331 | export none 332 | } 333 | parm { 334 | name "roll" 335 | baseparm 336 | label "Roll" 337 | invisible 338 | export none 339 | } 340 | parm { 341 | name "pos" 342 | baseparm 343 | label "Position" 344 | invisible 345 | export none 346 | } 347 | parm { 348 | name "uparmtype" 349 | baseparm 350 | label "Parameterization" 351 | invisible 352 | export none 353 | } 354 | parm { 355 | name "pathorient" 356 | baseparm 357 | label "Orient Along Path" 358 | invisible 359 | export none 360 | } 361 | parm { 362 | name "up" 363 | baseparm 364 | label "Orient Up Vector" 365 | invisible 366 | export none 367 | } 368 | parm { 369 | name "bank" 370 | baseparm 371 | label "Auto-Bank factor" 372 | invisible 373 | export none 374 | } 375 | } 376 | 377 | } 378 | -------------------------------------------------------------------------------- /src/gltf_hierarchy.hda/Object_1gltf__hierarchy/ExtraFileOptions: -------------------------------------------------------------------------------- 1 | { 2 | "PythonModule/Cursor":{ 3 | "type":"intarray", 4 | "value":[595,64] 5 | }, 6 | "PythonModule/IsExpr":{ 7 | "type":"bool", 8 | "value":false 9 | }, 10 | "PythonModule/IsPython":{ 11 | "type":"bool", 12 | "value":true 13 | }, 14 | "PythonModule/IsScript":{ 15 | "type":"bool", 16 | "value":true 17 | }, 18 | "PythonModule/Source":{ 19 | "type":"string", 20 | "value":"" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/gltf_hierarchy.hda/Object_1gltf__hierarchy/Help: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniGLTF/6546e41c89e666a5eaa4743abe41f25cbeb9c125/src/gltf_hierarchy.hda/Object_1gltf__hierarchy/Help -------------------------------------------------------------------------------- /src/gltf_hierarchy.hda/Object_1gltf__hierarchy/InternalFileOptions: -------------------------------------------------------------------------------- 1 | { 2 | "nodeconntype":{ 3 | "type":"bool", 4 | "value":false 5 | }, 6 | "nodeparmtype":{ 7 | "type":"bool", 8 | "value":false 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/gltf_hierarchy.hda/Object_1gltf__hierarchy/PythonModule: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import hou 4 | import string 5 | import struct 6 | import tempfile 7 | import base64 8 | import _gltf_hom_extensions as _gltf 9 | 10 | try: 11 | from hou import ui 12 | except: 13 | ui = None 14 | 15 | 16 | ############################################## 17 | # General utils 18 | 19 | # Throw an error and exit if we give it an invalid dir 20 | 21 | # (This is taken from Alembic Archive exporter) 22 | _sanitizeTable = None 23 | 24 | def _sanitizeName(name): 25 | def valid(i): 26 | ch = chr(i) 27 | if ch.isalnum(): 28 | return ch 29 | return '_' 30 | global _sanitizeTable 31 | if not _sanitizeTable: 32 | letters = ''.join([string.letters, string.digits, '_']) 33 | # Full alphabet 34 | alpha = ''.join(map(chr, range(256))) 35 | xlate = ''.join(map(valid, range(256))) 36 | _sanitizeTable = string.maketrans(alpha, xlate) 37 | name = string.translate(name, _sanitizeTable) 38 | if name[0].isdigit(): 39 | name = '_' + name 40 | return name 41 | 42 | ############################################### 43 | # Parm utils 44 | 45 | def _setNodeName(node, name): 46 | if len(name) == 0: 47 | return 48 | sanitized_name = _sanitizeName(name) 49 | node.setName(sanitized_name, True) 50 | 51 | def _setParmValue(node, parmname, value): 52 | parm = node.parm(parmname) 53 | if parm: 54 | parm.set(value) 55 | 56 | ################################################## 57 | 58 | def _new_geonode(parent, name, filename, mesh_id, locked, custom_attribs, promote_point_attrs, point_consolidation_dist): 59 | geo = parent.createNode("geo") 60 | gltf_sop = geo.createNode("gltf") 61 | _setNodeName(gltf_sop, name) 62 | 63 | _setParmValue(gltf_sop, "filename", filename) 64 | _setParmValue(gltf_sop, "loadby", "mesh") 65 | _setParmValue(gltf_sop, "meshid", mesh_id) 66 | _setParmValue(gltf_sop, "materialassigns", True) 67 | _setParmValue(gltf_sop, "usecustomattribs", custom_attribs) 68 | _setParmValue(gltf_sop, "promotepointattrs", promote_point_attrs) 69 | _setParmValue(gltf_sop, "pointconsolidatedist", point_consolidation_dist) 70 | gltf_sop.setHardLocked(locked) 71 | 72 | return geo, gltf_sop 73 | 74 | def _new_matnet(parent, name): 75 | matnet = parent.createNode("matnet") 76 | _setNodeName(matnet, name) 77 | return matnet 78 | 79 | def _new_pbr_node(parent): 80 | pbr_shadernode = parent.createNode('principledshader') 81 | 82 | return pbr_shadernode 83 | 84 | def _get_suffix_from_mimetype(mimetype): 85 | if mimetype == "image/jpeg": 86 | return ".jpg" 87 | elif mimetype == "image/png": 88 | return ".png" 89 | else: 90 | raise Exception("Invalid image mimeType!") 91 | 92 | # Creates a new null node based on the parameters given in "node" 93 | # The node has no transform if "node" contains no TRS or affine 94 | # matrix translation. 95 | def set_transform(node, gltf_node): 96 | if "matrix" in gltf_node: 97 | matrix = hou.Matrix4(gltf_node["matrix"]) 98 | node.setParmTransform(matrix) 99 | node.setParmTransform(matrix) 100 | 101 | if "translation" in gltf_node: 102 | translation = hou.Vector3(gltf_node["translation"]) 103 | node.parmTuple("t").set(translation) 104 | 105 | if "rotation" in gltf_node: 106 | rotation = hou.Quaternion(gltf_node["rotation"]) 107 | 108 | node.parmTuple("r").set(rotation.extractEulerRotates()) 109 | 110 | if "scale" in gltf_node: 111 | scale = hou.Vector3(gltf_node["scale"]) 112 | node.parmTuple("s").set(scale) 113 | 114 | return node 115 | 116 | ################################################## 117 | class GLTF_Scene: 118 | def __init__(self, root_node, filename, json_obj, is_glb, settings): 119 | self.json_obj = json_obj 120 | self.filename = filename 121 | file_head, file_tail = os.path.split(filename) 122 | file_name, file_ext = os.path.splitext(file_tail) 123 | self.file_tail = file_name 124 | self.root_node = root_node 125 | self.base_folder = os.path.dirname(filename) 126 | self.material_cache = dict() 127 | self.image_cache = dict() 128 | self.is_glb = is_glb 129 | self.tmp_img_ctr = 0 130 | self.buffer_data = [None] 131 | self.settings = settings 132 | 133 | # The number of times a name has appearaed 134 | self.name_set = dict() 135 | if "materials" in json_obj: 136 | for material in json_obj["materials"]: 137 | if not "name" in material: 138 | continue 139 | name = material["name"] 140 | appearances = 0 141 | if name in self.name_set: 142 | appearances = self.name_set[name] 143 | self.name_set[name] = appearances + 1 144 | 145 | if "buffers" in json_obj: 146 | self.buffer_data = [None] * len(json_obj["buffers"]) 147 | if "nodes" in json_obj: 148 | self.node_hasgeo = [None] * len(json_obj["nodes"]) 149 | 150 | def determine_nodes_with_geo(self): 151 | if not "nodes" in self.json_obj: 152 | return 153 | 154 | def recurs(node, idx): 155 | if self.node_hasgeo[idx]: 156 | return True 157 | 158 | if "mesh" in node: 159 | self.node_hasgeo[idx] = True 160 | return True 161 | 162 | if "children" in node: 163 | for child in node["children"]: 164 | if(recurs(self.json_obj["nodes"][child], idx)): 165 | self.node_hasgeo[idx] = True 166 | return True 167 | 168 | return False 169 | 170 | 171 | for (idx, node) in enumerate(self.json_obj["nodes"]): 172 | recurs(node, idx) 173 | 174 | def load_flattened(self, scene): 175 | self.init_networks() 176 | 177 | c_atrbs = self.settings["import_cust_attribs"] 178 | lock = self.settings["lock_geo"] 179 | filename = self.settings["filename"] 180 | 181 | geo = self.root_node.createNode("geo") 182 | gltf_sop = geo.createNode("gltf") 183 | 184 | _setParmValue(gltf_sop, "filename", filename) 185 | _setParmValue(gltf_sop, "loadby", "scene") 186 | _setParmValue(gltf_sop, "scene", 0) 187 | _setParmValue(gltf_sop, "usecustomattribs", c_atrbs) 188 | _setParmValue(gltf_sop, "loadMeshNames", True) 189 | _setParmValue(gltf_sop, "materialassigns", self.settings["import_mats"]) 190 | 191 | gltf_sop.setHardLocked(lock) 192 | 193 | if self.settings["import_mats"]: 194 | if self.settings["import_mats"] and "materials" in self.json_obj: 195 | for idx in xrange(len(self.json_obj["materials"])): 196 | self.import_material(idx) 197 | 198 | return geo, gltf_sop 199 | 200 | # This is seperated from the constructor, because this class can 201 | # potentially also be used for purposes other than building geometry 202 | def load(self): 203 | self.init_networks() 204 | 205 | if self.settings["import_unusedmats"] and "materials" in self.json_obj: 206 | for idx in xrange(len(self.json_obj["materials"])): 207 | self.import_material(idx) 208 | 209 | self.determine_nodes_with_geo() 210 | 211 | def get_abspath(self, filename): 212 | return os.path.join(os.path.abspath(self.base_folder), 213 | filename).replace("\\", "/") 214 | 215 | def init_networks(self): 216 | self.matnet = _new_matnet(self.root_node, "materials") 217 | 218 | def get_bufferview_data(self, bufferview_idx): 219 | bufferview = self.json_obj["bufferViews"][bufferview_idx] 220 | buffer_data = self.buffer_data[bufferview["buffer"]] 221 | 222 | byte_offset = 0 223 | if "byteOffset" in bufferview: 224 | byte_offset = bufferview["byteOffset"] 225 | byte_length = bufferview["byteLength"] 226 | 227 | return buffer_data[byte_offset : byte_offset + byte_length] 228 | 229 | def get_buffer_data(self, buffer_idx): 230 | if self.buffer_data[buffer_idx] == None: 231 | self.load_buffer(buffer_idx) 232 | return self.buffer_data[buffer_idx] 233 | 234 | def load_buffer(self, buffer_idx): 235 | buffer = self.json_obj["buffers"][buffer_idx] 236 | 237 | if "uri" in buffer: 238 | buffer_uri = buffer["uri"] 239 | with open(buffer_uri, "r") as f: 240 | buffer_data = f.read(length) 241 | self.buffer_data[buffer_idx] = buffer_data 242 | return 243 | else: 244 | raise Exception("TODO: Implement b64 buffer support!") 245 | 246 | def parse_scene(self, scene_id): 247 | 248 | scene = self.json_obj["scenes"][scene_id] 249 | for node in scene["nodes"]: 250 | self.walk_node(node, None) 251 | 252 | def walk_node(self, node_id, parent): 253 | if not self.node_hasgeo[node_id] and not self.settings["import_non_geo"]: 254 | return 255 | 256 | node = self.json_obj["nodes"][node_id] 257 | 258 | # Import each submesh 259 | if "mesh" in node and self.settings["import_geometry"]: 260 | new_node = self.create_mesh(node["mesh"]) 261 | else: 262 | new_node = self.root_node.createNode("null") 263 | 264 | set_transform(new_node, node) 265 | 266 | if "name" in node: 267 | _setNodeName(new_node, node["name"]) 268 | 269 | if parent != None: 270 | new_node.setNextInput(parent) 271 | 272 | # Import each child 273 | if "children" in node: 274 | for child in node["children"]: 275 | self.walk_node(child, new_node) 276 | 277 | def new_image_filename(self, image, image_idx, suffix): 278 | name = self.settings["asset_folder"] 279 | name = name + self.file_tail + "_" + str(image_idx) 280 | if "name" in image: 281 | name = name + "_" + image["name"] 282 | name = name + suffix 283 | return name 284 | 285 | def get_image_uri(self, image_idx): 286 | if image_idx in self.image_cache: 287 | return self.image_cache[image_idx] 288 | 289 | image = self.json_obj["images"][image_idx] 290 | if "uri" in image: 291 | # Unpack the image from 292 | uri = image["uri"] 293 | if uri[:5] == 'data:': 294 | data_start = uri.find(';base64,') 295 | if data_start != -1: 296 | data = uri[data_start+8:] 297 | mimetype = uri[5:data_start] 298 | suffix = _get_suffix_from_mimetype(mimetype) 299 | new_filename = self.new_image_filename(image, image_idx, suffix) 300 | with open(new_filename, 'wb') as newFile: 301 | newFile.write(base64.b64decode(data)) 302 | final_path = newFile.name.replace("\\", "/") 303 | self.image_cache[image_idx] = final_path 304 | return final_path 305 | 306 | # The image is already external - just return the path 307 | return self.get_abspath(image["uri"]) 308 | 309 | # Unpack the image from the binary buffer 310 | if "bufferView" in image: 311 | 312 | suffix = _get_suffix_from_mimetype(image["mimeType"]) 313 | 314 | data = self.get_bufferview_data(image["bufferView"]) 315 | 316 | new_filename = self.new_image_filename(image, image_idx, suffix) 317 | with open(new_filename, 'wb') as newFile: 318 | newFile.write(data) 319 | final_path = newFile.name.replace("\\", "/") 320 | self.image_cache[image_idx] = final_path 321 | return final_path 322 | 323 | def _translate_normal_texture(self, pbr_node, normal_texture): 324 | _setParmValue(pbr_node, "baseBumpAndNormal_enable", True) 325 | 326 | image_idx = normal_texture["index"] 327 | img_path = self.get_image_uri(image_idx) 328 | 329 | _setParmValue(pbr_node, "baseNormal_texture", img_path) 330 | 331 | if "scale" in normal_texture: 332 | _setParmValue(pbr_node, "baseNormal_scale", normal_texture["scale"]) 333 | 334 | def _translate_emissive_texture(self, pbr_node, emissive_texture): 335 | image = self.json_obj["images"][emissive_texture["index"]] 336 | _setParmValue(pbr_node, "emitcolor_useTexture", True) 337 | 338 | image_idx = emissive_texture["index"] 339 | img_path = self.get_image_uri(image_idx) 340 | _setParmValue(pbr_node, "emitcolor_texture", img_path) 341 | 342 | def _translate_metallic_roughness(self, pbr_node, mr_params): 343 | if "baseColorFactor" in mr_params: 344 | pbr_node.parmTuple("basecolor").set(mr_params["baseColorFactor"][:3]) 345 | # Alpha is stored seperately from color in Principled Shader 346 | _setParmValue(pbr_node, "opac", mr_params["baseColorFactor"][3]) 347 | else: 348 | pbr_node.parmTuple("basecolor").set([1, 1, 1]) 349 | _setParmValue(pbr_node, "opac", 1) 350 | 351 | if "baseColorTexture" in mr_params: 352 | _setParmValue(pbr_node, "basecolor_useTexture", True) 353 | texture = self.json_obj["textures"][mr_params["baseColorTexture"]["index"]] 354 | if "source" in texture: 355 | image_idx = texture["source"] 356 | imgpath = self.get_image_uri(image_idx) 357 | _setParmValue( 358 | pbr_node, 359 | "basecolor_texture", 360 | imgpath 361 | ) 362 | 363 | if "metallicFactor" in mr_params: 364 | _setParmValue(pbr_node, "metallic", mr_params["metallicFactor"]) 365 | else: 366 | _setParmValue(pbr_node, "metallic", 1) 367 | 368 | if "roughnessFactor" in mr_params: 369 | _setParmValue(pbr_node, "rough", mr_params["roughnessFactor"]) 370 | else: 371 | _setParmValue(pbr_node, "rough", 1) 372 | 373 | if "metallicRoughnessTexture" in mr_params: 374 | _setParmValue(pbr_node, "metallic_useTexture", True) 375 | _setParmValue(pbr_node, "rough_useTexture", True) 376 | texture = self.json_obj["textures"][mr_params["metallicRoughnessTexture"]["index"]] 377 | if "source" in texture: 378 | image_idx = texture["source"] 379 | img_path = self.get_image_uri(image_idx) 380 | _setParmValue(pbr_node, "baseNormal_texture", img_path) 381 | 382 | # Hook up metal/roughness material to texture 383 | _setParmValue(pbr_node, "metallic_texture", img_path) 384 | _setParmValue(pbr_node, "metallic_monoChannel", 3) 385 | 386 | _setParmValue(pbr_node, "rough_texture", img_path) 387 | _setParmValue(pbr_node, "rough_monoChannel", 2) 388 | 389 | # We use a naming scheme material_name_idx for cases of duplicate 390 | # materials 391 | def get_material_name(self, mat_idx, mat_name): 392 | is_duplicate = mat_name in self.name_set and self.name_set[mat_name] > 1 393 | if is_duplicate: 394 | return mat_name + "_" + str(mat_idx) 395 | 396 | return mat_name 397 | 398 | def import_material(self, material_id): 399 | # Check if we've created the material already 400 | if material_id in self.material_cache: 401 | return self.material_cache[material_id] 402 | 403 | material = self.json_obj["materials"][material_id] 404 | 405 | pbr_node = _new_pbr_node(self.matnet) 406 | if "name" in material: 407 | name = self.get_material_name(material_id, material["name"]) 408 | else: 409 | name = self.get_material_name(material_id, pbr_node.name()) 410 | _setNodeName(pbr_node, name) 411 | 412 | if "pbrMetallicRoughness" in material: 413 | self._translate_metallic_roughness(pbr_node, material["pbrMetallicRoughness"]) 414 | 415 | if "normalTexture" in material: 416 | self._translate_normal_texture(pbr_node, material["normalTexture"]) 417 | 418 | if "emissiveTexture" in material: 419 | self._translate_emissive_texture(pbr_node, material["emissiveTexture"]) 420 | 421 | if "emissiveFactor" in material: 422 | pbr_node.parmTuple("emitcolor").set(material["emissiveFactor"]) 423 | 424 | # TODO: Handle other material properties 425 | self.material_cache[material_id] = pbr_node 426 | 427 | return pbr_node 428 | 429 | def import_primitive(self, mesh, mesh_name, mesh_id): 430 | geonode, gltf_geo_node = _new_geonode( 431 | self.root_node, 432 | mesh_name, 433 | self.filename, 434 | mesh_id, 435 | self.settings["lock_geo"], 436 | self.settings["import_cust_attribs"], 437 | self.settings["promote_point_attrs"], 438 | self.settings["point_consolidation_dist"] 439 | ) 440 | 441 | for idx, prim in enumerate(mesh["primitives"]): 442 | if "material" in prim and self.settings["import_mats"]: 443 | material_node = self.import_material(prim["material"]) 444 | 445 | return geonode 446 | 447 | def create_mesh(self, mesh_id): 448 | mesh = self.json_obj["meshes"][mesh_id] 449 | 450 | if "name" in mesh: 451 | name = mesh["name"] 452 | else: 453 | name = "" 454 | 455 | mesh_node = self.import_primitive(mesh, name, mesh_id) 456 | 457 | return mesh_node 458 | 459 | ################################################## 460 | 461 | def _destroy_children(rootnode): 462 | for child in rootnode.children(): 463 | child.destroy() 464 | 465 | def LoadGLB(rootnode, filename, settings): 466 | with open(filename, "rb") as glb_file: 467 | 468 | content = glb_file.read() 469 | 470 | offset = 0 471 | header = struct.unpack_from("4sII", content) 472 | ver = header[1] 473 | 474 | if ver != 2: 475 | raise Exception("Attempted to load unsupported GLB version") 476 | 477 | offset = 12 478 | 479 | # Read in JSON chunk 480 | json_chunk = struct.unpack_from("I4s", content, offset) 481 | 482 | chunk_length = json_chunk[0] 483 | chunk_type = json_chunk[1] 484 | 485 | json_data = content[offset + 8 :offset + 8 + chunk_length] 486 | json_obj = json.loads(json_data) 487 | 488 | offset = offset + 8 + chunk_length 489 | 490 | # Read in DATA chunk 491 | data_chunk = struct.unpack_from("I4s", content, offset) 492 | data_chunk_length = data_chunk[0] 493 | data_chunk_type = data_chunk[1] 494 | bin_data = content[offset + 8 : offset + 8 + data_chunk_length] 495 | 496 | scene = GLTF_Scene(rootnode, filename, json_obj, True, settings) 497 | scene.buffer_data[0] = bin_data 498 | 499 | if settings["flattenhierarchy"]: 500 | scene.load_flattened(settings["scene"]) 501 | else: 502 | scene.load() 503 | scene.parse_scene(settings["scene"]) 504 | 505 | rootnode.layoutChildren() 506 | scene.matnet.layoutChildren() 507 | 508 | def LoadGLTF(rootnode, filename, settings): 509 | json_obj = json.load(open(filename)) 510 | scene = GLTF_Scene(rootnode, filename, json_obj, False, settings) 511 | 512 | if settings["flattenhierarchy"]: 513 | scene.load_flattened(settings["scene"]) 514 | else: 515 | scene.load() 516 | scene.parse_scene(settings["scene"]) 517 | 518 | # Format nodes nicely 519 | rootnode.layoutChildren() 520 | scene.matnet.layoutChildren() 521 | 522 | def EnsureAssetFolder(dirname, original_dirname): 523 | if not os.path.exists(dirname): 524 | try: 525 | os.makedirs(dirname) 526 | except IOError: 527 | return False 528 | 529 | # Need to check permissions on dir itself 530 | if not os.access(original_dirname, os.W_OK | os.X_OK): 531 | return False 532 | 533 | return True 534 | 535 | def LoadHierarchy(filename, rootnode): 536 | settings = {} 537 | settings["import_geometry"] = rootnode.parm("importgeometry").evalAsInt() != 0 538 | settings["import_cust_attribs"] = rootnode.parm("importcustomattributes").evalAsInt() != 0 539 | settings["import_mats"] = rootnode.parm("importmaterials").evalAsInt() != 0 540 | settings["import_unusedmats"] = (rootnode.parm("importunusedmaterials").evalAsInt()) != 0 and settings["import_mats"] 541 | settings["lock_geo"] = rootnode.parm("lockgeo").evalAsInt() != 0 542 | settings["import_non_geo"] = rootnode.parm("importnongeo").evalAsInt() != 0 543 | settings["scene"] = int(rootnode.parm("scene").evalAsString()) 544 | settings["asset_folder"] = rootnode.parm("assetfolder").evalAsString() 545 | settings["flattenhierarchy"] = rootnode.parm("flattenhierarchy").evalAsInt() != 0 546 | settings["filename"] = filename 547 | settings["promote_point_attrs"] = rootnode.parm("promotepointattrs").evalAsInt() != 0 548 | settings["point_consolidation_dist"] = rootnode.parm("pointconsolidatedist").evalAsFloat(); 549 | 550 | original_name = settings["asset_folder"] 551 | # Allow for both formats like $HIP/folder and $HIP/folder/ 552 | if not settings["asset_folder"].endswith("/"): 553 | settings["asset_folder"] += "/" 554 | 555 | # Check if we have an external image at the start, so that if we can't 556 | # unpack it in the asset folder, we just bail 557 | has_external_image = False 558 | 559 | if not EnsureAssetFolder(settings["asset_folder"], original_name): 560 | ui.displayMessage(title='Unable to Access Asset Folder', 561 | text='Unable to access directory ' + original_name + '. A valid external directory is required to unpack glTF buffers.', 562 | severity=hou.severityType.Error) 563 | return 564 | 565 | (root, ext) = os.path.splitext(filename) 566 | if ext.lower() == ".gltf": 567 | LoadGLTF(rootnode, filename, settings) 568 | elif ext.lower() == ".glb": 569 | LoadGLB(rootnode, filename, settings) 570 | else: 571 | ui.displayMessage(title='Invalid glTF File Type', 572 | text='Please select a valid file with an extension of .gltf or .glb.', 573 | severity=hou.severityType.Error) 574 | return 575 | 576 | def ReloadGeometry(rootnode): 577 | _destroy_children(rootnode) 578 | 579 | filename = rootnode.parm("filename").evalAsString() 580 | flatten = rootnode.parm("flattenhierarchy").evalAsInt() != 0 581 | 582 | _gltf.gltfClearCache(filename) 583 | 584 | LoadHierarchy(filename, rootnode) 585 | 586 | 587 | ##################################### 588 | 589 | def GenerateSceneMenu(rootnode): 590 | filename = rootnode.parm("filename").evalAsString() 591 | scenes = _gltf.gltfGetSceneList(filename) 592 | return scenes 593 | 594 | def EvaluateMenus(rootnode): 595 | scenes = GenerateSceneMenu(rootnode) 596 | if(len(scenes) == 0): 597 | if (ui): 598 | ui.displayMessage(title='No Scenes', 599 | text='Please select a GLTF file containing one or more scene', 600 | severity=hou.severityType.Error) 601 | return 602 | 603 | hou.pwd().parm("scene").set(GenerateSceneMenu(rootnode)[0]) -------------------------------------------------------------------------------- /src/gltf_hierarchy.hda/Object_1gltf__hierarchy/Sections.list: -------------------------------------------------------------------------------- 1 | "" 2 | DialogScript DialogScript 3 | CreateScript CreateScript 4 | TypePropertiesOptions TypePropertiesOptions 5 | Help Help 6 | Tools.shelf Tools.shelf 7 | InternalFileOptions InternalFileOptions 8 | PythonModule PythonModule 9 | ExtraFileOptions ExtraFileOptions 10 | -------------------------------------------------------------------------------- /src/gltf_hierarchy.hda/Object_1gltf__hierarchy/Tools.shelf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | OBJ 11 | 12 | 13 | $HDA_TABLE_AND_NAME 14 | 15 | Import 16 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/gltf_hierarchy.hda/Object_1gltf__hierarchy/TypePropertiesOptions: -------------------------------------------------------------------------------- 1 | SaveSpareParms := 0; 2 | CheckExternal := 1; 3 | SaveIcon := 0; 4 | GzipContents := 1; 5 | ContentsCompressionType := 1; 6 | UnlockOnCreate := 0; 7 | SaveCachedCode := 0; 8 | LockContents := 0; 9 | MakeDefault := 1; 10 | UseDSParms := 1; 11 | ForbidOutsideParms := 1; 12 | PrefixDroppedParmLabel := 0; 13 | PrefixDroppedParmName := 0; 14 | ParmsFromVfl := 0; 15 | -------------------------------------------------------------------------------- /src/gltf_hierarchy.hda/Sections.list: -------------------------------------------------------------------------------- 1 | "" 2 | INDEX__SECTION INDEX_SECTION 3 | houdini.hdalibrary houdini.hdalibrary 4 | Object_1gltf__hierarchy Object/gltf_hierarchy 5 | -------------------------------------------------------------------------------- /src/gltf_hierarchy.hda/houdini.hdalibrary: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniGLTF/6546e41c89e666a5eaa4743abe41f25cbeb9c125/src/gltf_hierarchy.hda/houdini.hdalibrary --------------------------------------------------------------------------------