├── .gitignore ├── Config ├── BaseHoudiniNiagara.ini └── FilterPlugin.ini ├── Content ├── HoudiniEvent.uasset ├── Modules │ ├── InitHoudiniPointCache.uasset │ ├── SampleHoudiniPointCache.uasset │ ├── SampleSpawnedHoudiniPointCache.uasset │ └── SpawnParticlesFromHoudiniPointCache.uasset ├── Particles │ ├── Emitters │ │ ├── HoudiniInterpolate_02.uasset │ │ ├── HoudiniNiagaraBasic.uasset │ │ ├── SingleFrameEvent.uasset │ │ └── SpawnFromHoudiniLocationEvent.uasset │ └── Systems │ │ ├── HoudiniEventsSimple.uasset │ │ └── RibbonFromInterpolate.uasset └── PointCache │ ├── P_on_surface.uasset │ ├── interp_word_02.uasset │ └── single_frame_event.uasset ├── HoudiniNiagara.uplugin ├── LICENSE.md ├── README.md ├── Resources ├── HCSVIcon128.png └── Icon128.png └── Source ├── HoudiniNiagara ├── Classes │ └── NiagaraDataInterfaceHoudini.h ├── HoudiniNiagara.Build.cs ├── Private │ ├── HoudiniNiagara.cpp │ ├── HoudiniPointCache.cpp │ ├── HoudiniPointCacheLoader.cpp │ ├── HoudiniPointCacheLoaderBJSON.cpp │ ├── HoudiniPointCacheLoaderCSV.cpp │ ├── HoudiniPointCacheLoaderJSON.cpp │ ├── HoudiniPointCacheLoaderJSONBase.cpp │ └── NiagaraDataInterfaceHoudini.cpp └── Public │ ├── HoudiniNiagara.h │ ├── HoudiniPointCache.h │ ├── HoudiniPointCacheLoader.h │ ├── HoudiniPointCacheLoaderBJSON.h │ ├── HoudiniPointCacheLoaderCSV.h │ ├── HoudiniPointCacheLoaderJSON.h │ └── HoudiniPointCacheLoaderJSONBase.h └── HoudiniNiagaraEditor ├── HoudiniNiagaraEditor.Build.cs ├── Private ├── HoudiniNiagaraEditor.cpp ├── HoudiniPointCacheAssetActions.cpp ├── HoudiniPointCacheExporterBase.cpp ├── HoudiniPointCacheExporterBase.h ├── HoudiniPointCacheExporterHBJSON.cpp ├── HoudiniPointCacheExporterHBJSON.h ├── HoudiniPointCacheExporterHCSV.cpp ├── HoudiniPointCacheExporterHCSV.h ├── HoudiniPointCacheExporterHJSON.cpp ├── HoudiniPointCacheExporterHJSON.h └── HoudiniPointCacheFactory.cpp └── Public ├── HoudiniNiagaraEditor.h ├── HoudiniPointCacheAssetActions.h └── HoudiniPointCacheFactory.h /.gitignore: -------------------------------------------------------------------------------- 1 | Binaries/ 2 | Intermediate/ -------------------------------------------------------------------------------- /Config/BaseHoudiniNiagara.ini: -------------------------------------------------------------------------------- 1 | [CoreRedirects] 2 | +ClassRedirects=(OldName="/Script/HoudiniNiagara.HoudiniCSV", NewName="/Script/HoudiniNiagara.HoudiniPointCache") 3 | +ClassRedirects=(OldName="/Script/HoudiniNiagara.NiagaraDataInterfaceHoudiniCSV", NewName="/Script/HoudiniNiagara.NiagaraDataInterfaceHoudini") 4 | -------------------------------------------------------------------------------- /Config/FilterPlugin.ini: -------------------------------------------------------------------------------- 1 | [FilterPlugin] 2 | ; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and 3 | ; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. 4 | ; 5 | ; Examples: 6 | ; /README.txt 7 | ; /Extras/... 8 | ; /Binaries/ThirdParty/*.dll 9 | /Config/BaseHoudiniNiagara.ini -------------------------------------------------------------------------------- /Content/HoudiniEvent.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniNiagara/593d1ec4a5e16019674cbedabc6587fcad5d2891/Content/HoudiniEvent.uasset -------------------------------------------------------------------------------- /Content/Modules/InitHoudiniPointCache.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniNiagara/593d1ec4a5e16019674cbedabc6587fcad5d2891/Content/Modules/InitHoudiniPointCache.uasset -------------------------------------------------------------------------------- /Content/Modules/SampleHoudiniPointCache.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniNiagara/593d1ec4a5e16019674cbedabc6587fcad5d2891/Content/Modules/SampleHoudiniPointCache.uasset -------------------------------------------------------------------------------- /Content/Modules/SampleSpawnedHoudiniPointCache.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniNiagara/593d1ec4a5e16019674cbedabc6587fcad5d2891/Content/Modules/SampleSpawnedHoudiniPointCache.uasset -------------------------------------------------------------------------------- /Content/Modules/SpawnParticlesFromHoudiniPointCache.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniNiagara/593d1ec4a5e16019674cbedabc6587fcad5d2891/Content/Modules/SpawnParticlesFromHoudiniPointCache.uasset -------------------------------------------------------------------------------- /Content/Particles/Emitters/HoudiniInterpolate_02.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniNiagara/593d1ec4a5e16019674cbedabc6587fcad5d2891/Content/Particles/Emitters/HoudiniInterpolate_02.uasset -------------------------------------------------------------------------------- /Content/Particles/Emitters/HoudiniNiagaraBasic.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniNiagara/593d1ec4a5e16019674cbedabc6587fcad5d2891/Content/Particles/Emitters/HoudiniNiagaraBasic.uasset -------------------------------------------------------------------------------- /Content/Particles/Emitters/SingleFrameEvent.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniNiagara/593d1ec4a5e16019674cbedabc6587fcad5d2891/Content/Particles/Emitters/SingleFrameEvent.uasset -------------------------------------------------------------------------------- /Content/Particles/Emitters/SpawnFromHoudiniLocationEvent.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniNiagara/593d1ec4a5e16019674cbedabc6587fcad5d2891/Content/Particles/Emitters/SpawnFromHoudiniLocationEvent.uasset -------------------------------------------------------------------------------- /Content/Particles/Systems/HoudiniEventsSimple.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniNiagara/593d1ec4a5e16019674cbedabc6587fcad5d2891/Content/Particles/Systems/HoudiniEventsSimple.uasset -------------------------------------------------------------------------------- /Content/Particles/Systems/RibbonFromInterpolate.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniNiagara/593d1ec4a5e16019674cbedabc6587fcad5d2891/Content/Particles/Systems/RibbonFromInterpolate.uasset -------------------------------------------------------------------------------- /Content/PointCache/P_on_surface.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniNiagara/593d1ec4a5e16019674cbedabc6587fcad5d2891/Content/PointCache/P_on_surface.uasset -------------------------------------------------------------------------------- /Content/PointCache/interp_word_02.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniNiagara/593d1ec4a5e16019674cbedabc6587fcad5d2891/Content/PointCache/interp_word_02.uasset -------------------------------------------------------------------------------- /Content/PointCache/single_frame_event.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniNiagara/593d1ec4a5e16019674cbedabc6587fcad5d2891/Content/PointCache/single_frame_event.uasset -------------------------------------------------------------------------------- /HoudiniNiagara.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 2, 4 | "VersionName": "2.6.0", 5 | "EngineVersion": "5.2", 6 | "FriendlyName": "Houdini Niagara", 7 | "Description": "Import Houdini point cloud data into UE5 to drive Niagara particle systems.", 8 | "Category": "FX", 9 | "CreatedBy": "Side Effects Software Inc.", 10 | "CreatedByURL": "http://www.sidefx.com", 11 | "DocsURL": "https://www.sidefx.com/docs/houdini/unreal/niagara.html", 12 | "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/79d2dd58387c4696a0f3458275f739d6", 13 | "SupportURL": "https://www.sidefx.com/bugs/submit/", 14 | "EnabledByDefault": false, 15 | "CanContainContent": true, 16 | "IsBetaVersion": false, 17 | "Installed": false, 18 | 19 | "Modules": 20 | [ 21 | { 22 | "Name": "HoudiniNiagara", 23 | "Type": "Runtime", 24 | "LoadingPhase": "Default" 25 | }, 26 | { 27 | "Name": "HoudiniNiagaraEditor", 28 | "Type": "Editor", 29 | "LoadingPhase": "Default" 30 | } 31 | ], 32 | 33 | "Plugins": 34 | [ 35 | { 36 | "Name": "Niagara", 37 | "Enabled": true 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2021 3 | Side Effects Software Inc. All rights reserved. 4 | 5 | Redistribution and use of in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, 9 | this list of conditions and the following disclaimer. 10 | 11 | 2. The names Side Effects Software and SideFX may not be used to endorse or 12 | promote products derived from this software without specific prior 13 | written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE `AS IS' AND ANY EXPRESS 16 | OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 17 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN 18 | NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT, 19 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 21 | OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 24 | EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Houdini Niagara plug-in for Unreal 2 | 3 | This plug-in adds a new "Houdini Data Interface" to Niagara. 4 | This version of the plugin is currently updated for UE5.4, but is compatible with previous versions of Unreal. 5 | 6 | The data interface allows importing and processing Houdini Point Cache assets in Niagara. 7 | The point cache files can be exported using the Niagara ROP, available via the SideFXLabs tools. 8 | https://www.sidefx.com/tutorials/sidefx-labs-installation/ 9 | 10 | Supported file types for the point caches are: 11 | - *.hjson: Houdini JSON point cache (ascii) 12 | - *.hbjson: Houdini JSON point cache (binary) 13 | - *.hcsv: Houdini CSV point cache (legacy CSV, used by previous version of this plugin) 14 | 15 | ### To install it: 16 | 17 | The Houdini Niagara plugin now ships with Houdini builds. 18 | It can be found in the engine folder of your Houdini install, next to the Houdini Engine plugin: 19 | 20 | __Path\To\Your\Houdini\Install\Houdini XX.Y.ZZZ\engine\unreal\5.x\HoudiniNiagara__ 21 | 22 | Alternatively, you can also download the prebuilt binaries in the "releases" section of this repo. 23 | 1. Unzip the downloaded files. 24 | 2. Copy the HoudiniNiagara folder into __Your_Unreal_Project/Plugins/FX__. 25 | 26 | ### To build it: 27 | - Copy the plug-in files to your UE5 source directory. (in Engine/Plugins/FX) 28 | - Build UE5 29 | 30 | You will now have access to the Houdini Niagara plug-in (under the Project/FX Category). 31 | 32 | Once enabled, the plug-in will give you access to a new Houdini Point Cache Data Interface in the Niagara Script Editor. The Data Interface will allow you to use multiples nodes and functions to access and parse data from the exported point cache. 33 | 34 | The Niagara plug-in must be enabled as well, as the Houdini Niagara plug-in depends on it too. 35 | 36 | ### For more information: 37 | 38 | #### Documentation: 39 | [Documentation for the plugin can be found here](https://www.sidefx.com/docs/houdini/unreal/niagara.html) 40 | 41 | #### Quick Start Videos: 42 | https://www.sidefx.com/tutorials/houdini-to-ue4s-niagara/ 43 | 44 | #### Demo Content: 45 | [Example hip files and a content plugin can be found here.](https://drive.google.com/open?id=1yvTNEq-kaPeecJzP3C34xxGq2h_eQERX) 46 | 47 | To install the content plugin: 48 | 1. Create a new ue5 project (if you don't have one already) 49 | 2. Copy HoudiniNiagaraDemo2020 into the Plugins folder of your Unreal project. 50 | 51 | #### Additional info and links to video tutorials here: 52 | https://www.sidefx.com/forum/topic/73075/ 53 | 54 | #### Programmable VFX with Unreal Engine's Niagara | GDC 2018 55 | https://youtu.be/mNPYdfRVPtM?t=2269 56 | 57 | 58 | -------------------------------------------------------------------------------- /Resources/HCSVIcon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniNiagara/593d1ec4a5e16019674cbedabc6587fcad5d2891/Resources/HCSVIcon128.png -------------------------------------------------------------------------------- /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sideeffects/HoudiniNiagara/593d1ec4a5e16019674cbedabc6587fcad5d2891/Resources/Icon128.png -------------------------------------------------------------------------------- /Source/HoudiniNiagara/Classes/NiagaraDataInterfaceHoudini.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | #pragma once 24 | 25 | #include "HoudiniPointCache.h" 26 | 27 | #include "CoreMinimal.h" 28 | #include "NiagaraCommon.h" 29 | #include "NiagaraDataInterface.h" 30 | #include "NiagaraShared.h" 31 | #include "Runtime/Launch/Resources/Version.h" 32 | #include "UObject/ObjectMacros.h" 33 | #include "VectorVM.h" 34 | 35 | #include "NiagaraDataInterfaceHoudini.generated.h" 36 | 37 | #if ENGINE_MAJOR_VERSION==5 && ENGINE_MINOR_VERSION < 2 38 | #if WITH_EDITORONLY_DATA 39 | #define HOUDINI_COMPILE_NIAGARA 40 | #endif 41 | #else 42 | #define HOUDINI_COMPILE_NIAGARA 43 | #endif 44 | 45 | USTRUCT() 46 | struct FHoudiniEvent 47 | { 48 | GENERATED_USTRUCT_BODY() 49 | 50 | UPROPERTY(EditAnywhere, Category = "Houdini Event") 51 | FVector Position; 52 | 53 | UPROPERTY(EditAnywhere, Category = "Houdini Event") 54 | FVector Normal; 55 | 56 | UPROPERTY(EditAnywhere, Category = "Houdini Event") 57 | float Impulse; 58 | 59 | UPROPERTY(EditAnywhere, Category = "Houdini Event") 60 | FVector Velocity; 61 | 62 | UPROPERTY(EditAnywhere, Category = "Houdini Event") 63 | int32 PointID; 64 | 65 | UPROPERTY(EditAnywhere, Category = "Houdini Event") 66 | float Time; 67 | 68 | UPROPERTY(EditAnywhere, Category = "Houdini Event") 69 | float Life; 70 | 71 | UPROPERTY(EditAnywhere, Category = "Houdini Event") 72 | FLinearColor Color; 73 | 74 | UPROPERTY(EditAnywhere, Category = "Houdini Event") 75 | int32 Type; 76 | 77 | FHoudiniEvent() 78 | : Position(FVector::ZeroVector) 79 | , Normal(FVector::ZeroVector) 80 | , Impulse(0.0f) 81 | , Velocity( FVector::ZeroVector) 82 | , PointID(0) 83 | , Time(0.0f) 84 | , Life(0.0f) 85 | , Color(FLinearColor::White) 86 | , Type(0) 87 | { 88 | } 89 | 90 | inline bool operator==(const FHoudiniEvent& Other) const 91 | { 92 | if ( (Other.Position != Position) || (Other.Normal != Normal) 93 | || (Other.Velocity != Velocity) || (Other.Impulse != Impulse) 94 | || (Other.PointID != PointID) || (Other.Time != Time) 95 | || (Other.Life != Life) || (Other.Color != Color) 96 | || (Other.Type != Type) ) 97 | return false; 98 | 99 | return true; 100 | } 101 | 102 | inline bool operator!=(const FHoudiniEvent& Other) const 103 | { 104 | return !(*this == Other); 105 | } 106 | }; 107 | 108 | 109 | /** Data Interface allowing sampling of UHoudiniPointCache assets (CSV, .json (binary) files) files. */ 110 | UCLASS(EditInlineNew, Category = "Houdini Niagara", meta = (DisplayName = "Houdini Point Cache Info")) 111 | class HOUDININIAGARA_API UNiagaraDataInterfaceHoudini : public UNiagaraDataInterface 112 | { 113 | GENERATED_UCLASS_BODY() 114 | 115 | #if ENGINE_MAJOR_VERSION==5 && ENGINE_MINOR_VERSION < 1 116 | public: 117 | DECLARE_NIAGARA_DI_PARAMETER(); 118 | 119 | #else 120 | BEGIN_SHADER_PARAMETER_STRUCT(FShaderParameters, ) 121 | SHADER_PARAMETER(int32, NumberOfSamples) 122 | SHADER_PARAMETER(int32, NumberOfAttributes) 123 | SHADER_PARAMETER(int32, NumberOfPoints) 124 | SHADER_PARAMETER(int32, MaxNumberOfIndexesPerPoint) 125 | SHADER_PARAMETER(int32, LastSpawnedPointId) 126 | SHADER_PARAMETER(float, LastSpawnTime) 127 | SHADER_PARAMETER(float, LastSpawnTimeRequest) 128 | SHADER_PARAMETER_SRV(Buffer, FloatValuesBuffer) 129 | SHADER_PARAMETER_SRV(Buffer, SpecialAttributeIndexesBuffer) 130 | SHADER_PARAMETER_SRV(Buffer, SpawnTimesBuffer) 131 | SHADER_PARAMETER_SRV(Buffer, LifeValuesBuffer) 132 | SHADER_PARAMETER_SRV(Buffer, PointTypesBuffer) 133 | SHADER_PARAMETER_SRV(Buffer, PointValueIndexesBuffer) 134 | SHADER_PARAMETER_SRV(Buffer, FunctionIndexToAttributeIndexBuffer) 135 | END_SHADER_PARAMETER_STRUCT() 136 | public: 137 | 138 | #endif 139 | 140 | UPROPERTY(EditAnywhere, Category = "Houdini Niagara", meta = (DisplayName = "Houdini Point Cache Asset")) 141 | TObjectPtr HoudiniPointCacheAsset; 142 | //#if ENGINE_MAJOR_VERSION==5 && ENGINE_MINOR_VERSION < 1 143 | // UHoudiniPointCache* HoudiniPointCacheAsset; 144 | //#else 145 | // // Houdini Point Cache Asset to sample 146 | // TObjectPtr HoudiniPointCacheAsset; 147 | //#endif 148 | 149 | 150 | //---------------------------------------------------------------------------- 151 | // UObject Interface 152 | virtual void PostInitProperties() override; 153 | virtual void PostLoad() override; 154 | #if WITH_EDITOR 155 | virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; 156 | #endif 157 | //---------------------------------------------------------------------------- 158 | // UNiagaraDataInterface Interface 159 | 160 | // Returns the DI's functions signatures 161 | virtual void GetFunctions(TArray& OutFunctions)override; 162 | 163 | // Returns the delegate for the passed function signature. 164 | virtual void GetVMExternalFunction(const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction &OutFunc) override; 165 | 166 | virtual bool Equals(const UNiagaraDataInterface* Other) const override; 167 | 168 | //---------------------------------------------------------------------------- 169 | // EXPOSED FUNCTIONS 170 | 171 | // Returns the float value at a given sample index and attribute index in the point cache 172 | void GetFloatValue(FVectorVMExternalFunctionContext& Context); 173 | 174 | // Returns a float value for a given sample index and attribute name in the point cache 175 | void GetFloatValueByString(FVectorVMExternalFunctionContext& Context, const FString& Attribute); 176 | 177 | // Returns a Vector3 value for a given sample index in the point cache 178 | void GetVectorValue(FVectorVMExternalFunctionContext& Context); 179 | 180 | // Returns a Vector3 value for a given sample index in the point cache by attribute 181 | void GetVectorValueByString(FVectorVMExternalFunctionContext& Context, const FString& Attribute); 182 | 183 | // Returns a Vector3 value for a given sample index in the point cache 184 | void GetVectorValueEx(FVectorVMExternalFunctionContext& Context); 185 | 186 | // Returns a Vector3 value for a given sample index in the point cache by attribute 187 | void GetVectorValueExByString(FVectorVMExternalFunctionContext& Context, const FString& Attribute); 188 | 189 | // Returns a Vector4 value for a given sample index in the point cache 190 | void GetVector4Value(FVectorVMExternalFunctionContext& Context); 191 | 192 | // Returns a Vector4 value for a given sample index in the point cache by attribute 193 | void GetVector4ValueByString(FVectorVMExternalFunctionContext& Context, const FString& Attribute); 194 | 195 | // Returns a Quat value for a given sample index in the point cache 196 | void GetQuatValue(FVectorVMExternalFunctionContext& Context); 197 | 198 | // Returns a Quat value for a given sample index in the point cache by attribute 199 | void GetQuatValueByString(FVectorVMExternalFunctionContext& Context, const FString& Attribute); 200 | 201 | // Returns the positions for a given sample index in the point cache 202 | void GetPosition(FVectorVMExternalFunctionContext& Context); 203 | 204 | // Returns the normals for a given sample index in the point cache 205 | void GetNormal(FVectorVMExternalFunctionContext& Context); 206 | 207 | // Returns the time for a given sample index in the point cache 208 | void GetTime(FVectorVMExternalFunctionContext& Context); 209 | 210 | // Returns the velocity for a given sample index in the point cache 211 | void GetVelocity(FVectorVMExternalFunctionContext& Context); 212 | 213 | // Returns the color for a given sample index in the point cache 214 | void GetColor(FVectorVMExternalFunctionContext& Context); 215 | 216 | // Returns the impulse value for a given sample index in the point cache 217 | void GetImpulse(FVectorVMExternalFunctionContext& Context); 218 | 219 | // Returns the position and time for a given sample index in the point cache 220 | void GetPositionAndTime(FVectorVMExternalFunctionContext& Context); 221 | 222 | // Returns the number of samples found in the point cache 223 | void GetNumberOfSamples(FVectorVMExternalFunctionContext& Context); 224 | 225 | // Returns the number of attributes in the point cache 226 | void GetNumberOfAttributes(FVectorVMExternalFunctionContext& Context); 227 | 228 | // Returns the number of unique points (by id) in the point cache 229 | void GetNumberOfPoints(FVectorVMExternalFunctionContext& Context); 230 | 231 | // Returns the last sample index of the points that should be spawned at time t 232 | void GetLastSampleIndexAtTime(FVectorVMExternalFunctionContext& Context); 233 | 234 | // Returns the indexes (min, max) and number of points that should be spawned at time t 235 | void GetPointIDsToSpawnAtTime(FVectorVMExternalFunctionContext& Context); 236 | 237 | // Returns the position for a given point at a given time 238 | void GetPointPositionAtTime(FVectorVMExternalFunctionContext& Context); 239 | 240 | // Returns a float value for a given point at a given time 241 | void GetPointValueAtTime(FVectorVMExternalFunctionContext& Context); 242 | 243 | // Returns a float value by attribute name for a given point at a given time 244 | void GetPointValueAtTimeByString(FVectorVMExternalFunctionContext& Context, const FString& Attribute); 245 | 246 | // Returns a Vector value for a given point at a given time 247 | void GetPointVectorValueAtTime(FVectorVMExternalFunctionContext& Context); 248 | 249 | // Returns a Vector value by attribute name for a given point at a given time 250 | void GetPointVectorValueAtTimeByString(FVectorVMExternalFunctionContext& Context, const FString& Attribute); 251 | 252 | // Returns a Vector value for a given point at a given time 253 | void GetPointVectorValueAtTimeEx(FVectorVMExternalFunctionContext& Context); 254 | 255 | // Returns a Vector value by attribute name for a given point at a given time 256 | void GetPointVectorValueAtTimeExByString(FVectorVMExternalFunctionContext& Context, const FString& Attribute); 257 | 258 | // Returns a Vector4 value for a given point at a given time 259 | void GetPointVector4ValueAtTime(FVectorVMExternalFunctionContext& Context); 260 | 261 | // Returns a Vector4 value by attribute name for a given point at a given time 262 | void GetPointVector4ValueAtTimeByString(FVectorVMExternalFunctionContext& Context, const FString& Attribute); 263 | 264 | // Returns a Quat value for a given point at a given time 265 | void GetPointQuatValueAtTime(FVectorVMExternalFunctionContext& Context); 266 | 267 | // Returns a Quat value by attribute name for a given point at a given time 268 | void GetPointQuatValueAtTimeByString(FVectorVMExternalFunctionContext& Context, const FString& Attribute); 269 | 270 | // Returns the sample indexes (previous, next) for reading values for a given point at a given time 271 | void GetSampleIndexesForPointAtTime(FVectorVMExternalFunctionContext& Context); 272 | 273 | // Return the life value for a given point 274 | void GetPointLife(FVectorVMExternalFunctionContext& Context); 275 | 276 | // Return the life of a given point at a given time 277 | //template 278 | void GetPointLifeAtTime(FVectorVMExternalFunctionContext& Context); 279 | 280 | // Return the type value for a given point 281 | void GetPointType(FVectorVMExternalFunctionContext& Context); 282 | 283 | //---------------------------------------------------------------------------- 284 | // Standard attribute accessor as a stopgap. These can be removed when 285 | // string-based attribute lookups work on both the CPU and GPU. 286 | 287 | // Sample a vector attribute value for a given point at a given time 288 | void GetPointGenericVectorAttributeAtTime(EHoudiniAttributes Attribute, FVectorVMExternalFunctionContext& Context, bool DoSwap, bool DoScale); 289 | 290 | // Sample a float attribute value for a given point at a given time 291 | void GetPointGenericFloatAttributeAtTime(EHoudiniAttributes Attribute, FVectorVMExternalFunctionContext& Context); 292 | 293 | // Sample a float attribute value for a given point at a given time 294 | void GetPointGenericInt32AttributeAtTime(EHoudiniAttributes Attribute, FVectorVMExternalFunctionContext& Context); 295 | 296 | void GetPointNormalAtTime(FVectorVMExternalFunctionContext& Context); 297 | 298 | void GetPointColorAtTime(FVectorVMExternalFunctionContext& Context); 299 | 300 | void GetPointAlphaAtTime(FVectorVMExternalFunctionContext& Context); 301 | 302 | void GetPointVelocityAtTime(FVectorVMExternalFunctionContext& Context); 303 | 304 | void GetPointImpulseAtTime(FVectorVMExternalFunctionContext& Context); 305 | 306 | void GetPointTypeAtTime(FVectorVMExternalFunctionContext& Context); 307 | 308 | //---------------------------------------------------------------------------- 309 | // GPU / HLSL Functions 310 | #if ENGINE_MAJOR_VERSION==5 && ENGINE_MINOR_VERSION < 1 311 | #if WITH_EDITORONLY_DATA 312 | virtual bool GetFunctionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, const FNiagaraDataInterfaceGeneratedFunction& FunctionInfo, int FunctionInstanceIndex, FString& OutHLSL) override; 313 | virtual void GetParameterDefinitionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) override; 314 | virtual void GetCommonHLSL(FString& OutHLSL) override; 315 | #endif 316 | #else 317 | 318 | #if WITH_EDITORONLY_DATA 319 | virtual bool GetFunctionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, const FNiagaraDataInterfaceGeneratedFunction& FunctionInfo, int FunctionInstanceIndex, FString& OutHLSL) override; 320 | virtual bool AppendCompileHash(FNiagaraCompileHashVisitor* InVisitor) const override; 321 | virtual void GetParameterDefinitionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) override; 322 | virtual void GetCommonHLSL(FString& OutHLSL) override; 323 | #endif 324 | 325 | //virtual bool UseLegacyShaderBindings() const override { return false; } 326 | virtual void BuildShaderParameters(FNiagaraShaderParametersBuilder& ShaderParametersBuilder) const override; 327 | virtual void SetShaderParameters(const FNiagaraDataInterfaceSetShaderParametersContext& Context) const override; 328 | virtual FNiagaraDataInterfaceParametersCS* CreateShaderStorage(const FNiagaraDataInterfaceGPUParamInfo& ParameterInfo, const FShaderParameterMap& ParameterMap) const override; 329 | virtual const FTypeLayoutDesc* GetShaderStorageType() const override; 330 | 331 | #endif 332 | 333 | virtual bool CanExecuteOnTarget(ENiagaraSimTarget Target)const override { return true; } 334 | 335 | // Members for GPU compatibility 336 | 337 | // Buffer and member variables base name 338 | static const FString NumberOfSamplesBaseName; 339 | static const FString NumberOfAttributesBaseName; 340 | static const FString NumberOfPointsBaseName; 341 | static const FString FloatValuesBufferBaseName; 342 | static const FString SpecialAttributeIndexesBufferBaseName; 343 | static const FString SpawnTimesBufferBaseName; 344 | static const FString LifeValuesBufferBaseName; 345 | static const FString PointTypesBufferBaseName; 346 | static const FString MaxNumberOfIndexesPerPointBaseName; 347 | static const FString PointValueIndexesBufferBaseName; 348 | static const FString LastSpawnedPointIdBaseName; 349 | static const FString LastSpawnTimeBaseName; 350 | static const FString LastSpawnTimeRequestBaseName; 351 | static const FString FunctionIndexToAttributeIndexBufferBaseName; 352 | 353 | // Member variables accessors 354 | FORCEINLINE int32 GetNumberOfSamples()const { return HoudiniPointCacheAsset ? HoudiniPointCacheAsset->GetNumberOfSamples() : 0; } 355 | FORCEINLINE int32 GetNumberOfAttributes()const { return HoudiniPointCacheAsset ? HoudiniPointCacheAsset->GetNumberOfAttributes() : 0; } 356 | FORCEINLINE int32 GetNumberOfPoints()const { return HoudiniPointCacheAsset ? HoudiniPointCacheAsset->GetNumberOfPoints() : 0; } 357 | FORCEINLINE int32 GetMaxNumberOfIndexesPerPoints()const { return HoudiniPointCacheAsset ? HoudiniPointCacheAsset->GetMaxNumberOfPointValueIndexes() + 1 : 0; } 358 | 359 | // GPU Buffers accessors 360 | /*FRWBuffer& GetFloatValuesGPUBuffer(); 361 | FRWBuffer& GetSpecialAttributesColumnIndexesGPUBuffer(); 362 | FRWBuffer& GetSpawnTimesGPUBuffer(); 363 | FRWBuffer& GetLifeValuesGPUBuffer(); 364 | FRWBuffer& GetPointTypesGPUBuffer(); 365 | FRWBuffer& GetPointValueIndexesGPUBuffer();*/ 366 | 367 | protected: 368 | virtual void PushToRenderThreadImpl() override; 369 | 370 | // Get the index of the function at InFunctionIndex in InGenerationFunctions if we only count functions with 371 | // the Attribute function specifier. 372 | // Return false if the function itself does not have the Attribute specifier, or if InGeneratedFunctions is empty. 373 | bool GetAttributeFunctionIndex(const TArray& InGeneratedFunctions, int InFunctionIndex, int &OutColTitleFunctionIndex) const; 374 | 375 | virtual bool CopyToInternal(UNiagaraDataInterface* Destination) const override; 376 | 377 | // The following state variables are now stored in Niagara on the emitter itself 378 | // as opposed to being stored internally. 379 | // // Last Spawned PointID 380 | // UPROPERTY() 381 | // int32 LastSpawnedPointID; 382 | // // Last Spawn time 383 | // UPROPERTY() 384 | // float LastSpawnTime; 385 | 386 | // // Float to track last desired time of GetPointIDsToSpawnAtTime and GetLastPointIDToSpawnAtTime(). 387 | // // This is used to detect emitter loops 388 | // UPROPERTY() 389 | // float LastSpawnTimeRequest; 390 | }; 391 | 392 | struct FNiagaraDataInterfaceProxyHoudini : public FNiagaraDataInterfaceProxy 393 | { 394 | FHoudiniPointCacheResource* Resource; 395 | 396 | TArray FunctionIndexToAttributeIndex; 397 | bool bFunctionIndexToAttributeIndexHasBeenBuilt; 398 | FRWBuffer FunctionIndexToAttributeIndexGPUBuffer; 399 | 400 | FNiagaraDataInterfaceProxyHoudini(); 401 | virtual ~FNiagaraDataInterfaceProxyHoudini(); 402 | 403 | virtual int32 PerInstanceDataPassedToRenderThreadSize() const override 404 | { 405 | return 0; 406 | } 407 | 408 | #if ENGINE_MAJOR_VERSION==5 && ENGINE_MINOR_VERSION < 1 409 | void UpdateFunctionIndexToAttributeIndexBuffer(const TMemoryImageArray& FunctionIndexToAttribute, bool bForceUpdate = false); 410 | #else 411 | void UpdateFunctionIndexToAttributeIndexBuffer(const TMemoryImageArray& FunctionIndexToAttribute, bool bForceUpdate = false); 412 | #endif 413 | 414 | }; -------------------------------------------------------------------------------- /Source/HoudiniNiagara/HoudiniNiagara.Build.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | using UnrealBuildTool; 25 | 26 | public class HoudiniNiagara : ModuleRules 27 | { 28 | public HoudiniNiagara(ReadOnlyTargetRules Target) : base(Target) 29 | { 30 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 31 | 32 | PrivateIncludePaths.AddRange(new string[] { 33 | "HoudiniNiagara/Private", 34 | }); 35 | PrivateDependencyModuleNames.AddRange( 36 | new string[] 37 | { 38 | "CoreUObject", 39 | "Engine", 40 | "Slate", 41 | "SlateCore", 42 | "NiagaraCore", 43 | "Niagara", 44 | "NiagaraShader", 45 | "RenderCore" 46 | } 47 | ); 48 | 49 | 50 | PublicDependencyModuleNames.AddRange( 51 | new string[] 52 | { 53 | "Core", 54 | "NiagaraCore", 55 | "Niagara", 56 | "NiagaraShader", 57 | "CoreUObject", 58 | "VectorVM", 59 | "RHI", 60 | "NiagaraVertexFactories", 61 | "RenderCore", 62 | "Json" 63 | } 64 | ); 65 | 66 | 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Source/HoudiniNiagara/Private/HoudiniNiagara.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #include "HoudiniNiagara.h" 25 | #include "Modules/ModuleManager.h" 26 | 27 | #define LOCTEXT_NAMESPACE "FHoudiniNiagaraModule" 28 | 29 | void FHoudiniNiagaraModule::StartupModule() 30 | { 31 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 32 | } 33 | 34 | void FHoudiniNiagaraModule::ShutdownModule() 35 | { 36 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 37 | // we call this function before unloading the module. 38 | } 39 | 40 | #undef LOCTEXT_NAMESPACE 41 | 42 | IMPLEMENT_MODULE(FHoudiniNiagaraModule, HoudiniNiagara) 43 | -------------------------------------------------------------------------------- /Source/HoudiniNiagara/Private/HoudiniPointCacheLoader.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #include "HoudiniPointCacheLoader.h" 25 | 26 | #include "HoudiniPointCache.h" 27 | 28 | #include "CoreMinimal.h" 29 | #include "HAL/PlatformProcess.h" 30 | #include "Misc/FileHelper.h" 31 | #include "Misc/Paths.h" 32 | #include "ShaderCompiler.h" 33 | 34 | FHoudiniPointCacheSortPredicate::FHoudiniPointCacheSortPredicate(const int32 &InTimeAttrIndex, const int32 &InAgeAttrIndex, const int32 &InIDAttrIndex ) 35 | : TimeAttributeIndex( InTimeAttrIndex ), AgeAttributeIndex(InAgeAttrIndex), IDAttributeIndex( InIDAttrIndex ) 36 | { 37 | 38 | } 39 | 40 | bool FHoudiniPointCacheSortPredicate::operator()( const TArray& A, const TArray& B ) const 41 | { 42 | float ATime = TNumericLimits< float >::Lowest(); 43 | if ( A.IsValidIndex( TimeAttributeIndex ) ) 44 | ATime = FCString::Atof( *A[ TimeAttributeIndex ] ); 45 | 46 | float BTime = TNumericLimits< float >::Lowest(); 47 | if ( B.IsValidIndex( TimeAttributeIndex ) ) 48 | BTime = FCString::Atof( *B[ TimeAttributeIndex ] ); 49 | 50 | if ( ATime != BTime ) 51 | { 52 | return ATime < BTime; 53 | } 54 | else 55 | { 56 | float AAge = TNumericLimits< float >::Lowest(); 57 | if (A.IsValidIndex(AgeAttributeIndex)) 58 | AAge = FCString::Atof(*A[AgeAttributeIndex]); 59 | 60 | float BAge = TNumericLimits< float >::Lowest(); 61 | if (B.IsValidIndex(AgeAttributeIndex)) 62 | BAge = FCString::Atof(*B[AgeAttributeIndex]); 63 | 64 | if (AAge != BAge) 65 | { 66 | return BAge < AAge; 67 | } 68 | else 69 | { 70 | float AID = TNumericLimits< float >::Lowest(); 71 | if (A.IsValidIndex(IDAttributeIndex)) 72 | AID = FCString::Atof(*A[IDAttributeIndex]); 73 | 74 | float BID = TNumericLimits< float >::Lowest(); 75 | if (B.IsValidIndex(IDAttributeIndex)) 76 | BID = FCString::Atof(*B[IDAttributeIndex]); 77 | 78 | return AID <= BID; 79 | } 80 | } 81 | } 82 | 83 | FHoudiniPointCacheLoader::FHoudiniPointCacheLoader(const FString& InFilePath) 84 | : FilePath(InFilePath) 85 | { 86 | 87 | } 88 | 89 | FHoudiniPointCacheLoader::~FHoudiniPointCacheLoader() 90 | { 91 | 92 | } 93 | 94 | #if WITH_EDITOR 95 | bool FHoudiniPointCacheLoader::LoadRawPointCacheData(UHoudiniPointCache* InAsset, const FString& InFilePath) const 96 | { 97 | InAsset->Modify(); 98 | return FFileHelper::LoadFileToArray( InAsset->RawDataCompressed, *InFilePath ); 99 | } 100 | #endif 101 | 102 | 103 | #if WITH_EDITOR 104 | void FHoudiniPointCacheLoader::CompressRawData(UHoudiniPointCache* InAsset) const 105 | { 106 | constexpr ECompressionFlags CompressFlags = COMPRESS_BiasMemory; 107 | const uint32 UncompressedSize = InAsset->RawDataCompressed.Num(); 108 | 109 | const FName CompressionName = NAME_Oodle; 110 | int32 CompressedSize = FCompression::CompressMemoryBound(CompressionName, UncompressedSize, CompressFlags); 111 | TArray CompressedData; 112 | 113 | CompressedData.SetNum(CompressedSize); 114 | 115 | if (FCompression::CompressMemory( 116 | CompressionName, 117 | CompressedData.GetData(), 118 | CompressedSize, 119 | InAsset->RawDataCompressed.GetData(), 120 | UncompressedSize, 121 | CompressFlags)) 122 | { 123 | CompressedData.SetNum(CompressedSize); 124 | CompressedData.Shrink(); 125 | 126 | InAsset->RawDataCompressed = MoveTemp(CompressedData); 127 | InAsset->RawDataCompressionMethod = CompressionName; 128 | } 129 | 130 | InAsset->RawDataUncompressedSize = UncompressedSize; 131 | InAsset->RawDataFormatID = GetFormatID(); 132 | } 133 | #endif 134 | -------------------------------------------------------------------------------- /Source/HoudiniNiagara/Private/HoudiniPointCacheLoaderBJSON.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #include "HoudiniPointCacheLoaderBJSON.h" 25 | 26 | #include "HoudiniPointCache.h" 27 | 28 | #include "CoreMinimal.h" 29 | #include "Misc/CoreMiscDefines.h" 30 | #include "Serialization/MemoryReader.h" 31 | #include "ShaderCompiler.h" 32 | 33 | 34 | const unsigned char FHoudiniPointCacheLoaderBJSON::MarkerTypeChar; 35 | const unsigned char FHoudiniPointCacheLoaderBJSON::MarkerTypeInt8; 36 | const unsigned char FHoudiniPointCacheLoaderBJSON::MarkerTypeUInt8; 37 | const unsigned char FHoudiniPointCacheLoaderBJSON::MarkerTypeBool; 38 | const unsigned char FHoudiniPointCacheLoaderBJSON::MarkerTypeInt16; 39 | const unsigned char FHoudiniPointCacheLoaderBJSON::MarkerTypeUInt16; 40 | const unsigned char FHoudiniPointCacheLoaderBJSON::MarkerTypeInt32; 41 | const unsigned char FHoudiniPointCacheLoaderBJSON::MarkerTypeUInt32; 42 | const unsigned char FHoudiniPointCacheLoaderBJSON::MarkerTypeInt64; 43 | const unsigned char FHoudiniPointCacheLoaderBJSON::MarkerTypeUInt64; 44 | const unsigned char FHoudiniPointCacheLoaderBJSON::MarkerTypeFloat32; 45 | const unsigned char FHoudiniPointCacheLoaderBJSON::MarkerTypeFloat64; 46 | const unsigned char FHoudiniPointCacheLoaderBJSON::MarkerTypeString; 47 | const unsigned char FHoudiniPointCacheLoaderBJSON::MarkerObjectStart; 48 | const unsigned char FHoudiniPointCacheLoaderBJSON::MarkerObjectEnd; 49 | const unsigned char FHoudiniPointCacheLoaderBJSON::MarkerArrayStart; 50 | const unsigned char FHoudiniPointCacheLoaderBJSON::MarkerArrayEnd; 51 | 52 | FHoudiniPointCacheLoaderBJSON::FHoudiniPointCacheLoaderBJSON(const FString& InFilePath) : 53 | FHoudiniPointCacheLoaderJSONBase(InFilePath) 54 | { 55 | 56 | } 57 | 58 | #if WITH_EDITOR 59 | bool FHoudiniPointCacheLoaderBJSON::LoadToAsset(UHoudiniPointCache *InAsset) 60 | { 61 | const FString& InFilePath = GetFilePath(); 62 | FScopedLoadingState ScopedLoadingState(*InFilePath); 63 | 64 | // Reset the reader and load the whole file into raw buffer 65 | if (!LoadRawPointCacheData(InAsset, InFilePath)) 66 | { 67 | UE_LOG(LogHoudiniNiagara, Warning, TEXT("Failed to read file '%s' error."), *InFilePath); 68 | return false; 69 | } 70 | 71 | // Pre-allocate and reset buffer 72 | Buffer.SetNumZeroed(1024); 73 | 74 | // Construct reader to read from the (currently) uncompressed data buffer in memory. 75 | Reader = MakeUnique(InAsset->RawDataCompressed); 76 | if (!Reader) 77 | { 78 | UE_LOG(LogHoudiniNiagara, Warning, TEXT("Failed to reader data from raw data buffer.")); 79 | return false; 80 | } 81 | 82 | // Read start of root object 83 | unsigned char Marker = '\0'; 84 | if (!ReadMarker(Marker) || Marker != MarkerObjectStart) 85 | { 86 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Expected root object start.")); 87 | return false; 88 | } 89 | 90 | FString ObjectKey; 91 | if (!ReadNonContainerValue(ObjectKey, false)) 92 | return false; 93 | if (ObjectKey != TEXT("header")) 94 | return false; 95 | 96 | // Found header 97 | FHoudiniPointCacheJSONHeader Header; 98 | if (!ReadHeader(Header)) 99 | { 100 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Could not read header.")); 101 | return false; 102 | } 103 | 104 | // Set up the Attribute and SpecialAttributeIndexes arrays in the asset, 105 | // expanding attributes with size > 1 106 | // If time was not an attribute in the file, we add it as an attribute 107 | // but we always set time to the frame's time, irrespective of the existence of a time 108 | // attribute in the file 109 | uint32 NumAttributesPerFileSample = Header.NumAttributeComponents; 110 | ParseAttributesAndInitAsset(InAsset, Header); 111 | 112 | // Get Age attribute index, we'll use this to ensure we sort point spawn time correctly 113 | int32 IDAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::POINTID); 114 | int32 AgeAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::AGE); 115 | 116 | // Due to the way that some of the DI functions work, 117 | // we expect that the point IDs start at zero, and increment as the points are spawned 118 | // Make sure this is the case by converting the point IDs as we read them 119 | int32 NextPointID = 0; 120 | TMap HoudiniIDToNiagaraIDMap; 121 | 122 | // Expect cache_data key, object start, frames key 123 | if (!ReadNonContainerValue(ObjectKey, false, MarkerTypeString) || ObjectKey != TEXT("cache_data")) 124 | return false; 125 | 126 | if (!ReadMarker(Marker) || Marker != MarkerObjectStart) 127 | return false; 128 | 129 | if (!ReadNonContainerValue(ObjectKey, false, MarkerTypeString) || ObjectKey != TEXT("frames")) 130 | return false; 131 | 132 | // Read the frames array, each entry contains a 'frame' object 133 | // Expect array start 134 | if (!ReadMarker(Marker) || Marker != MarkerArrayStart) 135 | return false; 136 | 137 | uint32 NumFramesRead = 0; 138 | uint32 FrameStartSampleIndex = 0; 139 | TArray> TempFrameData; 140 | while (!Reader->AtEnd() && !IsNext(MarkerArrayEnd)) 141 | { 142 | // Expect object start 143 | if (!ReadMarker(Marker) || Marker != MarkerObjectStart) 144 | return false; 145 | 146 | float FrameNumber = 0.0f; 147 | float Time = 0; 148 | uint32 NumPointsInFrame = 0; 149 | 150 | // Read 'number' (frame number) 151 | if (!ReadNonContainerValue(ObjectKey, false, MarkerTypeString) || ObjectKey != TEXT("number")) 152 | return false; 153 | if (!ReadNonContainerValue(FrameNumber, false, MarkerTypeUInt32)) 154 | return false; 155 | 156 | // Read 'time' 157 | if (!ReadNonContainerValue(ObjectKey, false, MarkerTypeString) || ObjectKey != TEXT("time")) 158 | return false; 159 | if (!ReadNonContainerValue(Time, false, MarkerTypeFloat32)) 160 | return false; 161 | 162 | // Read 'num_points' (number of points in frame) 163 | if (!ReadNonContainerValue(ObjectKey, false, MarkerTypeString) || ObjectKey != TEXT("num_points")) 164 | return false; 165 | if (!ReadNonContainerValue(NumPointsInFrame, false, MarkerTypeUInt32)) 166 | return false; 167 | 168 | // Expect 'frame_data' key 169 | if (!ReadNonContainerValue(ObjectKey, false, MarkerTypeString) || ObjectKey != TEXT("frame_data")) 170 | return false; 171 | 172 | // Expect array start marker 173 | if (!ReadMarker(Marker) || Marker != MarkerArrayStart) 174 | return false; 175 | 176 | // Ensure we have enough space in our FrameData array to read the samples for this frame 177 | TempFrameData.SetNum(NumPointsInFrame); 178 | float PreviousAge = 0.0f; 179 | bool bNeedToSort = false; 180 | for (uint32 SampleIndex = 0; SampleIndex < NumPointsInFrame; ++SampleIndex) 181 | { 182 | // Initialize attributes for this sample 183 | TempFrameData[SampleIndex].Init(0, NumAttributesPerFileSample); 184 | 185 | // Expect array start marker 186 | if (!ReadMarker(Marker) || Marker != MarkerArrayStart) 187 | return false; 188 | 189 | for (uint32 AttrIndex = 0; AttrIndex < NumAttributesPerFileSample; ++AttrIndex) 190 | { 191 | float& Value = TempFrameData[SampleIndex][AttrIndex]; 192 | // Read a value 193 | if (!ReadNonContainerValue(Value, false, Header.AttributeComponentDataTypes[AttrIndex])) 194 | return false; 195 | 196 | if (AgeAttributeIndex != INDEX_NONE && AttrIndex == AgeAttributeIndex) 197 | { 198 | if (SampleIndex == 0) 199 | { 200 | PreviousAge = Value; 201 | } 202 | else if (PreviousAge < Value) 203 | { 204 | bNeedToSort = true; 205 | } 206 | } 207 | } 208 | 209 | // Expect array end marker 210 | if (!ReadMarker(Marker) || Marker != MarkerArrayEnd) 211 | return false; 212 | } 213 | 214 | // Sort this frame's data by age 215 | if (bNeedToSort) 216 | { 217 | TempFrameData.Sort(FHoudiniPointCacheSortPredicate(INDEX_NONE, AgeAttributeIndex, IDAttributeIndex)); 218 | } 219 | 220 | ProcessFrame(InAsset, FrameNumber, TempFrameData, Time, FrameStartSampleIndex, NumPointsInFrame, NumAttributesPerFileSample, Header, HoudiniIDToNiagaraIDMap, NextPointID); 221 | 222 | // Expect array end marker 223 | if (!ReadMarker(Marker) || Marker != MarkerArrayEnd) 224 | return false; 225 | 226 | // Expect object end 227 | if (!ReadMarker(Marker) || Marker != MarkerObjectEnd) 228 | return false; 229 | 230 | FrameStartSampleIndex += NumPointsInFrame; 231 | NumFramesRead++; 232 | } 233 | 234 | if (NumFramesRead != Header.NumFrames) 235 | { 236 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Inconsistent num_frames in header vs body: %d vs %d"), Header.NumFrames, NumFramesRead); 237 | return false; 238 | } 239 | 240 | // Expect array end - frames 241 | if (!ReadMarker(Marker) || Marker != MarkerArrayEnd) 242 | return false; 243 | 244 | // Expect object end - cache_data 245 | if (!ReadMarker(Marker) || Marker != MarkerObjectEnd) 246 | return false; 247 | 248 | // Expect object end - root 249 | if (!ReadMarker(Marker) || Marker != MarkerObjectEnd) 250 | return false; 251 | 252 | // We have finished ingesting the data. 253 | // Finalize data loading by compressing raw data. 254 | CompressRawData(InAsset); 255 | 256 | return true; 257 | } 258 | #endif 259 | 260 | bool FHoudiniPointCacheLoaderBJSON::ReadMarker(unsigned char &OutMarker) 261 | { 262 | if (!CheckReader()) 263 | return false; 264 | 265 | // Read TYPE (1 byte) 266 | Reader->Serialize(Buffer.GetData(), 1); 267 | OutMarker = Buffer[0]; 268 | return true; 269 | } 270 | 271 | bool FHoudiniPointCacheLoaderBJSON::ReadHeader(FHoudiniPointCacheJSONHeader &OutHeader) 272 | { 273 | // Expect object start 274 | unsigned char Marker = '\0'; 275 | if (!ReadMarker(Marker) || Marker != MarkerObjectStart) 276 | return false; 277 | 278 | // Attempt to reach each key and value of the header, return false if any value is missing or 279 | // not of the expected type 280 | FString HeaderKey; 281 | if (!ReadNonContainerValue(HeaderKey, false, MarkerTypeString) || HeaderKey != TEXT("version")) 282 | return false; 283 | if (!ReadNonContainerValue(OutHeader.Version, false, MarkerTypeString)) 284 | return false; 285 | 286 | if (!ReadNonContainerValue(HeaderKey, false, MarkerTypeString) || HeaderKey != TEXT("num_samples")) 287 | return false; 288 | if (!ReadNonContainerValue(OutHeader.NumSamples, false, MarkerTypeUInt32)) 289 | return false; 290 | 291 | if (!ReadNonContainerValue(HeaderKey, false, MarkerTypeString) || HeaderKey != TEXT("num_frames")) 292 | return false; 293 | if (!ReadNonContainerValue(OutHeader.NumFrames, false, MarkerTypeUInt32)) 294 | return false; 295 | 296 | if (!ReadNonContainerValue(HeaderKey, false, MarkerTypeString) || HeaderKey != TEXT("num_points")) 297 | return false; 298 | if (!ReadNonContainerValue(OutHeader.NumPoints, false, MarkerTypeUInt32)) 299 | return false; 300 | 301 | if (!ReadNonContainerValue(HeaderKey, false, MarkerTypeString) || HeaderKey != TEXT("num_attrib")) 302 | return false; 303 | if (!ReadNonContainerValue(OutHeader.NumAttributes, false, MarkerTypeUInt16)) 304 | return false; 305 | 306 | // Preallocate Attribute arrays from NumAttributes 307 | OutHeader.Attributes.Empty(OutHeader.NumAttributes); 308 | OutHeader.AttributeSizes.Empty(OutHeader.NumAttributes); 309 | 310 | if (!ReadNonContainerValue(HeaderKey, false, MarkerTypeString) || HeaderKey != TEXT("attrib_name")) 311 | return false; 312 | if (!ReadArray(OutHeader.Attributes, MarkerTypeString)) 313 | return false; 314 | 315 | // Check that attrib_name was the expected size 316 | if (OutHeader.Attributes.Num() != OutHeader.NumAttributes) 317 | { 318 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Header inconsistent: attrib_name array size mismatch: %d vs %d"), OutHeader.Attributes.Num(), OutHeader.NumAttributes); 319 | return false; 320 | } 321 | 322 | if (!ReadNonContainerValue(HeaderKey, false, MarkerTypeString) || HeaderKey != TEXT("attrib_size")) 323 | return false; 324 | if (!ReadArray(OutHeader.AttributeSizes, MarkerTypeUInt8)) 325 | return false; 326 | 327 | // Check that attrib_size was the expected size 328 | if (OutHeader.AttributeSizes.Num() != OutHeader.NumAttributes) 329 | { 330 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Header inconsistent: attrib_size array size mismatch: %d vs %d"), OutHeader.AttributeSizes.Num(), OutHeader.NumAttributes); 331 | return false; 332 | } 333 | 334 | // Calculate the number of attribute components (sum of attribute size over all attributes) 335 | OutHeader.NumAttributeComponents = 0; 336 | for (uint32 AttrSize : OutHeader.AttributeSizes) 337 | { 338 | OutHeader.NumAttributeComponents += AttrSize; 339 | } 340 | OutHeader.AttributeComponentDataTypes.Empty(OutHeader.NumAttributeComponents); 341 | 342 | if (!ReadNonContainerValue(HeaderKey, false, MarkerTypeString) || HeaderKey != TEXT("attrib_data_type")) 343 | return false; 344 | if (!ReadArray(OutHeader.AttributeComponentDataTypes, MarkerTypeChar)) 345 | return false; 346 | 347 | // Check that attrib_data_type was the expected size 348 | if (OutHeader.AttributeComponentDataTypes.Num() != OutHeader.NumAttributeComponents) 349 | { 350 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Header inconsistent: attrib_data_type array size mismatch: %d vs %d"), OutHeader.AttributeComponentDataTypes.Num(), OutHeader.NumAttributeComponents); 351 | return false; 352 | } 353 | 354 | if (!ReadNonContainerValue(HeaderKey, false, MarkerTypeString) || HeaderKey != TEXT("data_type")) 355 | return false; 356 | if (!ReadNonContainerValue(OutHeader.DataType, false, MarkerTypeString)) 357 | return false; 358 | 359 | // Expect object end 360 | if (!ReadMarker(Marker) || Marker != MarkerObjectEnd) 361 | return false; 362 | return true; 363 | } 364 | 365 | bool FHoudiniPointCacheLoaderBJSON::ReadNonContainerValue(FString &OutValue, bool bInReadMarkerType, unsigned char InMarkerType) 366 | { 367 | if (!CheckReader()) 368 | return false; 369 | 370 | uint32 Size = 0; 371 | unsigned char MarkerType = '\0'; 372 | if (bInReadMarkerType) 373 | { 374 | // Read TYPE (1 byte) 375 | Reader->Serialize(Buffer.GetData(), 1); 376 | MarkerType = Buffer[0]; 377 | } 378 | else 379 | { 380 | MarkerType = InMarkerType; 381 | } 382 | 383 | if (MarkerType != MarkerTypeString && MarkerType != MarkerTypeChar) 384 | { 385 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Expected string or char, found type %c"), TCHAR(MarkerType)); 386 | return false; 387 | } 388 | 389 | if (MarkerType == MarkerTypeString) 390 | { 391 | // Get the number of characters in the string as Size 392 | if (!ReadNonContainerValue(Size, true)) 393 | { 394 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Could not determine string size.")); 395 | return false; 396 | } 397 | // Strings are encoded with utf-8, calculate number of bytes to read 398 | Size = Size * sizeof(UTF8CHAR); 399 | } 400 | else 401 | { 402 | // char 1 byte 403 | Size = 1; 404 | } 405 | Reader->Serialize(Buffer.GetData(), Size); 406 | if (Size >= static_cast(Buffer.Num())) 407 | { 408 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Buffer too small to read string with size %d"), Size) 409 | return false; 410 | } 411 | Buffer[Size] = '\0'; 412 | OutValue = UTF8_TO_TCHAR(Buffer.GetData()); 413 | 414 | return true; 415 | } 416 | 417 | bool FHoudiniPointCacheLoaderBJSON::Peek(int32 InSize) 418 | { 419 | if (!CheckReader()) 420 | return false; 421 | 422 | int64 Position = Reader->Tell(); 423 | if (Reader->TotalSize() - Position < InSize) 424 | { 425 | return false; 426 | } 427 | Reader->ByteOrderSerialize(Buffer.GetData(), InSize); 428 | Reader->Seek(Position); 429 | 430 | return true; 431 | } 432 | 433 | bool FHoudiniPointCacheLoaderBJSON::CheckReader(bool bInCheckAtEnd) const 434 | { 435 | if (!Reader || !Reader.IsValid()) 436 | { 437 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Binary JSON reader is not valid.")) 438 | return false; 439 | } 440 | // Check that we are not at the end of the archive 441 | if (bInCheckAtEnd && Reader->AtEnd()) 442 | { 443 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Binary JSON reader reach EOF early.")) 444 | return false; 445 | } 446 | 447 | return true; 448 | } 449 | -------------------------------------------------------------------------------- /Source/HoudiniNiagara/Private/HoudiniPointCacheLoaderCSV.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #include "HoudiniPointCacheLoaderCSV.h" 25 | 26 | #include "HoudiniPointCache.h" 27 | 28 | #include "CoreMinimal.h" 29 | #include "HAL/PlatformProcess.h" 30 | #include "Misc/CoreMiscDefines.h" 31 | #include "Misc/FileHelper.h" 32 | #include "Misc/Paths.h" 33 | #include "ShaderCompiler.h" 34 | 35 | 36 | FHoudiniPointCacheLoaderCSV::FHoudiniPointCacheLoaderCSV(const FString& InFilePath) 37 | : FHoudiniPointCacheLoader(InFilePath) 38 | { 39 | 40 | } 41 | 42 | #if WITH_EDITOR 43 | bool FHoudiniPointCacheLoaderCSV::LoadToAsset(UHoudiniPointCache *InAsset) 44 | { 45 | // Parse the file to a string array 46 | TArray StringArray; 47 | if (!FFileHelper::LoadFileToStringArray(StringArray, *GetFilePath())) 48 | return false; 49 | 50 | if (!UpdateFromStringArray(InAsset, StringArray)) 51 | { 52 | return false; 53 | } 54 | 55 | // Load uncompressed raw data into asset. 56 | if (!LoadRawPointCacheData(InAsset, *GetFilePath())) 57 | { 58 | return false; 59 | } 60 | // Finalize load by compressing raw data. 61 | CompressRawData(InAsset); 62 | 63 | return true; 64 | } 65 | #endif 66 | 67 | #if WITH_EDITOR 68 | bool FHoudiniPointCacheLoaderCSV::UpdateFromStringArray(UHoudiniPointCache *InAsset, TArray& InStringArray) 69 | { 70 | if (!InAsset) 71 | { 72 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Cannot load CSV file to HoudiniPointCache, InAsset is NULL.")); 73 | return false; 74 | } 75 | 76 | // Reset the CSV sizes 77 | InAsset->NumberOfAttributes = 0; 78 | InAsset->NumberOfSamples = 0; 79 | InAsset->NumberOfPoints = 0; 80 | 81 | // Get references to point cache data arrays 82 | TArray &FloatSampleData = InAsset->GetFloatSampleData(); 83 | TArray &SpawnTimes = InAsset->GetSpawnTimes(); 84 | TArray &LifeValues = InAsset->GetLifeValues(); 85 | TArray &PointTypes = InAsset->GetPointTypes(); 86 | TArray &SpecialAttributeIndexes = InAsset->GetSpecialAttributeIndexes(); 87 | TArray &PointValueIndexes = InAsset->GetPointValueIndexes(); 88 | 89 | // Reset the column indexes of the special attributes 90 | SpecialAttributeIndexes.Init( INDEX_NONE, EHoudiniAttributes::HOUDINI_ATTR_SIZE ); 91 | 92 | if ( InStringArray.Num() <= 0 ) 93 | { 94 | UE_LOG( LogHoudiniNiagara, Error, TEXT( "Could not load the CSV file, error: not enough rows in the file." ) ); 95 | return false; 96 | } 97 | 98 | // Remove empty rows from the CSV 99 | InStringArray.RemoveAll( [&]( const FString& InString ) { return InString.IsEmpty(); } ); 100 | 101 | // Number of rows in the CSV (ignoring the title row) 102 | InAsset->NumberOfSamples = InStringArray.Num() - 1; 103 | if ( InAsset->NumberOfSamples < 1 ) 104 | { 105 | UE_LOG( LogHoudiniNiagara, Error, TEXT( "Could not load the CSV file, error: not enough rows in the file." ) ); 106 | return false; 107 | } 108 | 109 | // See if we need to use a custom title row 110 | // The custom title row will be ignored if it is empty or only composed of spaces 111 | FString TitleRow = InAsset->SourceCSVTitleRow; 112 | TitleRow.ReplaceInline(TEXT(" "), TEXT("")); 113 | if ( TitleRow.IsEmpty() ) 114 | InAsset->SetUseCustomCSVTitleRow(false); 115 | 116 | if ( !InAsset->GetUseCustomCSVTitleRow() ) 117 | InAsset->SourceCSVTitleRow = InStringArray[0]; 118 | 119 | // Parses the CSV file's title row to update the column indexes of special values we're interested in 120 | // Also look for packed vectors in the first row and update the indexes accordingly 121 | bool HasPackedVectors = false; 122 | if ( !ParseCSVTitleRow( InAsset, InAsset->SourceCSVTitleRow, InStringArray[1], HasPackedVectors ) ) 123 | return false; 124 | 125 | // Remove the title row now that it's been processed 126 | InStringArray.RemoveAt( 0 ); 127 | 128 | // Parses each string of the csv file to a string array 129 | TArray< TArray< FString > > ParsedStringArrays; 130 | ParsedStringArrays.SetNum( InAsset->NumberOfSamples ); 131 | for ( int32 rowIdx = 0; rowIdx < InAsset->NumberOfSamples; rowIdx++ ) 132 | { 133 | // Get the current row 134 | FString CurrentRow = InStringArray[ rowIdx ]; 135 | if ( HasPackedVectors ) 136 | { 137 | // Clean up the packing characters: ()" from the row so it can be parsed properly 138 | CurrentRow.ReplaceInline( TEXT("("), TEXT("") ); 139 | CurrentRow.ReplaceInline( TEXT(")"), TEXT("") ); 140 | CurrentRow.ReplaceInline( TEXT("\""), TEXT("") ); 141 | } 142 | 143 | // Parse the current row to an array 144 | TArray CurrentParsedRow; 145 | CurrentRow.ParseIntoArray( CurrentParsedRow, TEXT(",") ); 146 | 147 | // Check that the parsed row and number of columns match 148 | if ( InAsset->NumberOfAttributes != CurrentParsedRow.Num() ) 149 | UE_LOG( LogHoudiniNiagara, Warning, 150 | TEXT("Error while parsing the CSV File. Row %d has %d values instead of the expected %d!"), 151 | rowIdx + 1, CurrentParsedRow.Num(), InAsset->NumberOfAttributes ); 152 | 153 | // Store the parsed row 154 | ParsedStringArrays[ rowIdx ] = CurrentParsedRow; 155 | } 156 | 157 | // If we have time and/or age values, we have to make sure the csv rows are sorted by time and/or age 158 | int32 TimeAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::TIME); 159 | int32 AgeAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::AGE); 160 | int32 IDAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::POINTID); 161 | if ( TimeAttributeIndex != INDEX_NONE ) 162 | { 163 | // First check if we need to sort the array 164 | bool NeedToSort = false; 165 | float PreviousTimeValue = 0.0f; 166 | float PreviousAgeValue = 0.0f; 167 | for ( int32 rowIdx = 0; rowIdx < ParsedStringArrays.Num(); rowIdx++ ) 168 | { 169 | if ( !ParsedStringArrays[ rowIdx ].IsValidIndex( TimeAttributeIndex ) && 170 | !ParsedStringArrays[ rowIdx ].IsValidIndex( AgeAttributeIndex ) ) 171 | continue; 172 | 173 | // Get the current time value 174 | float CurrentTimeValue = PreviousTimeValue; 175 | if (ParsedStringArrays[rowIdx].IsValidIndex(TimeAttributeIndex)) 176 | { 177 | CurrentTimeValue = FCString::Atof(*(ParsedStringArrays[rowIdx][TimeAttributeIndex])); 178 | } 179 | 180 | // Get the current age value 181 | float CurrentAgeValue = PreviousAgeValue; 182 | if (ParsedStringArrays[rowIdx].IsValidIndex(AgeAttributeIndex)) 183 | { 184 | CurrentAgeValue = FCString::Atof(*(ParsedStringArrays[rowIdx][AgeAttributeIndex])); 185 | } 186 | 187 | if ( rowIdx == 0 ) 188 | { 189 | PreviousTimeValue = CurrentTimeValue; 190 | PreviousAgeValue = CurrentAgeValue; 191 | continue; 192 | } 193 | 194 | // Time values arent sorted properly 195 | if ( PreviousTimeValue > CurrentTimeValue || (PreviousTimeValue == CurrentTimeValue && PreviousAgeValue < CurrentAgeValue ) ) 196 | { 197 | NeedToSort = true; 198 | break; 199 | } 200 | } 201 | 202 | if ( NeedToSort ) 203 | { 204 | // We need to sort the CSV rows by their time values 205 | ParsedStringArrays.Sort( FHoudiniPointCacheSortPredicate( TimeAttributeIndex, AgeAttributeIndex, IDAttributeIndex ) ); 206 | } 207 | } 208 | 209 | // Initialize our different buffers 210 | FloatSampleData.Empty(); 211 | FloatSampleData.SetNumZeroed( InAsset->NumberOfSamples * InAsset->NumberOfAttributes ); 212 | 213 | /* 214 | StringCSVData.Empty(); 215 | StringCSVData.SetNumZeroed( NumberOfSamples * NumberOfAttributes ); 216 | */ 217 | // Due to the way that some of the DI functions work, 218 | // we expect that the point IDs start at zero, and increment as the points are spawned 219 | // Make sure this is the case by converting the point IDs as we read them 220 | int32 NextPointID = 0; 221 | TMap HoudiniIDToNiagaraIDMap; 222 | 223 | 224 | // We also keep track of the row indexes for each time values 225 | //float lastTimeValue = 0.0; 226 | //TimeValuesIndexes.Empty(); 227 | 228 | // And the row indexes for each point 229 | PointValueIndexes.Empty(); 230 | 231 | // Extract all the values from the table to the float & string buffers 232 | TArray CurrentParsedRow; 233 | for ( int rowIdx = 0; rowIdx < ParsedStringArrays.Num(); rowIdx++ ) 234 | { 235 | CurrentParsedRow = ParsedStringArrays[ rowIdx ]; 236 | 237 | // Store the CSV Data in the buffers 238 | // The data is stored transposed in those buffers 239 | int32 CurrentID = -1; 240 | for ( int colIdx = 0; colIdx < InAsset->NumberOfAttributes; colIdx++ ) 241 | { 242 | // Get the string value for the current column 243 | FString CurrentVal = TEXT("0"); 244 | if ( CurrentParsedRow.IsValidIndex( colIdx ) ) 245 | { 246 | CurrentVal = CurrentParsedRow[ colIdx ]; 247 | } 248 | else 249 | { 250 | UE_LOG( LogHoudiniNiagara, Warning, 251 | TEXT("Error while parsing the CSV File. Row %d has an invalid value for column %d!"), 252 | rowIdx + 1, colIdx + 1 ); 253 | } 254 | 255 | // Convert the string value to a float 256 | float FloatValue = FCString::Atof( *CurrentVal ); 257 | 258 | // Handle point IDs here 259 | if ( colIdx == IDAttributeIndex ) 260 | { 261 | // If the point ID doesn't exist in the Houdini/Niagara mapping, create a new a entry. 262 | // Otherwise, replace the point ID with the Niagara ID. 263 | 264 | int32 PointID = FMath::FloorToInt(FloatValue); 265 | // The point ID may need to be replaced 266 | if ( !HoudiniIDToNiagaraIDMap.Contains( PointID ) ) 267 | { 268 | // We found a new point, so we add it to the ID map 269 | HoudiniIDToNiagaraIDMap.Add( PointID, NextPointID++ ); 270 | 271 | // Add a new array for that point's indexes 272 | PointValueIndexes.Add( FPointIndexes() ); 273 | } 274 | 275 | // Get the Niagara ID from the Houdini ID 276 | CurrentID = HoudiniIDToNiagaraIDMap[ PointID ]; 277 | FloatValue = (float)CurrentID; 278 | 279 | // Add the current row to this point's row index list 280 | PointValueIndexes[ CurrentID ].SampleIndexes.Add( rowIdx ); 281 | } 282 | 283 | // Store the Value in the buffer 284 | FloatSampleData[ rowIdx + ( colIdx * InAsset->NumberOfSamples ) ] = FloatValue; 285 | } 286 | 287 | // If we dont have Point ID informations, we still want to fill the PointValueIndexes array 288 | if ( !InAsset->IsValidAttributeAttributeIndex( EHoudiniAttributes::POINTID ) ) 289 | { 290 | // Each row is considered its own point 291 | PointValueIndexes.Add(FPointIndexes()); 292 | PointValueIndexes[ rowIdx ].SampleIndexes.Add( rowIdx ); 293 | } 294 | } 295 | 296 | InAsset->NumberOfPoints = HoudiniIDToNiagaraIDMap.Num(); 297 | if ( InAsset->NumberOfPoints <= 0 ) 298 | InAsset->NumberOfPoints = InAsset->NumberOfSamples; 299 | 300 | // Look for point specific attributes to build some helper arrays 301 | int32 LifeAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::LIFE); 302 | //int32 AgeAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::AGE); 303 | int32 TypeAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::TYPE); 304 | 305 | SpawnTimes.Empty(); 306 | SpawnTimes.Init( -FLT_MAX, InAsset->NumberOfPoints ); 307 | 308 | LifeValues.Empty(); 309 | LifeValues.Init( -FLT_MAX, InAsset->NumberOfPoints ); 310 | 311 | PointTypes.Empty(); 312 | PointTypes.Init( -1, InAsset->NumberOfPoints ); 313 | 314 | const float SpawnBias = 0.001f; 315 | for ( int rowIdx = 0; rowIdx < InAsset->NumberOfSamples; rowIdx++ ) 316 | { 317 | // Get the point ID 318 | int32 CurrentID = rowIdx; 319 | if ( IDAttributeIndex != INDEX_NONE ) 320 | { 321 | // Remap the external point ID to the expected Niagara particle ID. 322 | CurrentID = (int32)FloatSampleData[ rowIdx + ( IDAttributeIndex * InAsset->NumberOfSamples ) ]; 323 | CurrentID = HoudiniIDToNiagaraIDMap[CurrentID]; 324 | } 325 | 326 | // Get the time value for the current row 327 | float CurrentTime = 0.0f; 328 | if ( TimeAttributeIndex != INDEX_NONE ) 329 | CurrentTime = FloatSampleData[ rowIdx + ( TimeAttributeIndex * InAsset->NumberOfSamples ) ]; 330 | 331 | if ( FMath::IsNearlyEqual(SpawnTimes[ CurrentID ], -FLT_MAX) ) 332 | { 333 | // We have detected a new particle. 334 | 335 | // Calculate spawn and life from attributes. 336 | // Spawn time is when the point is first seen 337 | if (AgeAttributeIndex != INDEX_NONE) 338 | { 339 | // If we have an age attribute we can more accurately calculate the particle's spawn time. 340 | SpawnTimes[ CurrentID ] = CurrentTime - FloatSampleData [rowIdx + (AgeAttributeIndex * InAsset->NumberOfSamples) ]; 341 | } 342 | else 343 | { 344 | // We don't have an age attribute. Simply use the current time as the spawn time. 345 | // Note that we bias the value slightly to that the particle is already spawned at the CurrentTime. 346 | SpawnTimes[ CurrentID ] = CurrentTime; 347 | } 348 | 349 | if ( LifeAttributeIndex != INDEX_NONE ) 350 | { 351 | LifeValues[ CurrentID ] = FloatSampleData [rowIdx + (LifeAttributeIndex * InAsset->NumberOfSamples) ]; 352 | } 353 | } 354 | 355 | // If we don't have a life attribute, keep setting the life attribute for the particle 356 | // so that when the particle is no longer present we have recorded its last observed time 357 | if ( LifeAttributeIndex == INDEX_NONE && LifeValues[ CurrentID ] < CurrentTime) 358 | { 359 | // Life is the difference between spawn time and time of death 360 | // Note that the particle should still be alive at this timestep. Since we don't 361 | // have access to a frame rate here we will workaround this for now by adding 362 | // a small value such that the particle dies *after* this timestep. 363 | LifeValues[ CurrentID ] = CurrentTime - SpawnTimes[ CurrentID ]; 364 | } 365 | 366 | // Keep track of the point type at spawn 367 | if ( PointTypes[ CurrentID ] < 0 ) 368 | { 369 | float CurrentType = 0.0f; 370 | if (TypeAttributeIndex != INDEX_NONE) 371 | InAsset->GetFloatValue( rowIdx, TypeAttributeIndex, CurrentType ); 372 | 373 | PointTypes[ CurrentID ] = (int32)CurrentType; 374 | } 375 | 376 | } 377 | return true; 378 | } 379 | #endif 380 | 381 | #if WITH_EDITOR 382 | bool FHoudiniPointCacheLoaderCSV::ParseCSVTitleRow( UHoudiniPointCache *InAsset, const FString& TitleRow, const FString& FirstValueRow, bool& HasPackedVectors ) 383 | { 384 | // Get the relevant point cache asset data arrays 385 | TArray &AttributeArray = InAsset->AttributeArray; 386 | TArray &SpecialAttributeIndexes = InAsset->GetSpecialAttributeIndexes(); 387 | 388 | // Get the number of values per row via the title row 389 | AttributeArray.Empty(); 390 | TitleRow.ParseIntoArray( AttributeArray, TEXT(",") ); 391 | InAsset->NumberOfAttributes = AttributeArray.Num(); 392 | if ( InAsset->NumberOfAttributes < 1 ) 393 | { 394 | UE_LOG( LogHoudiniNiagara, Error, TEXT( "Could not load the CSV file, error: not enough columns." ) ); 395 | return false; 396 | } 397 | 398 | // Look for the position, normal and time attributes indexes 399 | for ( int32 n = 0; n < AttributeArray.Num(); n++ ) 400 | { 401 | // Remove spaces from the title row 402 | AttributeArray[ n ].ReplaceInline( TEXT(" "), TEXT("") ); 403 | 404 | FString CurrentTitle = AttributeArray[ n ]; 405 | if ( CurrentTitle.Equals( TEXT("P.x"), ESearchCase::IgnoreCase ) || CurrentTitle.Equals(TEXT("P"), ESearchCase::IgnoreCase ) ) 406 | { 407 | if ( !InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::POSITION)) 408 | SpecialAttributeIndexes[EHoudiniAttributes::POSITION] = n; 409 | } 410 | else if ( CurrentTitle.Equals( TEXT("N.x"), ESearchCase::IgnoreCase ) || CurrentTitle.Equals(TEXT("N"), ESearchCase::IgnoreCase ) ) 411 | { 412 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::NORMAL)) 413 | SpecialAttributeIndexes[EHoudiniAttributes::NORMAL] = n; 414 | } 415 | else if ( CurrentTitle.Contains( TEXT("time"), ESearchCase::IgnoreCase ) ) 416 | { 417 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::TIME)) 418 | SpecialAttributeIndexes[EHoudiniAttributes::TIME] = n; 419 | } 420 | else if ( CurrentTitle.Equals( TEXT("id"), ESearchCase::IgnoreCase ) ) 421 | { 422 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::POINTID)) 423 | SpecialAttributeIndexes[EHoudiniAttributes::POINTID] = n; 424 | } 425 | else if ( CurrentTitle.Equals( TEXT("life"), ESearchCase::IgnoreCase ) ) 426 | { 427 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::LIFE)) 428 | SpecialAttributeIndexes[EHoudiniAttributes::LIFE] = n; 429 | } 430 | else if ( CurrentTitle.Equals( TEXT("age"), ESearchCase::IgnoreCase ) ) 431 | { 432 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::AGE)) 433 | SpecialAttributeIndexes[EHoudiniAttributes::AGE] = n; 434 | } 435 | else if ( CurrentTitle.Equals( TEXT("Cd.r"), ESearchCase::IgnoreCase ) || CurrentTitle.Equals(TEXT("Cd"), ESearchCase::IgnoreCase ) ) 436 | { 437 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::COLOR)) 438 | SpecialAttributeIndexes[EHoudiniAttributes::COLOR] = n; 439 | } 440 | else if ( CurrentTitle.Equals( TEXT("alpha"), ESearchCase::IgnoreCase ) ) 441 | { 442 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::ALPHA)) 443 | SpecialAttributeIndexes[EHoudiniAttributes::ALPHA] = n; 444 | } 445 | else if ( CurrentTitle.Equals( TEXT("v.x"), ESearchCase::IgnoreCase ) || CurrentTitle.Equals(TEXT("v"), ESearchCase::IgnoreCase ) ) 446 | { 447 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::VELOCITY)) 448 | SpecialAttributeIndexes[EHoudiniAttributes::VELOCITY] = n; 449 | } 450 | else if ( CurrentTitle.Equals(TEXT("type"), ESearchCase::IgnoreCase ) ) 451 | { 452 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::TYPE)) 453 | SpecialAttributeIndexes[EHoudiniAttributes::TYPE] = n; 454 | } 455 | else if ( CurrentTitle.Equals(TEXT("impulse"), ESearchCase::IgnoreCase ) ) 456 | { 457 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::IMPULSE)) 458 | SpecialAttributeIndexes[EHoudiniAttributes::IMPULSE] = n; 459 | } 460 | } 461 | 462 | // Read the first row of the CSV file, and look for packed vectors value (X,Y,Z) 463 | // We'll have to expand them in the title row to match the parsed data 464 | HasPackedVectors = false; 465 | int32 FoundPackedVectorCharIndex = 0; 466 | while ( FoundPackedVectorCharIndex != INDEX_NONE ) 467 | { 468 | // Try to find ( in the row 469 | FoundPackedVectorCharIndex = FirstValueRow.Find( TEXT("("), ESearchCase::IgnoreCase, ESearchDir::FromStart, FoundPackedVectorCharIndex ); 470 | if ( FoundPackedVectorCharIndex == INDEX_NONE ) 471 | break; 472 | 473 | // We want to know which column this char belong to 474 | int32 FoundPackedVectorAttributeIndex = INDEX_NONE; 475 | { 476 | // Chop the first row up to the found character 477 | FString FirstRowLeft = FirstValueRow.Left( FoundPackedVectorCharIndex ); 478 | 479 | // ReplaceInLine returns the number of occurences of ",", that's what we want! 480 | FoundPackedVectorAttributeIndex = FirstRowLeft.ReplaceInline(TEXT(","), TEXT("")); 481 | } 482 | 483 | if ( !AttributeArray.IsValidIndex( FoundPackedVectorAttributeIndex ) ) 484 | { 485 | UE_LOG( LogHoudiniNiagara, Warning, 486 | TEXT( "Error while parsing the CSV File. Couldn't unpack vector found at character %d in the first row!" ), 487 | FoundPackedVectorCharIndex + 1 ); 488 | continue; 489 | } 490 | 491 | // We found a packed vector, get its size 492 | int32 FoundVectorSize = 0; 493 | { 494 | // Extract the vector string 495 | int32 FoundPackedVectorEndCharIndex = FirstValueRow.Find( TEXT(")"), ESearchCase::IgnoreCase, ESearchDir::FromStart, FoundPackedVectorCharIndex ); 496 | FString VectorString = FirstValueRow.Mid( FoundPackedVectorCharIndex + 1, FoundPackedVectorEndCharIndex - FoundPackedVectorCharIndex - 1 ); 497 | 498 | // Use ReplaceInLine to count the number of , to get the vector's size! 499 | FoundVectorSize = VectorString.ReplaceInline( TEXT(","), TEXT("") ) + 1; 500 | } 501 | 502 | if ( FoundVectorSize < 2 ) 503 | continue; 504 | 505 | // Increment the number of columns 506 | InAsset->NumberOfAttributes += ( FoundVectorSize - 1 ); 507 | 508 | // Extract the special attributes column indexes we found 509 | int32 PositionAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::POSITION); 510 | int32 NormalAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::NORMAL); 511 | int32 ColorAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::COLOR); 512 | int32 AlphaAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::ALPHA); 513 | int32 VelocityAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::VELOCITY); 514 | 515 | // Expand TitleRowArray 516 | if ( ( FoundPackedVectorAttributeIndex == PositionAttributeIndex ) && ( FoundVectorSize == 3 ) ) 517 | { 518 | // Expand P to Px,Py,Pz 519 | AttributeArray[ PositionAttributeIndex ] = TEXT("P.x"); 520 | AttributeArray.Insert( TEXT( "P.y" ), PositionAttributeIndex + 1 ); 521 | AttributeArray.Insert( TEXT( "P.z" ), PositionAttributeIndex + 2 ); 522 | } 523 | else if ( ( FoundPackedVectorAttributeIndex == NormalAttributeIndex ) && ( FoundVectorSize == 3 ) ) 524 | { 525 | // Expand N to Nx,Ny,Nz 526 | AttributeArray[ NormalAttributeIndex ] = TEXT("N.x"); 527 | AttributeArray.Insert( TEXT("N.y"), NormalAttributeIndex + 1 ); 528 | AttributeArray.Insert( TEXT("N.z"), NormalAttributeIndex + 2 ); 529 | } 530 | else if ( ( FoundPackedVectorAttributeIndex == VelocityAttributeIndex ) && ( FoundVectorSize == 3 ) ) 531 | { 532 | // Expand V to Vx,Vy,Vz 533 | AttributeArray[ VelocityAttributeIndex ] = TEXT("v.x"); 534 | AttributeArray.Insert( TEXT("v.y"), VelocityAttributeIndex + 1 ); 535 | AttributeArray.Insert( TEXT("v.z"), VelocityAttributeIndex + 2 ); 536 | } 537 | else if ( ( FoundPackedVectorAttributeIndex == ColorAttributeIndex ) && ( ( FoundVectorSize == 3 ) || ( FoundVectorSize == 4 ) ) ) 538 | { 539 | // Expand Cd to R, G, B 540 | AttributeArray[ ColorAttributeIndex ] = TEXT("Cd.r"); 541 | AttributeArray.Insert( TEXT("Cd.g"), ColorAttributeIndex + 1 ); 542 | AttributeArray.Insert( TEXT("Cd.b"), ColorAttributeIndex + 2 ); 543 | 544 | if ( FoundVectorSize == 4 ) 545 | { 546 | // Insert A if we had RGBA 547 | AttributeArray.Insert( TEXT("Cd.a"), ColorAttributeIndex + 3 ); 548 | if ( AlphaAttributeIndex == INDEX_NONE ) 549 | AlphaAttributeIndex = ColorAttributeIndex + 3; 550 | } 551 | } 552 | else 553 | { 554 | // Expand the vector's title from V to V, V1, V2, V3 ... 555 | FString FoundPackedVectortTitle = AttributeArray[ FoundPackedVectorAttributeIndex ]; 556 | for ( int32 n = 0; n < FoundVectorSize; n++ ) 557 | { 558 | FString CurrentTitle = FString::Printf( TEXT( "%s.%d" ), *FoundPackedVectortTitle, n ); 559 | if (n == 0) 560 | AttributeArray[FoundPackedVectorAttributeIndex] = CurrentTitle; 561 | else 562 | AttributeArray.Insert( CurrentTitle, FoundPackedVectorAttributeIndex + n ); 563 | } 564 | } 565 | 566 | // We have inserted new column titles because of the packed vector, 567 | // so we need to offset all the special attributes column indexes after the inserted values 568 | for ( int32 AttrIdx = EHoudiniAttributes::HOUDINI_ATTR_BEGIN; AttrIdx < EHoudiniAttributes::HOUDINI_ATTR_SIZE; AttrIdx++ ) 569 | { 570 | if ( SpecialAttributeIndexes[AttrIdx] == INDEX_NONE ) 571 | continue; 572 | 573 | if ( SpecialAttributeIndexes[AttrIdx] > FoundPackedVectorAttributeIndex ) 574 | SpecialAttributeIndexes[AttrIdx] += FoundVectorSize - 1; 575 | } 576 | 577 | HasPackedVectors = true; 578 | FoundPackedVectorCharIndex++; 579 | } 580 | 581 | // For sanity, Check that the number of columns matches the title row and the first row 582 | { 583 | // Check the title row 584 | if ( InAsset->NumberOfAttributes != AttributeArray.Num() ) 585 | UE_LOG( LogHoudiniNiagara, Error, 586 | TEXT( "Error while parsing the CSV File. Found %d columns but the Title string has %d values! Some values will have an offset!" ), 587 | InAsset->NumberOfAttributes, AttributeArray.Num() ); 588 | 589 | // Use ReplaceInLine to count the number of columns in the first row and make sure it's correct 590 | FString FirstValueRowCopy = FirstValueRow; 591 | int32 FirstRowColumnCount = FirstValueRowCopy.ReplaceInline( TEXT(","), TEXT("") ) + 1; 592 | if ( InAsset->NumberOfAttributes != FirstRowColumnCount ) 593 | UE_LOG( LogHoudiniNiagara, Error, 594 | TEXT("Error while parsing the CSV File. Found %d columns but found %d values in the first row! Some values will have an offset!" ), 595 | InAsset->NumberOfAttributes, FirstRowColumnCount ); 596 | } 597 | /* 598 | // Update the TitleRow uproperty 599 | TitleRow.Empty(); 600 | for (int32 n = 0; n < TitleRowArray.Num(); n++) 601 | { 602 | TitleRow += TitleRowArray[n]; 603 | if ( n < TitleRowArray.Num() - 1 ) 604 | TitleRow += TEXT(","); 605 | } 606 | */ 607 | 608 | return true; 609 | } 610 | #endif -------------------------------------------------------------------------------- /Source/HoudiniNiagara/Private/HoudiniPointCacheLoaderJSON.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #include "HoudiniPointCacheLoaderJSON.h" 25 | 26 | #include "HoudiniPointCache.h" 27 | 28 | #include "CoreMinimal.h" 29 | #include "HAL/PlatformProcess.h" 30 | #include "Misc/CoreMiscDefines.h" 31 | #include "Misc/FileHelper.h" 32 | #include "Misc/Paths.h" 33 | #include "Serialization/JsonReader.h" 34 | #include "Serialization/JsonSerializer.h" 35 | #include "ShaderCompiler.h" 36 | 37 | 38 | FHoudiniPointCacheLoaderJSON::FHoudiniPointCacheLoaderJSON(const FString& InFilePath) : 39 | FHoudiniPointCacheLoaderJSONBase(InFilePath) 40 | { 41 | 42 | } 43 | 44 | #if WITH_EDITOR 45 | bool FHoudiniPointCacheLoaderJSON::LoadToAsset(UHoudiniPointCache *InAsset) 46 | { 47 | const FString& InFilePath = GetFilePath(); 48 | FScopedLoadingState ScopedLoadingState(*InFilePath); 49 | 50 | FString JsonString; 51 | if (!FFileHelper::LoadFileToString(JsonString, *InFilePath)) 52 | { 53 | UE_LOG(LogHoudiniNiagara, Warning, TEXT("Failed to read file '%s'."), *InFilePath); 54 | return false; 55 | } 56 | 57 | const TSharedRef>& Reader = TJsonReaderFactory<>::Create(JsonString); 58 | 59 | // Attempt to deserialize the JSON file to a FJsonObject 60 | TSharedPtr PointCacheObject; 61 | if (!FJsonSerializer::Deserialize(Reader, PointCacheObject) || !PointCacheObject.IsValid()) 62 | { 63 | UE_LOG(LogHoudiniNiagara, Warning, TEXT("Failed to deserialize file '%s'."), *InFilePath); 64 | return false; 65 | } 66 | 67 | // Populate the header struct from 'header' JSON object 68 | FHoudiniPointCacheJSONHeader Header; 69 | if (!ReadHeader(*PointCacheObject, Header)) 70 | { 71 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Could not read header.")); 72 | return false; 73 | } 74 | 75 | // Set up the Attribute and SpecialAttributeIndexes arrays in the asset, 76 | // expanding attributes with size > 1 77 | // If time was not an attribute in the file, we add it as an attribute 78 | // but we always set time to the frame's time, irrespective of the existence of a time 79 | // attribute in the file 80 | uint32 NumAttributesPerFileSample = Header.NumAttributeComponents; 81 | ParseAttributesAndInitAsset(InAsset, Header); 82 | 83 | // Get Age attribute index, we'll use this to ensure we sort point spawn time correctly 84 | int32 IDAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::POINTID); 85 | int32 AgeAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::AGE); 86 | 87 | // Due to the way that some of the DI functions work, 88 | // we expect that the point IDs start at zero, and increment as the points are spawned 89 | // Make sure this is the case by converting the point IDs as we read them 90 | int32 NextPointID = 0; 91 | TMap HoudiniIDToNiagaraIDMap; 92 | 93 | // Expect cache_data key, object start, frames key 94 | const TSharedPtr &CacheDataObject = PointCacheObject->GetObjectField(TEXT("cache_data")); 95 | if (!CacheDataObject.IsValid()) 96 | return false; 97 | 98 | const TArray> &Frames = CacheDataObject->GetArrayField(TEXT("frames")); 99 | 100 | if (Frames.Num() != Header.NumFrames) 101 | { 102 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Inconsistent num_frames in header vs body: %d vs %d"), Header.NumFrames, Frames.Num()); 103 | return false; 104 | } 105 | 106 | uint32 FrameStartSampleIndex = 0; 107 | TArray> TempFrameData; 108 | for (const TSharedPtr &FrameEntryAsValue : Frames) 109 | { 110 | const TSharedPtr &FrameEntryObject = FrameEntryAsValue->AsObject(); 111 | if (!FrameEntryObject.IsValid()) 112 | return false; 113 | 114 | float FrameNumber = FrameEntryObject->GetNumberField(TEXT("number")); 115 | float Time = FrameEntryObject->GetNumberField(TEXT("time")); 116 | uint32 NumPointsInFrame = FrameEntryObject->GetNumberField(TEXT("num_points")); 117 | 118 | const TArray> FrameData = FrameEntryObject->GetArrayField(TEXT("frame_data")); 119 | 120 | // Ensure we have enough space in our FrameData array to read the samples for this frame 121 | TempFrameData.SetNum(NumPointsInFrame); 122 | float PreviousAge = 0.0f; 123 | bool bNeedToSort = false; 124 | uint32 SampleIndex = 0; 125 | for (const TSharedPtr &SampleAsValue : FrameData) 126 | { 127 | if (SampleIndex >= NumPointsInFrame) 128 | { 129 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Found more samples in frame %d as specified %d"), (int)FrameNumber, (int)NumPointsInFrame) 130 | return false; 131 | } 132 | 133 | // Initialize attributes for this sample 134 | TempFrameData[SampleIndex].Init(0, NumAttributesPerFileSample); 135 | uint32 AttrIndex = 0; 136 | for (const TSharedPtr &AttrEntryAsValue : SampleAsValue->AsArray()) 137 | { 138 | if (AttrIndex >= NumAttributesPerFileSample) 139 | { 140 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Found more attributes in frame %d, sample %d as specified %d"), (int)FrameNumber, (int)SampleIndex, (int)NumAttributesPerFileSample) 141 | return false; 142 | } 143 | 144 | float& Value = TempFrameData[SampleIndex][AttrIndex]; 145 | Value = AttrEntryAsValue->AsNumber(); 146 | 147 | if (AgeAttributeIndex != INDEX_NONE && AttrIndex == AgeAttributeIndex) 148 | { 149 | if (SampleIndex == 0) 150 | { 151 | PreviousAge = Value; 152 | } 153 | else if (PreviousAge < Value) 154 | { 155 | bNeedToSort = true; 156 | } 157 | } 158 | AttrIndex++; 159 | } 160 | 161 | SampleIndex++; 162 | } 163 | 164 | // Sort this frame's data by age 165 | if (bNeedToSort) 166 | { 167 | TempFrameData.Sort(FHoudiniPointCacheSortPredicate(INDEX_NONE, AgeAttributeIndex, IDAttributeIndex)); 168 | } 169 | 170 | ProcessFrame(InAsset, FrameNumber, TempFrameData, Time, FrameStartSampleIndex, NumPointsInFrame, NumAttributesPerFileSample, Header, HoudiniIDToNiagaraIDMap, NextPointID); 171 | 172 | FrameStartSampleIndex += NumPointsInFrame; 173 | } 174 | 175 | // Load uncompressed raw data into asset. 176 | // TODO: Rebuild JSON string from this buffer to avoid loading data twice. 177 | if (!LoadRawPointCacheData(InAsset, *GetFilePath())) 178 | { 179 | return false; 180 | } 181 | 182 | // Finalize load by compressing raw data. 183 | CompressRawData(InAsset); 184 | 185 | return true; 186 | } 187 | #endif 188 | 189 | bool FHoudiniPointCacheLoaderJSON::ReadHeader(const FJsonObject &InPointCacheObject, FHoudiniPointCacheJSONHeader &OutHeader) const 190 | { 191 | TSharedPtr HeaderObject = InPointCacheObject.GetObjectField(TEXT("header")); 192 | if (!HeaderObject.IsValid()) 193 | return false; 194 | 195 | OutHeader.Version = HeaderObject->GetStringField(TEXT("version")); 196 | OutHeader.NumSamples = HeaderObject->GetNumberField(TEXT("num_samples")); 197 | OutHeader.NumFrames = HeaderObject->GetNumberField(TEXT("num_frames")); 198 | OutHeader.NumPoints = HeaderObject->GetNumberField(TEXT("num_points")); 199 | OutHeader.NumAttributes = HeaderObject->GetNumberField(TEXT("num_attrib")); 200 | 201 | // Preallocate Attribute arrays from NumAttributes 202 | OutHeader.Attributes.Empty(OutHeader.NumAttributes); 203 | OutHeader.AttributeSizes.Empty(OutHeader.NumAttributes); 204 | 205 | for (const TSharedPtr &ArrayValue : HeaderObject->GetArrayField(TEXT("attrib_name"))) 206 | { 207 | OutHeader.Attributes.Add(ArrayValue->AsString()); 208 | } 209 | 210 | // Check that attrib_name was the expected size 211 | if (OutHeader.Attributes.Num() != OutHeader.NumAttributes) 212 | { 213 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Header inconsistent: attrib_name array size mismatch: %d vs %d"), OutHeader.Attributes.Num(), OutHeader.NumAttributes); 214 | return false; 215 | } 216 | 217 | // Read attribute sizes and calculate the number of attribute components (sum of attribute size over all attributes) 218 | OutHeader.NumAttributeComponents = 0; 219 | for (const TSharedPtr &ArrayValue : HeaderObject->GetArrayField(TEXT("attrib_size"))) 220 | { 221 | uint8 AttrSize = ArrayValue->AsNumber(); 222 | OutHeader.AttributeSizes.Add(AttrSize); 223 | OutHeader.NumAttributeComponents += AttrSize; 224 | } 225 | 226 | // Check that attrib_size was the expected size 227 | if (OutHeader.AttributeSizes.Num() != OutHeader.NumAttributes) 228 | { 229 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Header inconsistent: attrib_size array size mismatch: %d vs %d"), OutHeader.AttributeSizes.Num(), OutHeader.NumAttributes); 230 | return false; 231 | } 232 | 233 | OutHeader.AttributeComponentDataTypes.Empty(OutHeader.NumAttributeComponents); 234 | 235 | for (const TSharedPtr &ArrayValue : HeaderObject->GetArrayField(TEXT("attrib_data_type"))) 236 | { 237 | const FString& DataType = ArrayValue->AsString(); 238 | if (DataType.Len() > 0) 239 | OutHeader.AttributeComponentDataTypes.Add(DataType[0]); 240 | else 241 | OutHeader.AttributeComponentDataTypes.Add('\0'); 242 | } 243 | 244 | // Check that attrib_data_type was the expected size 245 | if (OutHeader.AttributeComponentDataTypes.Num() != OutHeader.NumAttributeComponents) 246 | { 247 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Header inconsistent: attrib_data_type array size mismatch: %d vs %d"), OutHeader.AttributeComponentDataTypes.Num(), OutHeader.NumAttributeComponents); 248 | return false; 249 | } 250 | 251 | OutHeader.DataType = HeaderObject->GetStringField(TEXT("data_type")); 252 | 253 | return true; 254 | } 255 | -------------------------------------------------------------------------------- /Source/HoudiniNiagara/Private/HoudiniPointCacheLoaderJSONBase.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #include "HoudiniPointCacheLoaderJSONBase.h" 25 | 26 | #include "HoudiniPointCache.h" 27 | 28 | #include "CoreMinimal.h" 29 | #include "HAL/PlatformProcess.h" 30 | #include "Misc/CoreMiscDefines.h" 31 | #include "Misc/Paths.h" 32 | #include "ShaderCompiler.h" 33 | 34 | 35 | FHoudiniPointCacheLoaderJSONBase::FHoudiniPointCacheLoaderJSONBase(const FString& InFilePath) : 36 | FHoudiniPointCacheLoader(InFilePath) 37 | { 38 | 39 | } 40 | 41 | 42 | FHoudiniPointCacheLoaderJSONBase::~FHoudiniPointCacheLoaderJSONBase() 43 | { 44 | 45 | } 46 | 47 | 48 | bool FHoudiniPointCacheLoaderJSONBase::ParseAttributesAndInitAsset(UHoudiniPointCache *InAsset, const FHoudiniPointCacheJSONHeader &InHeader) 49 | { 50 | // Get the relevant point cache asset data arrays 51 | TArray &AttributeArray = InAsset->AttributeArray; 52 | TArray &SpecialAttributeIndexes = InAsset->GetSpecialAttributeIndexes(); 53 | 54 | // Reset the indexes of the special attributes 55 | SpecialAttributeIndexes.Init(INDEX_NONE, EHoudiniAttributes::HOUDINI_ATTR_SIZE); 56 | 57 | InAsset->NumberOfAttributes = InHeader.NumAttributeComponents; 58 | InAsset->AttributeArray.Empty(InAsset->NumberOfAttributes); 59 | 60 | // Look for the position, normal and time attributes indexes 61 | uint32 AttrIndex = 0; 62 | for (uint32 HeaderAttrIndex = 0; HeaderAttrIndex < InHeader.NumAttributes; ++HeaderAttrIndex) 63 | { 64 | uint32 AttrSize = InHeader.AttributeSizes[HeaderAttrIndex]; 65 | // Remove spaces from the attribute name 66 | FString AttrName = InHeader.Attributes[HeaderAttrIndex]; 67 | AttrName.ReplaceInline( TEXT(" "), TEXT("") ); 68 | 69 | bool bIsVector = false; 70 | bool bIsColor = false; 71 | if (AttrName.Equals(TEXT("P"), ESearchCase::IgnoreCase)) 72 | { 73 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::POSITION)) 74 | SpecialAttributeIndexes[EHoudiniAttributes::POSITION] = AttrIndex; 75 | bIsVector = true; 76 | } 77 | else if ( AttrName.Equals(TEXT("N"), ESearchCase::IgnoreCase ) ) 78 | { 79 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::NORMAL)) 80 | SpecialAttributeIndexes[EHoudiniAttributes::NORMAL] = AttrIndex; 81 | bIsVector = true; 82 | } 83 | else if ( AttrName.Contains( TEXT("time"), ESearchCase::IgnoreCase ) ) 84 | { 85 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::TIME)) 86 | SpecialAttributeIndexes[EHoudiniAttributes::TIME] = AttrIndex; 87 | } 88 | else if ( AttrName.Equals( TEXT("id"), ESearchCase::IgnoreCase ) ) 89 | { 90 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::POINTID)) 91 | SpecialAttributeIndexes[EHoudiniAttributes::POINTID] = AttrIndex; 92 | } 93 | else if ( AttrName.Equals( TEXT("life"), ESearchCase::IgnoreCase ) ) 94 | { 95 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::LIFE)) 96 | SpecialAttributeIndexes[EHoudiniAttributes::LIFE] = AttrIndex; 97 | } 98 | else if ( AttrName.Equals( TEXT("age"), ESearchCase::IgnoreCase ) ) 99 | { 100 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::AGE)) 101 | SpecialAttributeIndexes[EHoudiniAttributes::AGE] = AttrIndex; 102 | } 103 | else if ( AttrName.Equals(TEXT("Cd"), ESearchCase::IgnoreCase ) ) 104 | { 105 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::COLOR)) 106 | SpecialAttributeIndexes[EHoudiniAttributes::COLOR] = AttrIndex; 107 | bIsColor = true; 108 | } 109 | else if ( AttrName.Equals( TEXT("alpha"), ESearchCase::IgnoreCase ) ) 110 | { 111 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::ALPHA)) 112 | SpecialAttributeIndexes[EHoudiniAttributes::ALPHA] = AttrIndex; 113 | } 114 | else if ( AttrName.Equals(TEXT("v"), ESearchCase::IgnoreCase ) ) 115 | { 116 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::VELOCITY)) 117 | SpecialAttributeIndexes[EHoudiniAttributes::VELOCITY] = AttrIndex; 118 | bIsVector = true; 119 | } 120 | else if ( AttrName.Equals(TEXT("type"), ESearchCase::IgnoreCase ) ) 121 | { 122 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::TYPE)) 123 | SpecialAttributeIndexes[EHoudiniAttributes::TYPE] = AttrIndex; 124 | } 125 | else if ( AttrName.Equals(TEXT("impulse"), ESearchCase::IgnoreCase ) ) 126 | { 127 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::IMPULSE)) 128 | SpecialAttributeIndexes[EHoudiniAttributes::IMPULSE] = AttrIndex; 129 | } 130 | 131 | if (AttrSize > 1) 132 | { 133 | const TCHAR VectorSuffixes[4] = {'x', 'y', 'z', 'w'}; 134 | const TCHAR ColorSuffixes[4] = {'r', 'g', 'b', 'a'}; 135 | for (uint32 ComponentIndex = 0; ComponentIndex < AttrSize; ++ComponentIndex) 136 | { 137 | if (bIsVector) 138 | { 139 | AttributeArray.Add(FString::Printf(TEXT("%s.%c"), *AttrName, VectorSuffixes[ComponentIndex])); 140 | } 141 | else if (bIsColor) 142 | { 143 | AttributeArray.Add(FString::Printf(TEXT("%s.%c"), *AttrName, ColorSuffixes[ComponentIndex])); 144 | } 145 | else 146 | { 147 | AttributeArray.Add(FString::Printf(TEXT("%s.%d"), *AttrName, ComponentIndex + 1)); 148 | } 149 | } 150 | } 151 | else 152 | { 153 | AttributeArray.Add(AttrName); 154 | } 155 | 156 | AttrIndex += AttrSize; 157 | } 158 | 159 | // If there was no time attribute in the file, add it 160 | if (!InAsset->IsValidAttributeAttributeIndex(EHoudiniAttributes::TIME)) 161 | { 162 | InAsset->AttributeArray.Add(TEXT("time")); 163 | InAsset->NumberOfAttributes = InAsset->AttributeArray.Num(); 164 | SpecialAttributeIndexes[EHoudiniAttributes::TIME] = InAsset->NumberOfAttributes - 1; 165 | } 166 | 167 | // Get references to the various data arrays of the asset and initialize them 168 | TArray &FloatSampleData = InAsset->GetFloatSampleData(); 169 | TArray &SpawnTimes = InAsset->GetSpawnTimes(); 170 | TArray &LifeValues = InAsset->GetLifeValues(); 171 | TArray &PointTypes = InAsset->GetPointTypes(); 172 | TArray &PointValueIndexes = InAsset->GetPointValueIndexes(); 173 | 174 | // Pre-allocate arrays in the point cache asset based off of the header 175 | InAsset->NumberOfPoints = InHeader.NumPoints; 176 | InAsset->NumberOfSamples = InHeader.NumSamples; 177 | InAsset->NumberOfFrames = InHeader.NumFrames; 178 | InAsset->FirstFrame = FLT_MAX; 179 | InAsset->LastFrame = -FLT_MAX; 180 | InAsset->MinSampleTime = FLT_MAX; 181 | InAsset->MaxSampleTime = -FLT_MAX; 182 | 183 | FloatSampleData.Empty(InAsset->NumberOfSamples * InAsset->NumberOfAttributes); 184 | FloatSampleData.Init(-FLT_MAX, InAsset->NumberOfSamples * InAsset->NumberOfAttributes); 185 | SpawnTimes.Empty(InAsset->NumberOfPoints); 186 | SpawnTimes.Init(-FLT_MAX, InAsset->NumberOfPoints); 187 | LifeValues.Empty(InAsset->NumberOfPoints); 188 | LifeValues.Init(-FLT_MAX, InAsset->NumberOfPoints); 189 | PointTypes.Empty(InAsset->NumberOfPoints); 190 | PointTypes.Init(-1, InAsset->NumberOfPoints); 191 | PointValueIndexes.Empty(InAsset->NumberOfPoints); 192 | PointValueIndexes.Init(FPointIndexes(), InAsset->NumberOfPoints); 193 | 194 | return true; 195 | } 196 | 197 | 198 | bool FHoudiniPointCacheLoaderJSONBase::ProcessFrame(UHoudiniPointCache *InAsset, float InFrameNumber, const TArray> &InFrameData, float InFrameTime, uint32 InFrameStartSampleIndex, uint32 InNumPointsInFrame, uint32 InNumAttributesPerPoint, const FHoudiniPointCacheJSONHeader &InHeader, TMap& InHoudiniIDToNiagaraIDMap, int32 &OutNextPointID) const 199 | { 200 | // Get references to the various data arrays of the asset 201 | TArray &FloatSampleData = InAsset->GetFloatSampleData(); 202 | TArray &SpawnTimes = InAsset->GetSpawnTimes(); 203 | TArray &LifeValues = InAsset->GetLifeValues(); 204 | TArray &PointTypes = InAsset->GetPointTypes(); 205 | TArray &PointValueIndexes = InAsset->GetPointValueIndexes(); 206 | 207 | // Set Min/Max Time seen in asset 208 | if (InFrameTime < InAsset->MinSampleTime) 209 | { 210 | InAsset->MinSampleTime = InFrameTime; 211 | } 212 | if (InFrameTime > InAsset->MaxSampleTime) 213 | { 214 | InAsset->MaxSampleTime = InFrameTime; 215 | } 216 | if (InFrameNumber < InAsset->FirstFrame) 217 | { 218 | InAsset->FirstFrame = InFrameNumber; 219 | } 220 | if (InFrameNumber > InAsset->LastFrame) 221 | { 222 | InAsset->LastFrame = InFrameNumber; 223 | } 224 | 225 | // Get Age attribute index, we'll use this to ensure we sort point spawn time correctly 226 | int32 IDAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::POINTID); 227 | int32 AgeAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::AGE); 228 | int32 LifeAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::LIFE); 229 | int32 TypeAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::TYPE); 230 | int32 TimeAttributeIndex = InAsset->GetAttributeAttributeIndex(EHoudiniAttributes::TIME); 231 | 232 | if (InFrameData.Num() != InNumPointsInFrame) 233 | { 234 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Inconsistent InFrameData size vs specified number of points in frame.")); 235 | return false; 236 | } 237 | 238 | // Copy the frame data into the FloatSampleData array, determine unique points IDs 239 | // Also calculate SpawnTimes, LifeValues (if the life attribute exists) 240 | int32 CurrentID = -1; 241 | for (uint32 FrameSampleIndex = 0; FrameSampleIndex < InNumPointsInFrame; ++FrameSampleIndex) 242 | { 243 | uint32 SampleIndex = InFrameStartSampleIndex + FrameSampleIndex; 244 | // Check Attribute sample array size 245 | if (InFrameData[FrameSampleIndex].Num() != InNumAttributesPerPoint) 246 | { 247 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Inconsistent InFrameData at SampleIndex %d: point attribute array size vs specified number of attributes per point."), SampleIndex); 248 | return false; 249 | } 250 | for (uint32 AttrIndex = 0; AttrIndex < InNumAttributesPerPoint; ++AttrIndex) 251 | { 252 | // Get the float value for the attribute 253 | float FloatValue = InFrameData[FrameSampleIndex][AttrIndex]; 254 | 255 | // Handle point IDs here 256 | if (AttrIndex == IDAttributeIndex) 257 | { 258 | // If the point ID doesn't exist in the Houdini/Niagara mapping, create a new a entry. 259 | // Otherwise, replace the point ID with the Niagara ID. 260 | int32 PointID = FMath::FloorToInt(FloatValue); 261 | 262 | // The point ID may need to be replaced 263 | if (!InHoudiniIDToNiagaraIDMap.Contains(PointID)) 264 | { 265 | // We found a new point, so we add it to the ID map 266 | InHoudiniIDToNiagaraIDMap.Add(PointID, OutNextPointID++); 267 | 268 | // // Add a new array for that point's indexes 269 | // PointValueIndexes.Add(FPointIndexes()); 270 | } 271 | 272 | // Get the Niagara ID from the Houdini ID 273 | CurrentID = InHoudiniIDToNiagaraIDMap[PointID]; 274 | 275 | // Check that CurrentID is still in the expected range 276 | if (CurrentID < 0 || CurrentID >= InAsset->NumberOfPoints) 277 | { 278 | // ID is out of range 279 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Generated out of range point ID %d, expected max number of points %d"), CurrentID, InAsset->NumberOfPoints); 280 | return false; 281 | } 282 | 283 | FloatValue = static_cast(CurrentID); 284 | 285 | // Add the current sample index to this point's sample index list 286 | PointValueIndexes[CurrentID].SampleIndexes.Add(SampleIndex); 287 | } 288 | 289 | // Store the Value in the buffer 290 | FloatSampleData[SampleIndex + (AttrIndex * InAsset->NumberOfSamples)] = FloatValue; 291 | } 292 | 293 | // Always use the frame time, in other words, ignore a 'time' attribute 294 | if (TimeAttributeIndex != INDEX_NONE) 295 | { 296 | FloatSampleData[SampleIndex + (TimeAttributeIndex * InAsset->NumberOfSamples)] = InFrameTime; 297 | } 298 | 299 | // If we dont have Point ID information, we still want to fill the PointValueIndexes array 300 | if (IDAttributeIndex == INDEX_NONE) 301 | { 302 | // Each sample is considered its own point 303 | // PointValueIndexes.Add(FPointIndexes()); 304 | PointValueIndexes[SampleIndex].SampleIndexes.Add(SampleIndex); 305 | } 306 | 307 | // Calculate SpawnTimes, LifeValues and PointTypes 308 | // Get the reconstructed point id 309 | if (IDAttributeIndex != INDEX_NONE) 310 | { 311 | // Get remapped the external point ID to the expected Niagara particle ID. 312 | CurrentID = static_cast(FloatSampleData[SampleIndex + (IDAttributeIndex * InAsset->NumberOfSamples)]); 313 | // CurrentID = InHoudiniIDToNiagaraIDMap[CurrentID]; 314 | } 315 | else 316 | { 317 | CurrentID = SampleIndex; 318 | } 319 | 320 | // The time value comes from the frame entry 321 | const float CurrentTime = InFrameTime; 322 | 323 | if (FMath::IsNearlyEqual(SpawnTimes[CurrentID], -FLT_MAX)) 324 | { 325 | // We have detected a new particle. 326 | 327 | // Calculate spawn and life from attributes. 328 | // Spawn time is when the point is first seen 329 | if (AgeAttributeIndex != INDEX_NONE) 330 | { 331 | // If we have an age attribute we can more accurately calculate the particle's spawn time. 332 | SpawnTimes[CurrentID] = CurrentTime - FloatSampleData[SampleIndex + (AgeAttributeIndex * InAsset->NumberOfSamples)]; 333 | } 334 | else 335 | { 336 | // We don't have an age attribute. Simply use the current time as the spawn time. 337 | // Note that we bias the value slightly to that the particle is already spawned at the CurrentTime. 338 | SpawnTimes[CurrentID] = CurrentTime; 339 | } 340 | 341 | if (LifeAttributeIndex != INDEX_NONE) 342 | { 343 | LifeValues[CurrentID] = FloatSampleData[SampleIndex + (LifeAttributeIndex * InAsset->NumberOfSamples)]; 344 | } 345 | } 346 | 347 | // If we don't have a life attribute, keep setting the life attribute for the particle 348 | // so that when the particle is no longer present we have recorded its last observed time 349 | if (LifeAttributeIndex == INDEX_NONE && LifeValues[CurrentID] < CurrentTime) 350 | { 351 | // Life is the difference between spawn time and time of death 352 | // Note that the particle should still be alive at this timestep. Since we don't 353 | // have access to a frame rate here we will workaround this for now by adding 354 | // a small value such that the particle dies *after* this timestep. 355 | LifeValues[CurrentID] = CurrentTime - SpawnTimes[CurrentID]; 356 | } 357 | 358 | // Keep track of the point type at spawn 359 | if (PointTypes[CurrentID] < 0) 360 | { 361 | float CurrentType = 0.0f; 362 | if (TypeAttributeIndex != INDEX_NONE) 363 | CurrentType = FloatSampleData[SampleIndex + (TypeAttributeIndex * InAsset->NumberOfSamples)]; 364 | 365 | PointTypes[CurrentID] = static_cast(CurrentType); 366 | } 367 | } 368 | 369 | return true; 370 | } -------------------------------------------------------------------------------- /Source/HoudiniNiagara/Public/HoudiniNiagara.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #pragma once 25 | 26 | #include "CoreMinimal.h" 27 | #include "Modules/ModuleInterface.h" 28 | 29 | class FHoudiniNiagaraModule : public IModuleInterface 30 | { 31 | public: 32 | 33 | /** IModuleInterface implementation */ 34 | virtual void StartupModule() override; 35 | virtual void ShutdownModule() override; 36 | }; -------------------------------------------------------------------------------- /Source/HoudiniNiagara/Public/HoudiniPointCache.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | #pragma once 24 | 25 | #include "CoreMinimal.h" 26 | #include "HAL/PlatformProcess.h" 27 | #include "Misc/CoreMiscDefines.h" 28 | #include "Misc/FileHelper.h" 29 | #include "Misc/Paths.h" 30 | #include "RenderResource.h" 31 | #include "RHIUtilities.h" 32 | #include "Runtime/Launch/Resources/Version.h" 33 | #include "ShaderCompiler.h" 34 | #include "UObject/Object.h" 35 | #include "UObject/ObjectMacros.h" 36 | #include "UObject/UObjectGlobals.h" 37 | 38 | #if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 4 39 | #include "UObject/AssetRegistryTagsContext.h" 40 | #endif 41 | 42 | #include "HoudiniPointCache.generated.h" 43 | 44 | DECLARE_LOG_CATEGORY_EXTERN( LogHoudiniNiagara, All, All ); 45 | 46 | UENUM() 47 | enum EHoudiniAttributes 48 | { 49 | HOUDINI_ATTR_BEGIN, 50 | 51 | POSITION = HOUDINI_ATTR_BEGIN, 52 | NORMAL, 53 | TIME, 54 | POINTID, 55 | LIFE, 56 | COLOR, 57 | ALPHA, 58 | VELOCITY, 59 | TYPE, 60 | IMPULSE, 61 | AGE, 62 | 63 | HOUDINI_ATTR_SIZE, 64 | HOUDINI_ATTR_END = HOUDINI_ATTR_SIZE - 1 65 | 66 | }; 67 | 68 | USTRUCT() 69 | struct FPointIndexes 70 | { 71 | GENERATED_BODY() 72 | 73 | // Simple structure for storing all the sample indexes used for a given point 74 | UPROPERTY() 75 | TArray SampleIndexes; 76 | }; 77 | 78 | UENUM() 79 | enum class EHoudiniPointCacheFileType : uint8 80 | { 81 | Invalid, 82 | CSV, 83 | JSON, 84 | BJSON, 85 | }; 86 | 87 | struct FNiagaraDIHoudini_StaticDataPassToRT 88 | { 89 | ~FNiagaraDIHoudini_StaticDataPassToRT() 90 | { 91 | //UE_LOG(LogHoudiniNiagara, Warning, TEXT("Deleted!")); 92 | } 93 | 94 | TArray FloatData; 95 | TArray SpawnTimes; 96 | TArray LifeValues; 97 | TArray PointTypes; 98 | TArray SpecialAttributeIndexes; 99 | TArray PointValueIndexes; 100 | TArray Attributes; 101 | 102 | int32 NumSamples; 103 | int32 NumAttributes; 104 | int32 NumPoints; 105 | int32 MaxNumIndexesPerPoint; 106 | }; 107 | 108 | /** 109 | * point cache resource. 110 | */ 111 | class FHoudiniPointCacheResource : public FRenderResource 112 | { 113 | public: 114 | 115 | // GPU Buffers 116 | FRWBuffer FloatValuesGPUBuffer; 117 | FRWBuffer SpecialAttributeIndexesGPUBuffer; 118 | FRWBuffer SpawnTimesGPUBuffer; 119 | FRWBuffer LifeValuesGPUBuffer; 120 | FRWBuffer PointTypesGPUBuffer; 121 | FRWBuffer PointValueIndexesGPUBuffer; 122 | 123 | int32 MaxNumberOfIndexesPerPoint; 124 | int32 NumSamples; 125 | int32 NumAttributes; 126 | int32 NumPoints; 127 | 128 | TArray Attributes; 129 | 130 | TUniquePtr CachedData; 131 | 132 | /** Default constructor. */ 133 | FHoudiniPointCacheResource() : CachedData(nullptr){} 134 | 135 | #if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 3 136 | virtual void InitRHI(FRHICommandListBase& RHICmdList) override; 137 | #else 138 | virtual void InitRHI() override; 139 | #endif 140 | virtual void ReleaseRHI() override; 141 | 142 | virtual FString GetFriendlyName() const override { return TEXT("FHoudiniPointCacheResource"); } 143 | 144 | void AcceptStaticDataUpdate(TUniquePtr& Update); 145 | 146 | virtual ~FHoudiniPointCacheResource() {} 147 | }; 148 | 149 | 150 | UCLASS(BlueprintType) 151 | class HOUDININIAGARA_API UHoudiniPointCache : public UObject 152 | { 153 | friend class UNiagaraDataInterfaceHoudini; 154 | 155 | GENERATED_UCLASS_BODY() 156 | 157 | public: 158 | 159 | //----------------------------------------------------------------------------------------- 160 | // MEMBER FUNCTIONS 161 | //----------------------------------------------------------------------------------------- 162 | 163 | #if WITH_EDITOR 164 | bool UpdateFromFile( const FString& TheFileName ); 165 | #endif 166 | 167 | void SetFileName( const FString& TheFilename ); 168 | 169 | // Returns the number of points found in the point cache 170 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 171 | int32 GetNumberOfPoints() const; 172 | 173 | // Returns the number of samples found in the point cache 174 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 175 | int32 GetNumberOfSamples() const; 176 | 177 | // Returns the number of attributes found in the point cache 178 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 179 | int32 GetNumberOfAttributes() const; 180 | 181 | // Return the attribute index for a specific attribute 182 | //UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 183 | int32 GetAttributeAttributeIndex(const EHoudiniAttributes& Attr) const; 184 | 185 | // Returns if the specific attribute has a valid attribute index 186 | bool IsValidAttributeAttributeIndex(const EHoudiniAttributes& Attr) const; 187 | 188 | // Returns the attribute index for a given string. 189 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 190 | bool GetAttributeIndexFromString(const FString& Attribute, int32& AttributeIndex) const; 191 | 192 | // Returns the attribute index for a given string. This is a static version of the function that 193 | // takes the attribute name array as an argument as well. 194 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 195 | static bool GetAttributeIndexInArrayFromString(const FString& InAttribute, const TArray& InAttributeArray, int32& OutAttributeIndex); 196 | 197 | // Returns the float value at a given point in the point cache 198 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 199 | bool GetFloatValue( const int32& sampleIndex, const int32& attrIndex, float& value ) const; 200 | // Returns the float value at a given point in the point cache 201 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 202 | bool GetFloatValueForString( const int32& sampleIndex, const FString& Attribute, float& value ) const; 203 | /* 204 | // Returns the string value at a given point in the point cache 205 | bool GetCSVStringValue( const int32& sampleIndex, const int32& attrIndex, FString& value ); 206 | // Returns the string value at a given point in the point cache 207 | bool GetCSVStringValue( const int32& sampleIndex, const FString& Attribute, FString& value ); 208 | */ 209 | // Returns a Vector3 for a given point in the point cache 210 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 211 | bool GetVectorValue( const int32& sampleIndex, const int32& attrIndex, FVector& value, const bool& DoSwap = true, const bool& DoScale = true ) const; 212 | // Returns a Vector3 for a given point in the point cache by column name 213 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 214 | bool GetVectorValueForString(const int32& sampleIndex, const FString& Attribute, FVector& value, const bool& DoSwap = true, const bool& DoScale = true) const; 215 | // Returns a Vector4 for a given point in the point cache 216 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 217 | bool GetVector4Value( const int32& sampleIndex, const int32& attrIndex, FVector4& value ) const; 218 | // Returns a Vector4 for a given point in the point cache by column name 219 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 220 | bool GetVector4ValueForString(const int32& sampleIndex, const FString& Attribute, FVector4& value ) const; 221 | // Returns a Quat for a given point in the point cache 222 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 223 | bool GetQuatValue( const int32& sampleIndex, const int32& attrIndex, FQuat& value, const bool& DoHoudiniToUnrealConversion = true ) const; 224 | // Returns a Quat for a given point in the point cache by column name 225 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 226 | bool GetQuatValueForString(const int32& sampleIndex, const FString& Attribute, FQuat& value, const bool& DoHoudiniToUnrealConversion = true ) const; 227 | 228 | // Returns a time value for a given point in the point cache 229 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 230 | bool GetTimeValue( const int32& sampleIndex, float& value ) const; 231 | // Returns a Position Vector3 for a given point in the point cache (converted to unreal's coordinate system) 232 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 233 | bool GetPositionValue( const int32& sampleIndex, FVector& value ) const; 234 | // Returns a Normal Vector3 for a given point in the point cache (converted to unreal's coordinate system) 235 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 236 | bool GetNormalValue( const int32& sampleIndex, FVector& value ) const; 237 | // Returns a Color for a given point in the point cache 238 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 239 | bool GetColorValue( const int32& sampleIndex, FLinearColor& value ) const; 240 | // Returns a Velocity Vector3 for a given point in the point cache 241 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 242 | bool GetVelocityValue(const int32& sampleIndex, FVector& value ) const; 243 | // Returns an Impulse float value for a given point in the point cache 244 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 245 | bool GetImpulseValue(const int32& sampleIndex, float& value) const; 246 | 247 | // Get the last sample index for a given time value (the sample with a time smaller or equal to desiredTime) 248 | // If the point cache doesn't have time informations, returns false and set LastsampleIndex to the last sample in the file 249 | // If desiredTime is smaller than the time value in the first sample, LastsampleIndex will be set to -1 250 | // If desiredTime is higher than the last time value in the last sample of the point cache, LastIndex will be set to the last sample's index 251 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 252 | bool GetLastSampleIndexAtTime( const float& desiredTime, int32& lastSampleIndex ) const; 253 | 254 | // Get the last pointID of the points to be spawned at time t 255 | // Invalid Index are used to indicate edge cases: 256 | // -1 will be returned if there is no points to spawn ( t is smaller than the first point time ) 257 | // NumberOfSamples will be returned if all points in the CSV have been spawned ( t is higher than the last point time ) 258 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 259 | bool GetLastPointIDToSpawnAtTime( const float& time, int32& lastID ) const; 260 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 261 | bool GetPointIDsToSpawnAtTime( 262 | const float& desiredTime, 263 | int32& MinID, int32& MaxID, int32& Count, 264 | int32& LastSpawnedPointID, float& LastSpawnTime, float& LastSpawnTimeRequest) const; 265 | 266 | bool GetPointIDsToSpawnAtTime_DEPR( 267 | const float& desiredTime, 268 | int32& MinID, int32& MaxID, int32& Count, 269 | int32& LastSpawnedPointID, float& LastSpawnTime ) const; 270 | 271 | // Returns the previous and next sample indexes for reading the values of a specified point at a given time 272 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 273 | bool GetSampleIndexesForPointAtTime(const int32& PointID, const float& desiredTime, int32& PrevSampleIndex, int32& NextSampleIndex, float& PrevWeight) const; 274 | // Returns the value for a point at a given time value (linearly interpolated) 275 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 276 | bool GetPointValueAtTime(const int32& PointID, const int32& AttributeIndex, const float& desiredTime, float& Value) const; 277 | // Returns the value for a point at a given time value (linearly interpolated), via the attribute name 278 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 279 | bool GetPointValueAtTimeForString(const int32& PointID, const FString& Attribute, const float& desiredTime, float& Value) const; 280 | 281 | // Returns the Vector Value for a given point at a given time value (linearly interpolated) 282 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 283 | bool GetPointVectorValueAtTime(int32 PointID, int32 AttributeIndex, float desiredTime, FVector& Vector, bool DoSwap, bool DoScale) const; 284 | 285 | // Returns the Vector Value for a given point at a given time value (linearly interpolated), via the attribute name 286 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 287 | bool GetPointVectorValueAtTimeForString(int32 PointID, const FString& Attribute, float desiredTime, FVector& Vector, bool DoSwap, bool DoScale) const; 288 | 289 | // Returns the Vector4 Value for a given point at a given time value (linearly interpolated) 290 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 291 | bool GetPointVector4ValueAtTime(int32 PointID, int32 AttributeIndex, float desiredTime, FVector4& Vector) const; 292 | 293 | // Returns the Vector4 Value for a given point at a given time value (linearly interpolated), via the attribute name 294 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 295 | bool GetPointVector4ValueAtTimeForString(int32 PointID, const FString& Attribute, float desiredTime, FVector4& Vector) const; 296 | 297 | // Returns the Quat Value for a given point at a given time value (linearly interpolated) 298 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 299 | bool GetPointQuatValueAtTime(int32 PointID, int32 AttributeIndex, float desiredTime, FQuat& Quat, bool DoHoudiniToUnrealConversion = true ) const; 300 | 301 | // Returns the Quat Value for a given point at a given time value (linearly interpolated), via the attribute name 302 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 303 | bool GetPointQuatValueAtTimeForString(int32 PointID, const FString& Attribute, float desiredTime, FQuat& Quat, bool DoHoudiniToUnrealConversion = true ) const; 304 | 305 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 306 | bool GetPointFloatValueAtTime(int32 PointID, int32 AttributeIndex, float desiredTime, float& Value) const; 307 | 308 | // Return the integer value of the point at the keyframe before the desired time. No value interpolation will take place. 309 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 310 | bool GetPointInt32ValueAtTime(int32 PointID, int32 AttributeIndex, float desiredTime, int32& Value) const; 311 | 312 | // Returns the Position Value for a given point at a given time value (linearly interpolated) 313 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 314 | bool GetPointPositionAtTime(const int32& PointID, const float& desiredTime, FVector& Vector) const; 315 | // Return a given point's life value at spawn 316 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 317 | bool GetPointLife(const int32& PointID, float& Value) const; 318 | // Return a point's life for a given time value 319 | // Note this function currently behaves exactly the same as GetPointLife 320 | // since the Lifetime value is currently treated as a constant. This could 321 | // change in the future. 322 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 323 | bool GetPointLifeAtTime(const int32& PointID, const float& DesiredTime, float& Value) const; 324 | // Return a point's type at spawn 325 | UFUNCTION(BlueprintCallable, Category = "Houdini Attributes Data") 326 | bool GetPointType(const int32& PointID, int32& Value) const; 327 | 328 | 329 | // Returns the maximum number of indexes per point, used for flattening the buffer for HLSL conversion 330 | int32 GetMaxNumberOfPointValueIndexes() const; 331 | 332 | //----------------------------------------------------------------------------------------- 333 | // MEMBER VARIABLES 334 | //----------------------------------------------------------------------------------------- 335 | 336 | UPROPERTY( VisibleAnywhere, Category = "Houdini Point Cache Properties" ) 337 | FString FileName; 338 | 339 | // The number of values stored in the point cache 340 | UPROPERTY( VisibleAnywhere, Category = "Houdini Point Cache Properties" ) 341 | int32 NumberOfSamples; 342 | 343 | // The number of attributes stored in the point cache 344 | UPROPERTY( VisibleAnywhere, Category = "Houdini Point Cache Properties" ) 345 | int32 NumberOfAttributes; 346 | 347 | // The number of unique points found in the point cache 348 | UPROPERTY( VisibleAnywhere, Category = "Houdini Point Cache Properties") 349 | int32 NumberOfPoints; 350 | 351 | // The number of frames imported into the point cache 352 | UPROPERTY( VisibleAnywhere, Category = "Houdini Point Cache Properties") 353 | int32 NumberOfFrames; 354 | 355 | // The first frame of the exported frame range 356 | UPROPERTY( VisibleAnywhere, Category = "Houdini Point Cache Properties") 357 | float FirstFrame; 358 | 359 | // The last frame of the exported frame range 360 | UPROPERTY( VisibleAnywhere, Category = "Houdini Point Cache Properties") 361 | float LastFrame; 362 | 363 | // The minimum sample time value, in seconds, in the point cache 364 | UPROPERTY( VisibleAnywhere, Category = "Houdini Point Cache Properties") 365 | float MinSampleTime; 366 | 367 | // The maximum sample time value, in seconds, in the point cache 368 | UPROPERTY( VisibleAnywhere, Category = "Houdini Point Cache Properties") 369 | float MaxSampleTime; 370 | 371 | // The source title row for CSV files, describing the content of each column and used to locate specific values in the point cache. 372 | // Editing this will trigger a re-import of the point cache. 373 | UPROPERTY(EditAnywhere, Category = "Houdini Point Cache Properties") 374 | FString SourceCSVTitleRow; 375 | 376 | // The final attribute names used by the asset after parsing. 377 | // Packed vector values are expanded, so additional attributes (.0, .1, ... or .x, .y, .z) might have been inserted. 378 | // Use the indexes in this array to access your data. 379 | UPROPERTY( VisibleAnywhere, Category = "Houdini Point Cache Properties" ) 380 | TArray AttributeArray; 381 | 382 | #if WITH_EDITORONLY_DATA 383 | /** Importing data and options used for this asset */ 384 | UPROPERTY( EditAnywhere, Instanced, Category = ImportSettings ) 385 | class UAssetImportData* AssetImportData; 386 | 387 | // Raw data of the source file so that we can export it again. 388 | UPROPERTY() 389 | TArray RawDataCompressed; 390 | 391 | // Compression scheme used to compress raw 392 | UPROPERTY( VisibleAnywhere, Category = "Houdini Point Cache Properties" ) 393 | FName RawDataFormatID; 394 | 395 | // Size of data when uncompressed 396 | UPROPERTY( VisibleAnywhere, Category = "Houdini Point Cache Properties" ) 397 | uint32 RawDataUncompressedSize; 398 | 399 | // Compression scheme used to compress raw 400 | UPROPERTY( VisibleAnywhere, Category = "Houdini Point Cache Properties" ) 401 | FName RawDataCompressionMethod; 402 | #endif 403 | 404 | #if WITH_EDITOR 405 | bool HasRawData() const { return RawDataCompressed.Num() > 0; }; 406 | 407 | virtual void PostInitProperties() override; 408 | virtual void PostEditChangeProperty(FPropertyChangedEvent & PropertyChangedEvent) override; 409 | #endif 410 | 411 | #if ENGINE_MAJOR_VERSION == 5 && ENGINE_MINOR_VERSION >= 4 412 | virtual void GetAssetRegistryTags(FAssetRegistryTagsContext Context) const override; 413 | #endif 414 | 415 | virtual void GetAssetRegistryTags(TArray< FAssetRegistryTag > & OutTags) const override; 416 | 417 | void BeginDestroy() override; 418 | 419 | // Data Accessors, const and non-const versions 420 | TArray& GetFloatSampleData() { return FloatSampleData; } 421 | 422 | UFUNCTION(BlueprintCallable, Category = "Houdini Point Cache Data") 423 | const TArray& GetFloatSampleData() const { return FloatSampleData; } 424 | 425 | TArray& GetSpawnTimes() { return SpawnTimes; } 426 | 427 | UFUNCTION(BlueprintCallable, Category = "Houdini Point Cache Data") 428 | const TArray& GetSpawnTimes() const { return SpawnTimes; } 429 | 430 | TArray& GetLifeValues() { return LifeValues; } 431 | 432 | UFUNCTION(BlueprintCallable, Category = "Houdini Point Cache Data") 433 | const TArray& GetLifeValues() const { return LifeValues; } 434 | 435 | TArray& GetPointTypes() { return PointTypes; } 436 | 437 | UFUNCTION(BlueprintCallable, Category = "Houdini Point Cache Data") 438 | const TArray& GetPointTypes() const { return PointTypes; } 439 | 440 | TArray& GetSpecialAttributeIndexes() { return SpecialAttributeIndexes; } 441 | 442 | UFUNCTION(BlueprintCallable, Category = "Houdini Point Cache Data") 443 | const TArray& GetSpecialAttributeIndexes() const { return SpecialAttributeIndexes; } 444 | 445 | TArray& GetPointValueIndexes() { return PointValueIndexes; } 446 | 447 | UFUNCTION() 448 | const TArray& GetPointValueIndexes() const { return PointValueIndexes; } 449 | 450 | UFUNCTION(BlueprintCallable, Category = "Houdini Point Cache Settings") 451 | bool GetUseCustomCSVTitleRow() const { return UseCustomCSVTitleRow; } 452 | 453 | UFUNCTION(BlueprintCallable, Category = "Houdini Point Cache Settings") 454 | void SetUseCustomCSVTitleRow(bool bInUseCustomCSVTitleRow) { UseCustomCSVTitleRow = bInUseCustomCSVTitleRow; } 455 | 456 | /** The GPU resource for this point cache. */ 457 | TUniquePtr Resource; 458 | 459 | void RequestPushToGPU(); 460 | 461 | private: 462 | 463 | /* 464 | // Array containing the Raw String data 465 | UPROPERTY() 466 | TArray StringCSVData; 467 | */ 468 | 469 | // Array containing all the sample data converted to floats 470 | UPROPERTY() 471 | TArray FloatSampleData; 472 | 473 | // Array containing the spawn times for each point in the point cache 474 | UPROPERTY() 475 | TArray SpawnTimes; 476 | 477 | // Array containing all the life values for each point in the point cache 478 | UPROPERTY() 479 | TArray LifeValues; 480 | 481 | // Array containing all the type values for each point in the point cache 482 | UPROPERTY() 483 | TArray PointTypes; 484 | 485 | // Array containing the column indexes of the special attributes 486 | UPROPERTY() 487 | TArray SpecialAttributeIndexes; 488 | 489 | /* 490 | // Row indexes for new time values 491 | UPROPERTY() 492 | TMap TimeValuesIndexes; 493 | */ 494 | 495 | // Sample indexes for each point 496 | UPROPERTY() 497 | TArray< FPointIndexes > PointValueIndexes; 498 | 499 | /** For CSV source files, whether to use a custom title row. */ 500 | UPROPERTY() 501 | bool UseCustomCSVTitleRow; 502 | 503 | // The type of source file, such as CSV or JSON. 504 | UPROPERTY() 505 | EHoudiniPointCacheFileType FileType; 506 | }; -------------------------------------------------------------------------------- /Source/HoudiniNiagara/Public/HoudiniPointCacheLoader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #pragma once 25 | 26 | #include "CoreMinimal.h" 27 | #include "HoudiniPointCache.h" 28 | 29 | class UHoudiniPointCache; 30 | 31 | struct FHoudiniPointCacheSortPredicate 32 | { 33 | FHoudiniPointCacheSortPredicate(const int32 &InTimeAttrIndex, const int32 &InAgeAttrIndex, const int32 &InIDAttrIndex ); 34 | 35 | template 36 | bool operator()( const TArray& A, const TArray& B ) const 37 | { 38 | float ATime = TNumericLimits< float >::Lowest(); 39 | if ( A.IsValidIndex( TimeAttributeIndex ) ) 40 | ATime = A[ TimeAttributeIndex ]; 41 | 42 | float BTime = TNumericLimits< float >::Lowest(); 43 | if ( B.IsValidIndex( TimeAttributeIndex ) ) 44 | BTime = B[ TimeAttributeIndex ]; 45 | 46 | if ( ATime != BTime ) 47 | { 48 | return ATime < BTime; 49 | } 50 | else 51 | { 52 | float AAge = TNumericLimits< float >::Lowest(); 53 | if (A.IsValidIndex(AgeAttributeIndex)) 54 | AAge = A[AgeAttributeIndex]; 55 | 56 | float BAge = TNumericLimits< float >::Lowest(); 57 | if (B.IsValidIndex(AgeAttributeIndex)) 58 | BAge = B[AgeAttributeIndex]; 59 | 60 | if (AAge != BAge) 61 | { 62 | return BAge < AAge; 63 | } 64 | else 65 | { 66 | float AID = TNumericLimits< float >::Lowest(); 67 | if (A.IsValidIndex(IDAttributeIndex)) 68 | AID = A[IDAttributeIndex]; 69 | 70 | float BID = TNumericLimits< float >::Lowest(); 71 | if (B.IsValidIndex(IDAttributeIndex)) 72 | BID = B[IDAttributeIndex]; 73 | 74 | return AID <= BID; 75 | } 76 | } 77 | } 78 | 79 | bool operator()( const TArray& A, const TArray& B ) const; 80 | 81 | int32 TimeAttributeIndex; 82 | int32 AgeAttributeIndex; 83 | int32 IDAttributeIndex; 84 | }; 85 | 86 | 87 | /** 88 | * This class is a base class for file loaders for the HoudiniPointCache asset. 89 | */ 90 | class FHoudiniPointCacheLoader 91 | { 92 | public: 93 | FHoudiniPointCacheLoader(const FString& InFilePath); 94 | 95 | virtual ~FHoudiniPointCacheLoader(); 96 | 97 | template 98 | static TSharedPtr Create(const FString& InFilePath) 99 | { 100 | FHoudiniPointCacheLoader *Instance = new T(InFilePath); 101 | return MakeShareable(static_cast(Instance)); 102 | } 103 | 104 | 105 | #if WITH_EDITOR 106 | /** 107 | * Load the data from FilePath to InAsset. 108 | * Returns false on failure. 109 | */ 110 | virtual bool LoadToAsset(UHoudiniPointCache *InAsset) = 0; 111 | 112 | virtual FName GetFormatID() const { return NAME_None; }; 113 | 114 | const FString& GetFilePath() const { return FilePath; } 115 | #endif 116 | 117 | protected: 118 | 119 | #if WITH_EDITOR 120 | bool LoadRawPointCacheData(UHoudiniPointCache* InAsset, const FString& InFilePath) const; 121 | void CompressRawData(UHoudiniPointCache* InAsset) const; 122 | #endif 123 | 124 | private: 125 | /** The file to load from. */ 126 | FString FilePath; 127 | 128 | }; 129 | -------------------------------------------------------------------------------- /Source/HoudiniNiagara/Public/HoudiniPointCacheLoaderBJSON.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #pragma once 25 | 26 | #include "CoreMinimal.h" 27 | #include "HoudiniPointCacheLoaderJSONBase.h" 28 | 29 | class UHoudiniPointCache; 30 | 31 | /** This is loader for Houdini Point cache data from a custom binary JSON-like format. 32 | * See HoudiniPointCacheLoaderJSON.h for the text spec. 33 | * 34 | * Similar to the text JSON format objects are start and ended with a byte { or } and arrays 35 | * with [ ]. The data type of each field is known/fixed. Field names are present in the 36 | * file as strings, and strings are read as [size_type_marker:1 byte][size][string_data]. No other entries 37 | * in the file are prefixed with types or sizes. The point data attribute frame 38 | * sample array value types are determined by 'attrib_data_type' (see the various Marker* definitions) 39 | * in the header. 40 | */ 41 | class FHoudiniPointCacheLoaderBJSON : public FHoudiniPointCacheLoaderJSONBase 42 | { 43 | public: 44 | /** Construct with the input file path. */ 45 | FHoudiniPointCacheLoaderBJSON(const FString& InFilePath); 46 | 47 | #if WITH_EDITOR 48 | /** Load the data from FilePath into a UHoudiniPointCache asset. 49 | * @return false on errors, true otherwise. 50 | */ 51 | virtual bool LoadToAsset(UHoudiniPointCache *InAsset) override; 52 | 53 | virtual FName GetFormatID() const override { return "HBJSON"; }; 54 | #endif 55 | 56 | // Strings are written with [size_type_marker][size][string_data] 57 | // The header also contains a field 'attrib_data_type' that defines 58 | // data type of each attribute used in sample data. These markers 59 | // define the various supported data types, along with some special 60 | // markers for the start/end of objects and arrays. 61 | static const unsigned char MarkerTypeChar = 'c'; 62 | static const unsigned char MarkerTypeInt8 = 'b'; 63 | static const unsigned char MarkerTypeUInt8 = 'B'; 64 | static const unsigned char MarkerTypeBool = '?'; 65 | static const unsigned char MarkerTypeInt16 = 'h'; 66 | static const unsigned char MarkerTypeUInt16 = 'H'; 67 | static const unsigned char MarkerTypeInt32 = 'l'; 68 | static const unsigned char MarkerTypeUInt32 = 'L'; 69 | static const unsigned char MarkerTypeInt64 = 'q'; 70 | static const unsigned char MarkerTypeUInt64 = 'Q'; 71 | static const unsigned char MarkerTypeFloat32 = 'f'; 72 | static const unsigned char MarkerTypeFloat64 = 'd'; 73 | static const unsigned char MarkerTypeString = 's'; 74 | static const unsigned char MarkerObjectStart = '{'; 75 | static const unsigned char MarkerObjectEnd = '}'; 76 | static const unsigned char MarkerArrayStart = '['; 77 | static const unsigned char MarkerArrayEnd = ']'; 78 | 79 | /** Read the next `InSize` bytes into `Buffer` but leave the reader at the starting point. */ 80 | bool Peek(int32 InSize); 81 | 82 | /** Check if `InNext` is the next data that will be read from `Reader`. */ 83 | template 84 | inline bool IsNext(const T &InNext) 85 | { 86 | const bool bResult = (Peek(sizeof(T)) && (*reinterpret_cast(Buffer.GetData())) == InNext); 87 | return bResult; 88 | } 89 | 90 | /** Read a type marker from `Reader`. */ 91 | bool ReadMarker(unsigned char &OutMarker); 92 | 93 | /** Read an array from the file. To treat each entry in the array as the same data type, set InSingleMarkerType. To specify 94 | * a distinct data type for each array element, pass an array of markers as InMarkerTypes. 95 | */ 96 | template 97 | bool ReadArray(TArray &OutArray, unsigned char InSingleMarkerType='\0', const TArray *InMarkerTypes=nullptr) 98 | { 99 | // Expect array start 100 | unsigned char Marker = '\0'; 101 | if (!ReadMarker(Marker) || Marker != MarkerArrayStart) 102 | return false; 103 | // Read until we reach MarkerArrayEnd or EOF 104 | int32 Index = 0; 105 | while (!Reader->AtEnd() && !IsNext(MarkerArrayEnd)) 106 | { 107 | T Value; 108 | // Determine the data type, which in turn determines the number of bytes to 109 | // read next. If InSingleMarkerType is set and InMarkerTypes is not set, then 110 | // read everything as InSingleMarkerType data. 111 | unsigned char MarkerType = InSingleMarkerType; 112 | if (InMarkerTypes && Index < InMarkerTypes->Num()) 113 | { 114 | MarkerType = InMarkerTypes->GetData()[Index]; 115 | } 116 | if (!ReadNonContainerValue(Value, false, MarkerType)) 117 | { 118 | return false; 119 | } 120 | else 121 | { 122 | OutArray.Add(Value); 123 | } 124 | Index++; 125 | } 126 | 127 | // Consume the end of array marker 128 | if (!ReadMarker(Marker) || Marker != MarkerArrayEnd) 129 | return false; 130 | 131 | return true; 132 | } 133 | 134 | /** Read the header from `Reader` and populate `OutHeader`. */ 135 | bool ReadHeader(struct FHoudiniPointCacheJSONHeader &OutHeader); 136 | 137 | /** Read a plain old data type, no objects or arrays. If `bInReadMarkerType` is true, then first read one byte to determine the data type 138 | * to expect. Otherwise pass `InMarkerType` to specify the data type, and thus the size, to read from `Reader`. Data is read and interpreted 139 | * according to InMarkerType (or the detected marker) and then cast to T. 140 | */ 141 | template 142 | bool ReadNonContainerValue(T &OutValue, bool bInReadMarkerType=false, unsigned char InMarkerType=TCHAR('\0')) 143 | { 144 | uint32 Size = 0; 145 | unsigned char MarkerType = TCHAR('\0'); 146 | 147 | if (!CheckReader()) 148 | return false; 149 | 150 | if (bInReadMarkerType) 151 | { 152 | // Read TYPE (1 byte) 153 | Reader->Serialize(Buffer.GetData(), 1); 154 | MarkerType = Buffer[0]; 155 | } 156 | else 157 | { 158 | MarkerType = InMarkerType; 159 | } 160 | 161 | // Determine the size to read, in bytes, based on MarkerType 162 | switch (MarkerType) 163 | { 164 | case '\0': 165 | Size = sizeof(T); 166 | break; 167 | case MarkerTypeChar: 168 | Size = 1; 169 | break; 170 | case MarkerTypeInt8: 171 | case MarkerTypeUInt8: 172 | case MarkerTypeBool: 173 | Size = 1; 174 | break; 175 | case MarkerTypeInt16: 176 | case MarkerTypeUInt16: 177 | Size = 2; 178 | break; 179 | case MarkerTypeInt32: 180 | case MarkerTypeUInt32: 181 | Size = 4; 182 | break; 183 | case MarkerTypeInt64: 184 | case MarkerTypeUInt64: 185 | Size = 8; 186 | break; 187 | case MarkerTypeFloat32: 188 | Size = 4; 189 | break; 190 | case MarkerTypeFloat64: 191 | Size = 8; 192 | break; 193 | default: 194 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Unknown marker type %c"), TCHAR(MarkerType)); 195 | return false; 196 | } 197 | 198 | if (Size > sizeof(T)) 199 | { 200 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Could not read value of %d bytes into %d byte target."), Size, sizeof(T)); 201 | return false; 202 | } 203 | 204 | FMemory::Memzero(OutValue); 205 | if (Size == 0) 206 | return true; 207 | 208 | Reader->ByteOrderSerialize(Buffer.GetData(), Size); 209 | 210 | // Interpret data in the type associated with the marker and then cast to T 211 | switch (MarkerType) 212 | { 213 | case '\0': 214 | OutValue = *reinterpret_cast(Buffer.GetData()); 215 | break; 216 | case MarkerTypeChar: 217 | OutValue = static_cast(*reinterpret_cast(Buffer.GetData())); 218 | break; 219 | case MarkerTypeInt8: 220 | OutValue = static_cast(*reinterpret_cast(Buffer.GetData())); 221 | break; 222 | case MarkerTypeUInt8: 223 | OutValue = static_cast(*reinterpret_cast(Buffer.GetData())); 224 | break; 225 | case MarkerTypeBool: 226 | OutValue = static_cast(*reinterpret_cast(Buffer.GetData())); 227 | break; 228 | case MarkerTypeInt16: 229 | OutValue = static_cast(*reinterpret_cast(Buffer.GetData())); 230 | break; 231 | case MarkerTypeUInt16: 232 | OutValue = static_cast(*reinterpret_cast(Buffer.GetData())); 233 | break; 234 | case MarkerTypeInt32: 235 | OutValue = static_cast(*reinterpret_cast(Buffer.GetData())); 236 | break; 237 | case MarkerTypeUInt32: 238 | OutValue = static_cast(*reinterpret_cast(Buffer.GetData())); 239 | break; 240 | case MarkerTypeInt64: 241 | OutValue = static_cast(*reinterpret_cast(Buffer.GetData())); 242 | break; 243 | case MarkerTypeUInt64: 244 | OutValue = static_cast(*reinterpret_cast(Buffer.GetData())); 245 | break; 246 | case MarkerTypeFloat32: 247 | OutValue = static_cast(*reinterpret_cast(Buffer.GetData())); 248 | break; 249 | case MarkerTypeFloat64: 250 | OutValue = static_cast(*reinterpret_cast(Buffer.GetData())); 251 | break; 252 | default: 253 | UE_LOG(LogHoudiniNiagara, Error, TEXT("Unhandled marker type %c"), TCHAR(MarkerType)); 254 | return false; 255 | } 256 | 257 | return true; 258 | } 259 | 260 | /** Read a string from `Reader`. */ 261 | bool ReadNonContainerValue(FString &OutValue, bool bInReadMarkerType=false, unsigned char InMarkerType=MarkerTypeString); 262 | 263 | protected: 264 | // File stream 265 | TUniquePtr Reader; 266 | // Buffer that is used to store data that was read from the Reader and is being processed. 267 | TArray Buffer; 268 | 269 | // Checks if Reader is valid and not null, if not, log an error and return false 270 | bool CheckReader(bool bInCheckAtEnd=true) const; 271 | }; 272 | -------------------------------------------------------------------------------- /Source/HoudiniNiagara/Public/HoudiniPointCacheLoaderCSV.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #pragma once 25 | 26 | #include "CoreMinimal.h" 27 | #include "HoudiniPointCacheLoader.h" 28 | 29 | class UHoudiniPointCache; 30 | 31 | class FHoudiniPointCacheLoaderCSV : public FHoudiniPointCacheLoader 32 | { 33 | public: 34 | FHoudiniPointCacheLoaderCSV(const FString& InFilePath); 35 | 36 | #if WITH_EDITOR 37 | virtual bool LoadToAsset(UHoudiniPointCache *InAsset) override; 38 | 39 | virtual FName GetFormatID() const override { return "HCSV"; }; 40 | #endif 41 | 42 | protected: 43 | 44 | #if WITH_EDITOR 45 | virtual bool UpdateFromStringArray(UHoudiniPointCache *InAsset, TArray& InStringArray); 46 | 47 | // Parses the CSV title row to update the column indexes of special values we're interested in 48 | // Also look for packed vectors in the first row and update the indexes accordingly 49 | virtual bool ParseCSVTitleRow(UHoudiniPointCache *InAsset, const FString& TitleRow, const FString& FirstValueRow, bool& HasPackedVectors); 50 | #endif 51 | }; -------------------------------------------------------------------------------- /Source/HoudiniNiagara/Public/HoudiniPointCacheLoaderJSON.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #pragma once 25 | 26 | #include "CoreMinimal.h" 27 | #include "Dom/JsonObject.h" 28 | #include "HoudiniPointCacheLoaderJSONBase.h" 29 | 30 | class UHoudiniPointCache; 31 | 32 | /** 33 | * Text JSON Houdini Point Cache loader using FJsonReader and FJsonSerializer. 34 | * An example: 35 | * { 36 | * "header" : { 37 | * "version" : 1.0, 38 | * "num_samples" : 100, (?? needed ??) 39 | * “num_frames” : 10, 40 | * “num_points” : 10, 41 | * “num_attrib” : 5 42 | * “attrib_name” : [ "id", "P", “N”, “Cd”, “age” ] 43 | * “attrib_size” : [ 1, 3, 3, 4, 1 ] 44 | * “data_type” : “linear”, 45 | * }, 46 | * “cache_data” : { 47 | * “frames” : [ 48 | * { 49 | * “number” : 0 50 | * “time” : 0.0 51 | * “frame_data” : [ 52 | * [0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 53 | * [1, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 54 | * [2, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 55 | * [3, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 56 | * [4, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 57 | * [5, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 58 | * [6, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 59 | * [7, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 60 | * [8, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 61 | * [9, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 62 | * ] 63 | * }, 64 | * { 65 | * “number” : 1 66 | * “time” : 0.033333 67 | * “frame_data” : { 68 | * [0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 69 | * [1, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 70 | * [2, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 71 | * [3, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 72 | * [4, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 73 | * [5, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 74 | * [6, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 75 | * [7, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 76 | * [8, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 77 | * [9, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,1.0, 1.0,1.0, 1.0, 0.0 ] 78 | * } 79 | * } 80 | * ] 81 | * } 82 | * } 83 | */ 84 | class FHoudiniPointCacheLoaderJSON : public FHoudiniPointCacheLoaderJSONBase 85 | { 86 | public: 87 | FHoudiniPointCacheLoaderJSON(const FString& InFilePath); 88 | 89 | #if WITH_EDITOR 90 | virtual bool LoadToAsset(UHoudiniPointCache *InAsset) override; 91 | 92 | virtual FName GetFormatID() const override { return "HJSON"; }; 93 | #endif 94 | 95 | /** Read and process the 'header' object from the JSON InPointCacheObject and populate OutHeader. */ 96 | bool ReadHeader(const FJsonObject &InPointCacheObject, FHoudiniPointCacheJSONHeader &OutHeader) const; 97 | }; 98 | -------------------------------------------------------------------------------- /Source/HoudiniNiagara/Public/HoudiniPointCacheLoaderJSONBase.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #pragma once 25 | 26 | #include "CoreMinimal.h" 27 | #include "HoudiniPointCacheLoader.h" 28 | 29 | class UHoudiniPointCache; 30 | 31 | struct FHoudiniPointCacheJSONHeader 32 | { 33 | FString Version; 34 | uint32 NumSamples; 35 | uint32 NumFrames; 36 | uint32 NumPoints; 37 | uint32 NumAttributes; 38 | uint32 NumAttributeComponents; 39 | TArray Attributes; 40 | TArray AttributeSizes; 41 | TArray AttributeComponentDataTypes; 42 | FString DataType; 43 | }; 44 | 45 | 46 | /** 47 | * Base class for JSON-based Houdini Point Cache loaders. 48 | */ 49 | class FHoudiniPointCacheLoaderJSONBase : public FHoudiniPointCacheLoader 50 | { 51 | public: 52 | /** Construct with the path to the file */ 53 | FHoudiniPointCacheLoaderJSONBase(const FString& InFilePath); 54 | 55 | virtual ~FHoudiniPointCacheLoaderJSONBase(); 56 | 57 | protected: 58 | /** Using InHeader, parse the attributes and generate the expanded list (P -> P.x, P.y, P.z etc) and initialize (pre-allocate/reset) the 59 | * internal arrays of InAsset, such as FloatSampleData, SpawnTimes etc. 60 | */ 61 | virtual bool ParseAttributesAndInitAsset(UHoudiniPointCache *InAsset, const struct FHoudiniPointCacheJSONHeader &InHeader); 62 | 63 | /** Process one frame's data (InFrameData). 64 | * @param InAsset The point cache asset to populate. 65 | * @param InFrameNumber The frame number 66 | * @param InFrameData The frame's data, an array of arrays (point and attribute values for the point). 67 | * @param InFrameTime The time, in seconds, of the frame. 68 | * @param InFrameStartSampleIndex The sample index of the first sample in the frame. 69 | * @param InNumPointsInFrame The number of points in this frame. 70 | * @param InNumAttributesPerPoint The number of attributes in a point's sample. 71 | * @param InHeader The header data of the point cache file. 72 | * @param InHoudiniIDToNiagaraIDMap A map of point ids from the file, to our internal id 73 | * @param OutNextPointID The next point id (incremented everytime a new point is detected). 74 | * @return false if processing the frame failed. 75 | */ 76 | virtual bool ProcessFrame(UHoudiniPointCache *InAsset, float InFrameNumber, const TArray> &InFrameData, float InFrameTime, uint32 InFrameStartSampleIndex, uint32 InNumPointsInFrame, uint32 InNumAttributesPerPoint, const FHoudiniPointCacheJSONHeader &InHeader, TMap& InHoudiniIDToNiagaraIDMap, int32 &OutNextPointID) const; 77 | 78 | }; 79 | -------------------------------------------------------------------------------- /Source/HoudiniNiagaraEditor/HoudiniNiagaraEditor.Build.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | using UnrealBuildTool; 25 | 26 | public class HoudiniNiagaraEditor : ModuleRules 27 | { 28 | public HoudiniNiagaraEditor(ReadOnlyTargetRules Target) : base(Target) 29 | { 30 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 31 | 32 | 33 | PrivateIncludePaths.AddRange( 34 | new string[] 35 | { 36 | "HoudiniNiagaraEditor/Private", 37 | } 38 | ); 39 | 40 | 41 | PublicDependencyModuleNames.AddRange( 42 | new string[] 43 | { 44 | "Core", 45 | "Niagara", 46 | "NiagaraShader", 47 | "CoreUObject", 48 | "VectorVM", 49 | "RHI", 50 | "NiagaraVertexFactories", 51 | "RenderCore", 52 | "HoudiniNiagara", 53 | "UnrealEd", 54 | "EditorStyle", 55 | } 56 | ); 57 | 58 | 59 | PrivateDependencyModuleNames.AddRange( 60 | new string[] 61 | { 62 | "CoreUObject", 63 | "Engine", 64 | "Slate", 65 | "SlateCore", 66 | "Niagara", 67 | "NiagaraShader", 68 | "HoudiniNiagara", 69 | "Projects", 70 | "ToolMenus", 71 | } 72 | ); 73 | 74 | PrivateIncludePathModuleNames.AddRange( 75 | new string[] 76 | { 77 | "Engine", 78 | "LevelEditor", 79 | "AssetTools", 80 | "ContentBrowser", 81 | } 82 | ); 83 | 84 | DynamicallyLoadedModuleNames.AddRange( 85 | new string[] 86 | { 87 | // ... add any modules that your module loads dynamically here ... 88 | } 89 | ); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /Source/HoudiniNiagaraEditor/Private/HoudiniNiagaraEditor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #include "HoudiniNiagaraEditor.h" 25 | #include "HoudiniPointCacheAssetActions.h" 26 | 27 | #include "AssetRegistry/AssetRegistryModule.h" 28 | #include "Interfaces/IPluginManager.h" 29 | #include "Styling/SlateStyleRegistry.h" 30 | #include "Styling/SlateStyle.h" 31 | 32 | #define LOCTEXT_NAMESPACE "FHoudiniNiagaraModule" 33 | 34 | void FHoudiniNiagaraEditorModule::StartupModule() 35 | { 36 | // Register the Houdini CSV Type Actions 37 | IAssetTools& AssetTools = FModuleManager::LoadModuleChecked< FAssetToolsModule >("AssetTools").Get(); 38 | 39 | TSharedRef< IAssetTypeActions > HCSVAction = MakeShareable( new FHoudiniPointCacheAssetActions() ); 40 | AssetTools.RegisterAssetTypeActions( HCSVAction ); 41 | AssetTypeActions.Add( HCSVAction ); 42 | 43 | // Create Slate style set. 44 | if (!StyleSet.IsValid()) 45 | { 46 | // Create Slate style set. 47 | StyleSet = MakeShareable(new FSlateStyleSet(TEXT("HoudiniNiagaraStyle"))); 48 | StyleSet->SetContentRoot(FPaths::EngineContentDir() / TEXT("Editor/Slate")); 49 | StyleSet->SetCoreContentRoot(FPaths::EngineContentDir() / TEXT("Slate")); 50 | 51 | // Note, these sizes are in Slate Units. Slate Units do NOT have to map to pixels. 52 | const FVector2D Icon16x16(16.0f, 16.0f); 53 | const FVector2D Icon64x64(64.0f, 64.0f); 54 | const FVector2D Icon128x128(128.0f, 128.0f); 55 | 56 | static FString IconsDir = FPaths::EnginePluginsDir() / TEXT("FX/HoudiniNiagara/Resources/"); 57 | if (!FPaths::DirectoryExists(IconsDir)) 58 | { 59 | IconsDir = IPluginManager::Get().FindPlugin(TEXT("HoudiniNiagara"))->GetBaseDir() / TEXT("Resources/"); 60 | } 61 | 62 | if (FPaths::DirectoryExists(IconsDir) == false) 63 | IconsDir = FPaths::ProjectPluginsDir() / TEXT("HoudiniNiagara/Resources/"); 64 | 65 | // Register the Asset icon 66 | StyleSet->Set( 67 | "ClassIcon.HoudiniPointCache", 68 | new FSlateImageBrush(IconsDir + TEXT("HCSVIcon128.png"), Icon16x16)); 69 | 70 | StyleSet->Set( 71 | "ClassThumbnail.HoudiniPointCache", 72 | new FSlateImageBrush(IconsDir + TEXT("HCSVIcon128.png"), Icon64x64)); 73 | 74 | // Register Slate style. 75 | FSlateStyleRegistry::RegisterSlateStyle(*StyleSet.Get()); 76 | } 77 | } 78 | 79 | void FHoudiniNiagaraEditorModule::ShutdownModule() 80 | { 81 | // Unregister asset type actions we have previously registered. 82 | if ( FModuleManager::Get().IsModuleLoaded("AssetTools") ) 83 | { 84 | IAssetTools & AssetTools = FModuleManager::GetModuleChecked< FAssetToolsModule >("AssetTools").Get(); 85 | 86 | for ( int32 Index = 0; Index < AssetTypeActions.Num(); ++Index ) 87 | AssetTools.UnregisterAssetTypeActions( AssetTypeActions[Index].ToSharedRef() ); 88 | 89 | AssetTypeActions.Empty(); 90 | } 91 | 92 | // Unregister Slate style set. 93 | if (StyleSet.IsValid()) 94 | { 95 | // Unregister Slate style. 96 | FSlateStyleRegistry::UnRegisterSlateStyle(*StyleSet.Get()); 97 | 98 | ensure(StyleSet.IsUnique()); 99 | StyleSet.Reset(); 100 | } 101 | } 102 | 103 | #undef LOCTEXT_NAMESPACE 104 | 105 | IMPLEMENT_MODULE(FHoudiniNiagaraEditorModule, HoudiniNiagaraEditor) 106 | -------------------------------------------------------------------------------- /Source/HoudiniNiagaraEditor/Private/HoudiniPointCacheAssetActions.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | #include "HoudiniPointCacheAssetActions.h" 24 | 25 | #include "ToolMenus.h" 26 | #include "HoudiniPointCache.h" 27 | #include "EditorStyleSet.h" 28 | #include "Toolkits/AssetEditorToolkit.h" 29 | #include "EditorReimportHandler.h" 30 | #include "HAL/FileManager.h" 31 | 32 | //#include "HoudiniPointCacheAssetEditorToolkit.h" 33 | 34 | #define LOCTEXT_NAMESPACE "AssetTypeActions" 35 | 36 | 37 | FHoudiniPointCacheAssetActions::FHoudiniPointCacheAssetActions() 38 | { } 39 | 40 | bool FHoudiniPointCacheAssetActions::CanFilter() 41 | { 42 | return true; 43 | } 44 | 45 | 46 | void FHoudiniPointCacheAssetActions::GetActions(const TArray& InObjects, FToolMenuSection& Section) 47 | { 48 | FAssetTypeActions_Base::GetActions(InObjects, Section); 49 | 50 | auto HoudiniPointCacheAssets = GetTypedWeakObjectPtrs(InObjects); 51 | 52 | Section.AddMenuEntry( 53 | "ReimportHoudiniPointCacheLabel", 54 | LOCTEXT("ReimportHoudiniPointCacheLabel", "Reimport"), 55 | LOCTEXT("ReimportHoudiniPointCacheTooltip", "Reimport the selected Houdini Point Cache file(s)."), 56 | FSlateIcon(FAppStyle::GetAppStyleSetName(), "ContentBrowser.AssetActions.ReimportAsset"), 57 | FUIAction( 58 | FExecuteAction::CreateSP(this, &FHoudiniPointCacheAssetActions::ExecuteReimport, HoudiniPointCacheAssets), 59 | FCanExecuteAction::CreateSP(this, &FHoudiniPointCacheAssetActions::CanExecuteReimport, HoudiniPointCacheAssets) 60 | ) 61 | ); 62 | 63 | Section.AddMenuEntry( 64 | "OpenHoudiniPointCacheLabel", 65 | LOCTEXT("OpenHoudiniPointCacheLabel", "Open in Text Editor"), 66 | LOCTEXT("OpenHoudiniPointCacheTooltip", "Open the selected Houdini Point Cache file(s) in a Text Editor."), 67 | FSlateIcon(FAppStyle::GetAppStyleSetName(), "ContentBrowser.AssetActions.OpenInExternalEditor"), 68 | FUIAction( 69 | FExecuteAction::CreateSP(this, &FHoudiniPointCacheAssetActions::ExecuteOpenInEditor, HoudiniPointCacheAssets), 70 | FCanExecuteAction::CreateSP(this, &FHoudiniPointCacheAssetActions::CanExecuteOpenInEditor, HoudiniPointCacheAssets) 71 | ) 72 | ); 73 | 74 | Section.AddMenuEntry( 75 | "FindHoudiniPointCacheInExplorer", 76 | LOCTEXT("FindHoudiniPointCacheInExplorer", "Find Source File"), 77 | LOCTEXT("FindHoudiniPointCacheInExplorerTooltip", "Opens explorer at the location of this asset's source file."), 78 | FSlateIcon(FAppStyle::GetAppStyleSetName(), "ContentBrowser.AssetActions.OpenInExternalEditor"), 79 | FUIAction( 80 | FExecuteAction::CreateSP(this, &FHoudiniPointCacheAssetActions::ExecuteFindInExplorer, HoudiniPointCacheAssets), 81 | FCanExecuteAction::CreateSP(this, &FHoudiniPointCacheAssetActions::CanExecuteFindInExplorer, HoudiniPointCacheAssets) 82 | ) 83 | ); 84 | } 85 | 86 | 87 | uint32 FHoudiniPointCacheAssetActions::GetCategories() 88 | { 89 | return EAssetTypeCategories::Misc; 90 | } 91 | 92 | 93 | FText FHoudiniPointCacheAssetActions::GetName() const 94 | { 95 | return NSLOCTEXT("AssetTypeActions", "AssetTypeActions_HoudiniPointCache", "Houdini Point Cache Asset"); 96 | } 97 | 98 | 99 | UClass* FHoudiniPointCacheAssetActions::GetSupportedClass() const 100 | { 101 | return UHoudiniPointCache::StaticClass(); 102 | } 103 | 104 | FText FHoudiniPointCacheAssetActions::GetAssetDescription(const FAssetData& AssetData) const 105 | { 106 | /* 107 | UHoudiniPointCache* PointCacheAsset = Cast(AssetData.GetAsset()); 108 | if ( !PointCacheAsset ) 109 | return FText::GetEmpty(); 110 | 111 | FString StrDescription; 112 | StrDescription += TEXT("Number of Rows: ") + FString::FromInt(PointCacheAsset->NumberOfRows) + TEXT("\n"); 113 | StrDescription += TEXT("Number of Columns: ") + FString::FromInt(PointCacheAsset->NumberOfColumns) + TEXT("\n"); 114 | StrDescription += TEXT("Number of Points: ") + FString::FromInt(PointCacheAsset->NumberOfPoints) + TEXT("\n"); 115 | 116 | return FText::FromString(StrDescription); 117 | */ 118 | return FText::GetEmpty(); 119 | } 120 | 121 | 122 | FColor FHoudiniPointCacheAssetActions::GetTypeColor() const 123 | { 124 | //return FColor::Orange; 125 | return FColor(255, 165, 0); 126 | } 127 | 128 | 129 | bool FHoudiniPointCacheAssetActions::HasActions(const TArray& InObjects) const 130 | { 131 | return true; 132 | } 133 | 134 | 135 | class UThumbnailInfo* FHoudiniPointCacheAssetActions::GetThumbnailInfo(UObject* Asset) const 136 | { 137 | /* 138 | UHoudiniPointCache* HoudiniPointCache = CastChecked(Asset); 139 | UThumbnailInfo* ThumbnailInfo = HoudiniPointCache->ThumbnailInfo; 140 | if (ThumbnailInfo == NULL) 141 | { 142 | ThumbnailInfo = NewObject(HoudiniPointCache, NAME_None, RF_Transactional); 143 | HoudiniPointCache->ThumbnailInfo = ThumbnailInfo; 144 | } 145 | 146 | return ThumbnailInfo; 147 | */ 148 | 149 | return nullptr; 150 | } 151 | 152 | 153 | bool FHoudiniPointCacheAssetActions::CanExecuteReimport(const TArray> Objects) const 154 | { 155 | for ( auto ObjIt = Objects.CreateConstIterator(); ObjIt; ++ObjIt ) 156 | { 157 | auto Object = (*ObjIt).Get(); 158 | if (!Object) 159 | continue; 160 | 161 | // If one object is valid, we can execute the action 162 | return true; 163 | } 164 | 165 | return false; 166 | } 167 | 168 | void FHoudiniPointCacheAssetActions::ExecuteReimport(const TArray> Objects) const 169 | { 170 | for ( auto ObjIt = Objects.CreateConstIterator(); ObjIt; ++ObjIt ) 171 | { 172 | auto Object = (*ObjIt).Get(); 173 | if (!Object) 174 | continue; 175 | 176 | FReimportManager::Instance()->Reimport(Object, true); 177 | } 178 | } 179 | 180 | 181 | bool 182 | FHoudiniPointCacheAssetActions::CanExecuteOpenInEditor(const TArray> Objects) const 183 | { 184 | for (auto ObjIt = Objects.CreateConstIterator(); ObjIt; ++ObjIt) 185 | { 186 | auto Object = ( *ObjIt ).Get(); 187 | if (!Object) 188 | continue; 189 | 190 | const FString SourceFilePath = Object->FileName; 191 | if (!SourceFilePath.Len() || IFileManager::Get().FileSize(*SourceFilePath) == INDEX_NONE) 192 | continue; 193 | 194 | // If one object is valid, we can execute the action 195 | return true; 196 | } 197 | 198 | return false; 199 | } 200 | 201 | void 202 | FHoudiniPointCacheAssetActions::ExecuteOpenInEditor(const TArray> Objects) const 203 | { 204 | for ( auto ObjIt = Objects.CreateConstIterator(); ObjIt; ++ObjIt ) 205 | { 206 | auto Object = ( *ObjIt ).Get(); 207 | if (!Object) 208 | continue; 209 | 210 | const FString SourceFilePath = Object->FileName; 211 | if (!SourceFilePath.Len() || IFileManager::Get().FileSize(*SourceFilePath) == INDEX_NONE) 212 | continue; 213 | 214 | FPlatformProcess::LaunchFileInDefaultExternalApplication(*SourceFilePath, NULL, ELaunchVerb::Open); 215 | } 216 | } 217 | 218 | bool 219 | FHoudiniPointCacheAssetActions::CanExecuteFindInExplorer(const TArray> Objects) const 220 | { 221 | for ( auto ObjIt = Objects.CreateConstIterator(); ObjIt; ++ObjIt ) 222 | { 223 | auto Object = (*ObjIt).Get(); 224 | if (!Object) 225 | continue; 226 | 227 | const FString SourceFilePath = Object->FileName; 228 | if (!SourceFilePath.Len() || IFileManager::Get().FileSize(*SourceFilePath) == INDEX_NONE) 229 | continue; 230 | 231 | // If one object is valid, we can execute the action 232 | return true; 233 | } 234 | 235 | return false; 236 | } 237 | 238 | void 239 | FHoudiniPointCacheAssetActions::ExecuteFindInExplorer(const TArray> Objects) const 240 | { 241 | for ( auto ObjIt = Objects.CreateConstIterator(); ObjIt; ++ObjIt ) 242 | { 243 | auto Object = (*ObjIt).Get(); 244 | if (!Object) 245 | continue; 246 | 247 | const FString SourceFilePath = Object->FileName; 248 | if ( SourceFilePath.Len() && IFileManager::Get().FileSize(*SourceFilePath) != INDEX_NONE ) 249 | return FPlatformProcess::ExploreFolder(*SourceFilePath); 250 | } 251 | } 252 | 253 | #undef LOCTEXT_NAMESPACE 254 | -------------------------------------------------------------------------------- /Source/HoudiniNiagaraEditor/Private/HoudiniPointCacheExporterBase.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #include "HoudiniPointCacheExporterBase.h" 25 | #include "CoreMinimal.h" 26 | #include "Misc/Paths.h" 27 | #include "ShaderCompiler.h" 28 | #include "HoudiniPointCache.h" 29 | 30 | UHoudiniPointCacheExporterBase::UHoudiniPointCacheExporterBase() 31 | { 32 | 33 | } 34 | 35 | bool UHoudiniPointCacheExporterBase::SupportsObject(UObject* Object) const 36 | { 37 | if (!UExporter::SupportsObject(Object)) 38 | return false; // If the object doesn't match the SupportedClass, return false. 39 | 40 | // Further, ensure that the asset contains the source data needed for exporting. 41 | UHoudiniPointCache* PointCache = Cast(Object); 42 | if (!PointCache) 43 | return false; 44 | 45 | if (!IsRawFormatSupported(PointCache)) 46 | { 47 | return false; 48 | } 49 | 50 | return PointCache->HasRawData(); 51 | } 52 | 53 | bool UHoudiniPointCacheExporterBase::ExportBinary(UObject* Object, const TCHAR* Type, FArchive& Ar, 54 | FFeedbackContext* Warn, int32 FileIndex, uint32 PortFlags) 55 | { 56 | UHoudiniPointCache* PointCache = Cast(Object); 57 | if (!PointCache) 58 | { 59 | return false; 60 | } 61 | 62 | if (!PointCache->HasRawData()) 63 | { 64 | return false; 65 | } 66 | 67 | if (PointCache->RawDataCompressionMethod.IsEqual(NAME_None)) 68 | { 69 | return false; 70 | } 71 | 72 | // Uncompress data before serialization 73 | const uint32 UncompressedSize = PointCache->RawDataUncompressedSize; 74 | 75 | int32 CompressedSize = PointCache->RawDataCompressed.Num(); 76 | TArray UncompressedData; 77 | UncompressedData.SetNumUninitialized(UncompressedSize); 78 | 79 | if (FCompression::UncompressMemory( 80 | PointCache->RawDataCompressionMethod, 81 | UncompressedData.GetData(), 82 | UncompressedSize, 83 | PointCache->RawDataCompressed.GetData(), 84 | PointCache->RawDataCompressed.Num())) 85 | { 86 | Ar.Serialize(UncompressedData.GetData(), UncompressedSize); 87 | } 88 | 89 | return true; 90 | } 91 | -------------------------------------------------------------------------------- /Source/HoudiniNiagaraEditor/Private/HoudiniPointCacheExporterBase.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #pragma once 25 | 26 | #include "CoreMinimal.h" 27 | #include "Exporters/Exporter.h" 28 | #include "HoudiniPointCacheExporterBase.generated.h" 29 | 30 | UCLASS() 31 | class UHoudiniPointCacheExporterBase : public UExporter 32 | { 33 | GENERATED_BODY() 34 | public: 35 | UHoudiniPointCacheExporterBase(); 36 | 37 | // Returns whether this exporter supports the specific object 38 | virtual bool SupportsObject(UObject* Object) const override; 39 | virtual bool IsRawFormatSupported(class UHoudiniPointCache* PointCache) const { return true; } 40 | 41 | //~ Begin UExporter Interface 42 | // Default behaviour is to simply export the raw data, as is. 43 | virtual bool ExportBinary( UObject* Object, const TCHAR* Type, FArchive& Ar, FFeedbackContext* Warn, int32 FileIndex = 0, uint32 PortFlags=0 ) override; 44 | //~ End UExporter Interface 45 | }; 46 | -------------------------------------------------------------------------------- /Source/HoudiniNiagaraEditor/Private/HoudiniPointCacheExporterHBJSON.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #include "HoudiniPointCacheExporterHBJSON.h" 25 | 26 | #include "HoudiniPointCache.h" 27 | 28 | #include "CoreMinimal.h" 29 | #include "Misc/Paths.h" 30 | 31 | UHoudiniPointCacheExporterHBJSON::UHoudiniPointCacheExporterHBJSON() 32 | { 33 | SupportedClass = UHoudiniPointCache::StaticClass(); 34 | bText = false; 35 | PreferredFormatIndex = 0; 36 | FormatExtension.Add(TEXT("hbjson")); 37 | FormatDescription.Add(TEXT("HoudiniPointCache HBJSON File")); 38 | } 39 | 40 | bool UHoudiniPointCacheExporterHBJSON::IsRawFormatSupported(UHoudiniPointCache* PointCache) const 41 | { 42 | if (!Super::IsRawFormatSupported(PointCache)) 43 | return false; 44 | const bool bIsSupported = PointCache->RawDataFormatID.IsEqual("HBJSON"); 45 | return bIsSupported; 46 | } 47 | 48 | -------------------------------------------------------------------------------- /Source/HoudiniNiagaraEditor/Private/HoudiniPointCacheExporterHBJSON.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #pragma once 25 | 26 | #include "CoreMinimal.h" 27 | #include "HoudiniPointCacheExporterBase.h" 28 | 29 | #include "HoudiniPointCacheExporterHBJSON.generated.h" 30 | 31 | UCLASS() 32 | class UHoudiniPointCacheExporterHBJSON : public UHoudiniPointCacheExporterBase 33 | { 34 | GENERATED_BODY() 35 | public: 36 | UHoudiniPointCacheExporterHBJSON(); 37 | 38 | // Returns whether this exporter supports the specific object 39 | virtual bool IsRawFormatSupported(UHoudiniPointCache* PointCache) const override; 40 | }; 41 | -------------------------------------------------------------------------------- /Source/HoudiniNiagaraEditor/Private/HoudiniPointCacheExporterHCSV.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #include "HoudiniPointCacheExporterHCSV.h" 25 | #include "CoreMinimal.h" 26 | #include "Misc/Paths.h" 27 | #include "HoudiniPointCache.h" 28 | 29 | UHoudiniPointCacheExporterHCSV::UHoudiniPointCacheExporterHCSV() 30 | { 31 | SupportedClass = UHoudiniPointCache::StaticClass(); 32 | bText = false; 33 | PreferredFormatIndex = 0; 34 | FormatExtension.Add(TEXT("hcsv")); 35 | FormatDescription.Add(TEXT("HoudiniPointCache HCSV File")); 36 | } 37 | 38 | bool UHoudiniPointCacheExporterHCSV::IsRawFormatSupported(UHoudiniPointCache* PointCache) const 39 | { 40 | if (!Super::IsRawFormatSupported(PointCache)) 41 | return false; 42 | const bool bIsSupported = PointCache->RawDataFormatID.IsEqual("HCSV"); 43 | return bIsSupported; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /Source/HoudiniNiagaraEditor/Private/HoudiniPointCacheExporterHCSV.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #pragma once 25 | 26 | #include "CoreMinimal.h" 27 | #include "HoudiniPointCacheExporterBase.h" 28 | 29 | #include "HoudiniPointCacheExporterHCSV.generated.h" 30 | 31 | UCLASS() 32 | class UHoudiniPointCacheExporterHCSV : public UHoudiniPointCacheExporterBase 33 | { 34 | GENERATED_BODY() 35 | public: 36 | UHoudiniPointCacheExporterHCSV(); 37 | 38 | // Returns whether this exporter supports the specific object 39 | virtual bool IsRawFormatSupported(UHoudiniPointCache* PointCache) const override; 40 | }; 41 | -------------------------------------------------------------------------------- /Source/HoudiniNiagaraEditor/Private/HoudiniPointCacheExporterHJSON.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #include "HoudiniPointCacheExporterHJSON.h" 25 | #include "CoreMinimal.h" 26 | #include "Misc/Paths.h" 27 | #include "ShaderCompiler.h" 28 | #include "HoudiniPointCache.h" 29 | #include "CoreMinimal.h" 30 | 31 | 32 | UHoudiniPointCacheExporterHJSON::UHoudiniPointCacheExporterHJSON() 33 | { 34 | SupportedClass = UHoudiniPointCache::StaticClass(); 35 | bText = false; 36 | PreferredFormatIndex = 0; 37 | FormatExtension.Add(TEXT("hjson")); 38 | FormatDescription.Add(TEXT("HoudiniPointCache HJSON File")); 39 | } 40 | 41 | bool UHoudiniPointCacheExporterHJSON::IsRawFormatSupported(UHoudiniPointCache* PointCache) const 42 | { 43 | if (!Super::IsRawFormatSupported(PointCache)) 44 | return false; 45 | const bool bIsSupported = PointCache->RawDataFormatID.IsEqual("HJSON"); 46 | return bIsSupported; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /Source/HoudiniNiagaraEditor/Private/HoudiniPointCacheExporterHJSON.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #pragma once 25 | 26 | #include "CoreMinimal.h" 27 | #include "HoudiniPointCacheExporterBase.h" 28 | #include "HoudiniPointCacheExporterHJSON.generated.h" 29 | 30 | UCLASS() 31 | class UHoudiniPointCacheExporterHJSON : public UHoudiniPointCacheExporterBase 32 | { 33 | GENERATED_BODY() 34 | public: 35 | UHoudiniPointCacheExporterHJSON(); 36 | 37 | // Returns whether this exporter supports the specific object 38 | virtual bool IsRawFormatSupported(UHoudiniPointCache* PointCache) const override; 39 | }; 40 | -------------------------------------------------------------------------------- /Source/HoudiniNiagaraEditor/Private/HoudiniPointCacheFactory.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #include "HoudiniPointCacheFactory.h" 25 | #include "CoreMinimal.h" 26 | #include "HAL/PlatformProcess.h" 27 | #include "Misc/Paths.h" 28 | #include "ShaderCompiler.h" 29 | #include "Misc/CoreMiscDefines.h" 30 | #include "HoudiniPointCache.h" 31 | #include "EditorFramework/AssetImportData.h" 32 | #include "HAL/FileManager.h" 33 | #include "Editor.h" 34 | 35 | DEFINE_LOG_CATEGORY(LogHoudiniNiagaraEditor); 36 | 37 | #define LOCTEXT_NAMESPACE "HoudiniPointCacheFactory" 38 | 39 | ///////////////////////////////////////////////////// 40 | // UHoudiniPointCacheFactory 41 | UHoudiniPointCacheFactory::UHoudiniPointCacheFactory(const FObjectInitializer& ObjectInitializer) : Super( ObjectInitializer ) 42 | { 43 | // This factory is responsible for manufacturing HoudiniEngine assets. 44 | SupportedClass = UHoudiniPointCache::StaticClass(); 45 | 46 | // This factory does not manufacture new objects from scratch. 47 | bCreateNew = false; 48 | 49 | // This factory will open the editor for each new object. 50 | bEditAfterNew = false; 51 | 52 | // This factory will import objects from files. 53 | bEditorImport = true; 54 | 55 | // Factory does not import objects from text. 56 | bText = true; 57 | 58 | // Add supported formats. 59 | Formats.Add(FString(TEXT("hcsv;")) + NSLOCTEXT("HoudiniPointCacheFactory", "FormatHCSV", "HCSV File").ToString()); 60 | Formats.Add(FString(TEXT("hjson;")) + NSLOCTEXT("HoudiniPointCacheFactory", "FormatHJSON", "HJSON File").ToString()); 61 | Formats.Add(FString(TEXT("hbjson;")) + NSLOCTEXT("HoudiniPointCacheFactory", "FormatHBJSON", "HBJSON File").ToString()); 62 | } 63 | 64 | 65 | UObject* 66 | UHoudiniPointCacheFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) 67 | { 68 | UHoudiniPointCache* NewHoudiniPointCacheObject = NewObject(InParent, Class, Name, Flags | RF_Transactional); 69 | return NewHoudiniPointCacheObject; 70 | } 71 | 72 | UObject* 73 | UHoudiniPointCacheFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) 74 | { 75 | bOutOperationCanceled = false; 76 | 77 | UHoudiniPointCache* NewHoudiniPointCacheObject = NewObject(InParent, InClass, InName, Flags); 78 | if ( !NewHoudiniPointCacheObject ) 79 | return nullptr; 80 | 81 | if ( !NewHoudiniPointCacheObject->UpdateFromFile( Filename ) ) 82 | return nullptr; 83 | 84 | // Create reimport information. 85 | UAssetImportData * AssetImportData = NewHoudiniPointCacheObject->AssetImportData; 86 | if (!AssetImportData) 87 | { 88 | AssetImportData = NewObject< UAssetImportData >(NewHoudiniPointCacheObject, UAssetImportData::StaticClass()); 89 | NewHoudiniPointCacheObject->AssetImportData = AssetImportData; 90 | } 91 | 92 | //NewHoudiniPointCacheObject->AssetImportData->Update(Filename); 93 | AssetImportData->Update(UFactory::GetCurrentFilename()); 94 | 95 | // Broadcast notification that the new asset has been imported. 96 | GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, NewHoudiniPointCacheObject); 97 | 98 | return NewHoudiniPointCacheObject; 99 | } 100 | 101 | FText 102 | UHoudiniPointCacheFactory::GetDisplayName() const 103 | { 104 | return LOCTEXT("HoudiniPointCacheFactoryDescription", "Houdini Point Cache File"); 105 | } 106 | 107 | bool 108 | UHoudiniPointCacheFactory::DoesSupportClass(UClass * Class) 109 | { 110 | return Class == SupportedClass; 111 | } 112 | bool 113 | UHoudiniPointCacheFactory::FactoryCanImport(const FString& Filename) 114 | { 115 | const FString Extension = FPaths::GetExtension(Filename).ToLower(); 116 | 117 | if (Extension == TEXT("hcsv") || Extension == TEXT("hbjson") || Extension == TEXT("hjson")) 118 | { 119 | return true; 120 | } 121 | return false; 122 | } 123 | 124 | bool 125 | UHoudiniPointCacheFactory::CanReimport(UObject* Obj, TArray& OutFilenames) 126 | { 127 | UHoudiniPointCache* HoudiniPointCache = Cast(Obj); 128 | if (HoudiniPointCache) 129 | { 130 | if (HoudiniPointCache->AssetImportData) 131 | { 132 | HoudiniPointCache->AssetImportData->ExtractFilenames(OutFilenames); 133 | } 134 | else 135 | { 136 | OutFilenames.Add(TEXT("")); 137 | } 138 | return true; 139 | } 140 | return false; 141 | } 142 | 143 | void 144 | UHoudiniPointCacheFactory::SetReimportPaths(UObject* Obj, const TArray& NewReimportPaths) 145 | { 146 | UHoudiniPointCache* HoudiniPointCache = Cast( Obj ); 147 | if ( HoudiniPointCache 148 | && HoudiniPointCache->AssetImportData 149 | && ensure( NewReimportPaths.Num() == 1 ) ) 150 | { 151 | HoudiniPointCache->AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); 152 | } 153 | } 154 | 155 | EReimportResult::Type 156 | UHoudiniPointCacheFactory::Reimport(UObject* Obj) 157 | { 158 | UHoudiniPointCache* HoudiniPointCache = Cast(Obj); 159 | if (!HoudiniPointCache || !HoudiniPointCache->AssetImportData ) 160 | return EReimportResult::Failed; 161 | 162 | const FString FilePath = HoudiniPointCache->AssetImportData->GetFirstFilename(); 163 | if (!FilePath.Len()) 164 | return EReimportResult::Failed; 165 | 166 | UE_LOG(LogHoudiniNiagaraEditor, Log, TEXT("Performing atomic reimport of [%s]"), *FilePath); 167 | 168 | // Ensure that the file provided by the path exists 169 | if (IFileManager::Get().FileSize(*FilePath) == INDEX_NONE) 170 | { 171 | UE_LOG(LogHoudiniNiagaraEditor, Warning, TEXT("Cannot reimport: source file cannot be found.")); 172 | return EReimportResult::Failed; 173 | } 174 | 175 | bool OutCanceled = false; 176 | if (ImportObject(HoudiniPointCache->GetClass(), HoudiniPointCache->GetOuter(), *HoudiniPointCache->GetName(), RF_Public | RF_Standalone, FilePath, nullptr, OutCanceled) != nullptr) 177 | { 178 | UE_LOG(LogHoudiniNiagaraEditor, Log, TEXT("Imported successfully")); 179 | // Try to find the outer package so we can dirty it up 180 | if (HoudiniPointCache->GetOuter()) 181 | HoudiniPointCache->GetOuter()->MarkPackageDirty(); 182 | else 183 | HoudiniPointCache->MarkPackageDirty(); 184 | } 185 | else if (OutCanceled) 186 | { 187 | UE_LOG(LogHoudiniNiagaraEditor, Warning, TEXT("-- import canceled")); 188 | } 189 | else 190 | { 191 | UE_LOG(LogHoudiniNiagaraEditor, Warning, TEXT("-- import failed")); 192 | } 193 | 194 | return EReimportResult::Succeeded; 195 | } 196 | 197 | int32 198 | UHoudiniPointCacheFactory::GetPriority() const 199 | { 200 | return ImportPriority; 201 | } 202 | 203 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Source/HoudiniNiagaraEditor/Public/HoudiniNiagaraEditor.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #pragma once 25 | 26 | #include "CoreMinimal.h" 27 | #include "Modules/ModuleInterface.h" 28 | 29 | class IAssetTypeActions; 30 | class FSlateStyleSet; 31 | 32 | class FHoudiniNiagaraEditorModule : public IModuleInterface 33 | { 34 | public: 35 | 36 | /** IModuleInterface implementation */ 37 | virtual void StartupModule() override; 38 | virtual void ShutdownModule() override; 39 | 40 | private: 41 | 42 | /** AssetType actions associated with Houdini CSV assets. **/ 43 | TArray< TSharedPtr< IAssetTypeActions > > AssetTypeActions; 44 | 45 | /** Slate styleset used by this module. **/ 46 | TSharedPtr< FSlateStyleSet > StyleSet; 47 | }; -------------------------------------------------------------------------------- /Source/HoudiniNiagaraEditor/Public/HoudiniPointCacheAssetActions.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #pragma once 25 | 26 | #include "AssetTypeActions_Base.h" 27 | #include "Templates/SharedPointer.h" 28 | 29 | class UHoudiniPointCache; 30 | 31 | 32 | class FHoudiniPointCacheAssetActions : public FAssetTypeActions_Base 33 | { 34 | public: 35 | 36 | FHoudiniPointCacheAssetActions(); 37 | 38 | public: 39 | 40 | //~ FAssetTypeActions_Base overrides 41 | virtual FText GetName() const override; 42 | virtual FColor GetTypeColor() const override; 43 | virtual UClass* GetSupportedClass() const override; 44 | virtual uint32 GetCategories() override; 45 | virtual class UThumbnailInfo* GetThumbnailInfo(UObject* Asset) const override; 46 | virtual bool HasActions(const TArray& InObjects) const override; 47 | virtual void GetActions(const TArray& InObjects, struct FToolMenuSection& Section) override; 48 | 49 | virtual bool CanFilter() override; 50 | virtual FText GetAssetDescription(const FAssetData& AssetData) const override; 51 | 52 | private: 53 | 54 | // Reimport actions 55 | bool CanExecuteReimport(const TArray> Objects) const; 56 | void ExecuteReimport(const TArray> Objects) const; 57 | 58 | // Open In Editor Actions 59 | bool CanExecuteOpenInEditor(const TArray> Objects) const; 60 | void ExecuteOpenInEditor(const TArray> Objects) const; 61 | 62 | // Find In Explorer Actions 63 | bool CanExecuteFindInExplorer(const TArray> Objects) const; 64 | void ExecuteFindInExplorer(const TArray> Objects) const; 65 | }; 66 | -------------------------------------------------------------------------------- /Source/HoudiniNiagaraEditor/Public/HoudiniPointCacheFactory.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) <2018> Side Effects Software Inc. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | * SOFTWARE. 21 | * 22 | */ 23 | 24 | #pragma once 25 | 26 | #include "CoreMinimal.h" 27 | #include "UObject/ObjectMacros.h" 28 | #include "UObject/UObjectGlobals.h" 29 | #include "UObject/Object.h" 30 | #include "Factories/Factory.h" 31 | #include "EditorReimportHandler.h" 32 | #include "HoudiniPointCacheFactory.generated.h" 33 | 34 | DECLARE_LOG_CATEGORY_EXTERN(LogHoudiniNiagaraEditor, All, All); 35 | 36 | /** A factory for HoudiniPointCache assets. */ 37 | UCLASS( MinimalAPI ) 38 | class UHoudiniPointCacheFactory : public UFactory, public FReimportHandler 39 | { 40 | GENERATED_UCLASS_BODY() 41 | 42 | //~ Begin UFactory Interface 43 | 44 | // New file from the menu, uneeded! 45 | virtual UObject* FactoryCreateNew( UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn ) override; 46 | 47 | // Import file as text 48 | //virtual UObject* FactoryCreateText(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BufferEnd, FFeedbackContext* Warn) override; 49 | virtual UObject* FactoryCreateFile( UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled ) override; 50 | 51 | 52 | virtual FText GetDisplayName() const override; 53 | virtual bool DoesSupportClass( UClass * Class ) override; 54 | virtual bool FactoryCanImport( const FString& Filename ) override; 55 | 56 | //~ End UFactory Interface 57 | 58 | //~ Begin FReimportHandler Interface 59 | 60 | virtual bool CanReimport(UObject* Obj, TArray& OutFilenames) override; 61 | virtual void SetReimportPaths(UObject* Obj, const TArray& NewReimportPaths) override; 62 | virtual EReimportResult::Type Reimport(UObject* Obj) override; 63 | virtual int32 GetPriority() const override; 64 | 65 | //~ End FReimportHandler Interface 66 | }; --------------------------------------------------------------------------------